From 7135571de82b736d0c180258cec6f23f3abfc879 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 1 Feb 2026 17:33:40 -0500 Subject: [PATCH] docs: reorganize documentation structure for users and self-hosters - Remove internal development docs (phase tracking, implementation plans, security audits) - Move DEVELOPMENT.md to docs/contributing/ for contributor guidance - Move TROUBLESHOOTING.md from root to docs/ folder - Add docs/INDEX.md as navigation hub for all documentation - Clean up docs to focus on user/self-hoster facing content This reorganization separates user-facing documentation from internal contributor documentation, making the project more approachable for self-hosters. --- docs/CONVERSION_SERVICE.md | 231 --- docs/Code/bookmann/IMPLEMENTATION_PLAN.md | 1692 ----------------- docs/DEVICE_CAP_IMPLEMENTATION.md | 396 ---- docs/INDEX.md | 229 +++ docs/LEGACY_CLEANUP_PHASES_1-3_COMPLETE.md | 192 -- docs/PHASE1_COMPLETION_SUMMARY.md | 255 --- docs/PHASE2_COMPLETION_SUMMARY.md | 411 ---- docs/PHASE2_QUICK_SUMMARY.md | 170 -- docs/PROGRESS_ROUTES_ANALYSIS.md | 423 ----- docs/SECURITY_AUDIT.md | 518 ----- docs/SECURITY_ENHANCEMENTS.md | 597 ------ docs/TESTING.md | 553 ------ TROUBLESHOOTING.md => docs/TROUBLESHOOTING.md | 83 +- docs/contributing/DEVELOPMENT.md | 442 +++++ 14 files changed, 733 insertions(+), 5459 deletions(-) delete mode 100644 docs/CONVERSION_SERVICE.md delete mode 100644 docs/Code/bookmann/IMPLEMENTATION_PLAN.md delete mode 100644 docs/DEVICE_CAP_IMPLEMENTATION.md create mode 100644 docs/INDEX.md delete mode 100644 docs/LEGACY_CLEANUP_PHASES_1-3_COMPLETE.md delete mode 100644 docs/PHASE1_COMPLETION_SUMMARY.md delete mode 100644 docs/PHASE2_COMPLETION_SUMMARY.md delete mode 100644 docs/PHASE2_QUICK_SUMMARY.md delete mode 100644 docs/PROGRESS_ROUTES_ANALYSIS.md delete mode 100644 docs/SECURITY_AUDIT.md delete mode 100644 docs/SECURITY_ENHANCEMENTS.md delete mode 100644 docs/TESTING.md rename TROUBLESHOOTING.md => docs/TROUBLESHOOTING.md (75%) create mode 100644 docs/contributing/DEVELOPMENT.md diff --git a/docs/CONVERSION_SERVICE.md b/docs/CONVERSION_SERVICE.md deleted file mode 100644 index 07028b3..0000000 --- a/docs/CONVERSION_SERVICE.md +++ /dev/null @@ -1,231 +0,0 @@ -# EPUB to KEPUB Conversion Service - -## Overview - -The Conversion Service provides on-the-fly EPUB to KEPUB conversion with dual hash storage to ensure cross-device book matching continues to work after format conversion. - -## Key Features - -1. **On-Demand Conversion**: Converts EPUB to KEPUB when requested via OPDS with `?format=kepub` -2. **Dual Hash Storage**: Stores both original EPUB hash AND converted KEPUB hash in `media_item_formats` table -3. **Conversion Caching**: Caches converted files for 24 hours (configurable) to avoid re-conversion -4. **Hash Preservation**: After conversion, both hashes remain queryable for book matching -5. **Format Integrity**: Ensures converted KEPUB maintains all reading progress markers - -## Architecture - -``` -User requests book via OPDS with ?format=kepub - ↓ -Check media_item_formats table for existing KEPUB - ↓ -If KEPUB exists and is recent (< 24 hours): - → Serve pre-converted file - → Set X-Bookhoard-KEPUB-SHA256 header - ↓ -If KEPUB doesn't exist or is stale: - → Convert EPUB→KEPUB on-the-fly - → Calculate SHA-256 of converted KEPUB - → Store in media_item_formats (with converted_from_format_id) - → Serve converted file - → Set X-Bookhoard-KEPUB-SHA256 header - ↓ -Device downloads book with hash in response header - ↓ -Device syncs progress using hash for matching -``` - -## Configuration - -### Environment Variables - -Add these to your `.env` file or `system_config` table: - -```bash -# Conversion service configuration -BOOKHOARD_CONVERSION_CACHE_DIR=/var/bookhoard/cache/kepub -BOOKHOARD_CONVERSION_TOOL=/usr/bin/kepubify # or /usr/bin/ebook-convert -BOOKHOARD_CONVERSION_CACHE_TTL=24h -``` - -### Dockerfile Updates - -If using kepubify (recommended for Kobo): - -```dockerfile -# Install kepubify for EPUB→KEPUB conversion -RUN wget -O /usr/bin/kepubify https://github.com/pgaskin/kepubify/releases/latest/download/kepubify-linux-64bit \ - && chmod +x /usr/bin/kepubify -``` - -Or install Calibre for ebook-convert: - -```dockerfile -# Install Calibre for ebook-convert -RUN apt-get update && apt-get install -y calibre -``` - -## API Usage - -### Download KEPUB via OPDS - -```http -GET /opds/devices/{deviceId}/download/{bookId}?format=kepub -``` - -**Response Headers:** -- `Content-Type`: application/vnd.kobo+xml+zip -- `Content-Disposition`: attachment; filename="book.kepub.epub" -- `X-Bookhoard-UUID`: uuid-123 -- `X-Bookhoard-KEPUB-SHA256`: abc123... (KEPUB-specific hash) - -## Database Schema - -### media_item_formats Table - -```sql -CREATE TABLE media_item_formats ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - media_item_id UUID REFERENCES media_items(id) ON DELETE CASCADE, - format_type VARCHAR(10) NOT NULL, -- 'epub', 'kepub', 'pdf', 'cbz' - file_path VARCHAR(500), - file_sha256 CHAR(64), -- Hash for THIS format version - file_size_bytes BIGINT, - mime_type VARCHAR(100), - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - converted_from_format_id UUID REFERENCES media_item_formats(id), -- Track conversion chain - UNIQUE(media_item_id, format_type) -); -``` - -**Example Data:** -``` -Row 1: media_item_id=uuid-123, format_type='epub', file_sha256='abc123...' -Row 2: media_item_id=uuid-123, format_type='kepub', file_sha256='xyz789...', converted_from_format_id=Row1.id -``` - -## Implementation Details - -### Service Methods - -#### ConvertEPUBToKEPUB - -Converts an EPUB file to KEPUB format with hash storage. - -**Parameters:** -- `ctx context.Context`: Request context -- `mediaItemID pgtype.UUID`: ID of the media item -- `epubPath string`: Path to the source EPUB file - -**Returns:** -- `*ConvertedKEPUB`: Contains path, SHA256 hash, and cached status -- `error`: Conversion error if any - -**Behavior:** -1. Checks cache for existing KEPUB (recent conversions are reused) -2. Performs EPUB→KEPUB conversion using kepubify or ebook-convert -3. Calculates SHA-256 hash of converted file -4. Stores format record in database with dual hash -5. Returns converted file path and hash - -### Conversion Tools - -The service tries conversion tools in this order: - -1. **kepubify** (recommended): Purpose-built KEPUB converter - - Faster and more reliable for Kobo devices - - Download: https://github.com/pgaskin/kepubify/releases - -2. **ebook-convert** (fallback): Part of Calibre suite - - More versatile but slower - - Requires full Calibre installation - -### Cache Invalidation - -Converted KEPUB files are cached for 24 hours by default. This TTL is configurable via: -- Environment variable: `BOOKHOARD_CONVERSION_CACHE_TTL` -- Code: `conversionCacheTTL` field in `ConversionService` - -## Testing - -### Unit Tests - -```bash -go test ./internal/services/... -v -``` - -### Manual Testing with Bruno - -Use the provided Bruno test: -- `bruno/opds/Download Book KEPUB (On-the-fly Conversion).bru` - -This test verifies: -- KEPUB hash header is present -- Hash is 64 characters (SHA-256 format) -- Bookhoard UUID header is present -- Content-Type is correct for KEPUB - -## Troubleshooting - -### Conversion Failures - -**Problem**: KEPUB conversion fails -**Solution**: -1. Check if kepubify or ebook-convert is installed -2. Verify EPUB file is valid and accessible -3. Check cache directory permissions: `/var/bookhoard/cache/kepub` -4. Review conversion logs for specific error messages - -### Cache Issues - -**Problem**: Converted files not being cached -**Solution**: -1. Verify cache directory exists and is writable -2. Check `BOOKHOARD_CONVERSION_CACHE_DIR` environment variable -3. Ensure database can create media_item_formats records - -### Hash Mismatches - -**Problem**: Progress sync fails after conversion -**Solution**: -1. Verify dual hash storage: both EPUB and KEPUB hashes should exist in `media_item_formats` -2. Check `X-Bookhoard-KEPUB-SHA256` header in response -3. Ensure `converted_from_format_id` links KEPUB to source EPUB - -## Performance Considerations - -### First Conversion -- **Time**: 2-5 seconds per book (depends on file size) -- **CPU**: Medium (single-threaded conversion) -- **I/O**: Read EPUB, write KEPUB to cache - -### Cached Conversions -- **Time**: < 100ms (database lookup + file serve) -- **CPU**: Minimal -- **I/O**: Read cached KEPUB file - -### Storage Requirements -- **Cache Size**: ~1.1x original EPUB size (KEPUB is slightly larger) -- **Database**: ~200 bytes per converted format record -- **Recommendation**: 10 GB cache per 1000 books - -## Security - -### File Access -- Conversion service only processes files from library folders -- Converted files are stored in secure cache directory -- Original files are never modified - -### Input Validation -- All file paths are validated before conversion -- Media item IDs are verified against database -- User access permissions are checked via OPDS handler - -## Future Enhancements - -Potential improvements: -1. **Async Conversion**: Queue conversions for background processing -2. **Batch Conversion**: Pre-convert entire libraries during off-hours -3. **Format Variants**: Support PDF→EPUB, CBZ→EPUB, etc. -4. **Quality Settings**: Configurable conversion quality/size tradeoffs -5. **Distributed Caching**: Share cache across multiple server instances diff --git a/docs/Code/bookmann/IMPLEMENTATION_PLAN.md b/docs/Code/bookmann/IMPLEMENTATION_PLAN.md deleted file mode 100644 index d5c9311..0000000 --- a/docs/Code/bookmann/IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,1692 +0,0 @@ -# Bookhoard Implementation Plan - -## Executive Summary - -This plan implements a complete cross-device ebook management system with three major capabilities: - -1. **Universal Book Identification** - SHA-256 hashing, UUID, ISBN, ASIN, and OPF identifiers for content-based matching across devices -2. **Enhanced Collection Management** - Device-neutral "Collections" with auto-assign rules and per-device customization via shelf mappings -3. **OPDS-Based Wireless Book Delivery** - Industry-standard book distribution for Kobo, KOReader, Web, and Mobile apps -4. **Bidirectional Progress Synchronization** - Real-time sync with ContentId mapping to handle format conversions (EPUB → KEPUB) -5. **Device-Specific Configuration** - Per-device view settings and shelf mappings while maintaining unified data model - -**Key Design Principle**: Use OPDS for book acquisition (Layer 1) and internal APIs for state management (Layer 2), maintaining clear separation of concerns while enabling seamless user experience. - ---- - -## Table of Contents - -1. [Architecture Overview](#architecture-overview) -2. [Database Schema](#database-schema) -3. [API Endpoints](#api-endpoints) -4. [Implementation Phases](#implementation-phases) -5. [Device Setup Instructions](#device-setup-instructions) -6. [Security Considerations](#security-considerations) -7. [Testing Strategy](#testing-strategy) -8. [Glossary](#glossary) - ---- - -## Architecture Overview - -### System Design: Two-Layer Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Bookhoard Server │ -│ ┌──────────────────────────────────────────────────────────────┐ │ -│ │ Layer 1: Universal Book Identification │ │ -│ │ SHA-256, UUID, ISBN, ASIN, OPF identifiers │ │ -│ │ Device file aliases for path tracking │ │ -│ └──────────────────────────────────────────────────────────────────────┘ │ -│ ↓ matches books universally │ -│ ┌──────────────────────────────────────────────────────────────────────┐ │ -│ │ Layer 2: Collections (Device-Neutral Organization) │ │ -│ │ Collections with auto-assign rules │ │ -│ │ Device-specific shelf mappings (Kobo) │ │ -│ │ Per-user view settings │ │ -│ └──────────────────────────────────────────────────────────────────────┘ │ -│ ↓ provides organization │ -│ ┌──────────────────────────────────────────────────────────────────────┐ │ -│ │ Layer 3: OPDS (Primary Wireless Delivery) │ │ -│ │ Per-device OPDS feeds (Kobo, KOReader, etc.) │ │ -│ │ Format conversion (EPUB → KEPUB on-the-fly) │ │ -│ │ Dual hash storage (original + converted) │ │ -│ │ ContentId mapping (Bookhoard UUID ↔ Device ID) │ │ -│ └──────────────────────────────────────────────────────────────────────┘ │ -│ ↓ delivers books + provides IDs │ -│ ┌──────────────────────────────────────────────────────────────────────┐ │ -│ │ Layer 4: Internal APIs (State Management) │ │ -│ │ Progress sync (bidirectional) │ │ -│ │ Annotation sync (bidirectional) │ │ -│ │ Collection CRUD │ │ -│ │ WebSocket real-time updates │ │ -│ │ Device-specific operations │ │ -│ └──────────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────────┘ -``` - -### Access Method Matrix - -| Platform | Access Method | Purpose | Why This Method | -|-----------|----------------|---------|-----------------| -| **Kobo** | OPDS catalog | Kobo has built-in OPDS client, no custom Bookhoard client exists | -| **KOReader** | OPDS catalog (primary) + Sidecar + Internal API | KOReader has OPDS client, also supports plugins/sidecars for enhanced features | -| **Web App** | Internal API directly | We own and control web app, can make direct API calls efficiently | -| **Mobile App** | Internal API directly | We own and control mobile app, can make direct API calls efficiently | -| **Any OPDS Client** | OPDS catalog | Public catalog standard, any app can use it for browsing/downloading | - -### Key Design Principles - -1. **Canonical UUID Always Wins** - Bookhoard UUID (from `media_items.id`) is always used for progress tracking, never SHA-256. SHA-256 is only for matching books across devices, preventing format conversion issues. - -2. **Collections ≠ Device Inventory** - Collections are organizational metadata (like "smart playlists"). Books can be in collections without being on any device. Progress/annotations sync independently of collection membership. - -3. **OPDS for Acquisition, Internal APIs for State** - Two layers serve complementary purposes: - - OPDS: "What books are available to download?" (public catalog) - - Internal APIs: "How do I manage my books/sync state?" (private management) - -4. **Dual Hash Storage Preserves Integrity** - Store both original EPUB hash (`epub_sha256`) and converted KEPUB hash (`kepub_sha256`) in `media_item_formats` table. OPDS responses include format-specific hash in headers, enabling sidecar matching even after conversion. - -5. **Three-Tier Authentication** - Separate systems for different purposes: - - Tier 1 (Web/Mobile): JWT tokens for user authentication and permissions - - Tier 2 (Sync APIs): Device tokens for progress/annotation sync - - Tier 3 (OPDS): Device tokens for catalog access (optional per-device) - -6. **Terminology Separation** - Always use "Collections" terminology in Bookhoard UI. Map Collections to device-specific "Shelves" only at API/device level. Kobo devices see "Shelves", KOReader/Web/Mobile see "Collections". Prevents legal issues. - -7. **OPDS Primary for All Devices** - Kobo, KOReader, Web, and Mobile all use OPDS as primary book delivery method. Sidecar files provide fallback/enhanced features but are optional. - -8. **System Configuration Flexibility** - Use `system_config` table to store base URLs (`base_url`, `opds_base_url`, `api_base_url`). Sidecar generation reads from these values, enabling flexible deployment (different domains, reverse proxies) with user overrides available. - ---- - -## Database Schema - -### Schema Overview - -**7 new tables** + extensions to 4 existing tables - -### Table: media_items (Extended) - -```sql --- Universal identifiers for cross-device matching -ALTER TABLE media_items ADD COLUMN file_sha256 CHAR(64); -ALTER TABLE media_items ADD COLUMN opf_identifier VARCHAR(255); -ALTER TABLE media_items ADD COLUMN opf_uuid VARCHAR(255); - --- Hash confidence for matching priority --- 'high': OPF UUID or ISBN available --- 'medium': ISBN/ASIN available but no OPF UUID --- 'low': Only title/author match available -ALTER TABLE media_items ADD COLUMN hash_confidence VARCHAR(20); - --- Create indexes for fast lookup -CREATE INDEX idx_media_items_sha256 ON media_items(file_sha256); -CREATE INDEX idx_media_items_opf_identifier ON media_items(opf_identifier); -``` - -### Table: media_item_formats (NEW) - -```sql --- Track all format versions with their hashes --- Critical for dual hash storage and format-specific OPDS delivery - -CREATE TABLE media_item_formats ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - media_item_id UUID REFERENCES media_items(id) ON DELETE CASCADE, - format_type VARCHAR(10) NOT NULL, -- 'epub', 'kepub', 'pdf', 'cbz' - file_path VARCHAR(500), - file_sha256 CHAR(64), - file_size_bytes BIGINT, - mime_type VARCHAR(100), - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - converted_from_format_id UUID REFERENCES media_item_formats(id), -- If this is converted from another format - UNIQUE(media_item_id, format_type) -); - -CREATE INDEX idx_media_item_formats_media ON media_item_formats(media_item_id, format_type); -CREATE INDEX idx_media_item_formats_sha256 ON media_item_formats(file_sha256); -``` - -### Table: device_file_aliases (NEW) - -```sql --- Track file paths per device for cross-device matching --- When same book has different file paths on different devices, we can still match them via SHA-256 - -CREATE TABLE device_file_aliases ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - media_item_id UUID REFERENCES media_items(id) ON DELETE CASCADE, - device_id UUID REFERENCES devices(id) ON DELETE CASCADE, - file_path VARCHAR(500) NOT NULL, - file_sha256 CHAR(64), - confidence_score FLOAT DEFAULT 1.0, - last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - UNIQUE(device_id, file_path) -); - -CREATE INDEX idx_device_file_aliases_media_device ON device_file_aliases(media_item_id, device_id); -CREATE INDEX idx_device_file_aliases_sha256 ON device_file_aliases(file_sha256); -``` - -### Table: collections (NEW) - -```sql --- Device-neutral collections (separate from Kobo shelves) --- Each user has their own independent collection namespace - -CREATE TABLE collections ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES users(id) ON DELETE CASCADE, - name VARCHAR(100) NOT NULL, - description TEXT, - color VARCHAR(7), -- Hex color for UI - icon VARCHAR(50), -- Emoji or icon name - auto_assign_rules JSONB, -- See schema below for structure - view_settings JSONB, -- Per-device view preferences - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - UNIQUE(user_id, name) -); -``` - -### Table: collection_items (NEW) - -```sql --- Which books belong to each collection - -CREATE TABLE collection_items ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - collection_id UUID REFERENCES collections(id) ON DELETE CASCADE, - media_item_id UUID REFERENCES media_items(id) ON DELETE CASCADE, - added_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - added_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL, -- Manual vs auto - UNIQUE(collection_id, media_item_id) -); - -CREATE INDEX idx_collection_items_collection ON collection_items(collection_id); -CREATE INDEX idx_collection_items_media ON collection_items(media_item_id); -``` - -### Table: device_shelf_mappings (NEW) - -```sql --- Map Bookhoard collections to device-specific shelf names --- This is where "Collections" terminology maps to Kobo's "Shelves" - -CREATE TABLE device_shelf_mappings ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - collection_id UUID REFERENCES collections(id) ON DELETE CASCADE, - device_id UUID REFERENCES devices(id) ON DELETE CASCADE, - device_shelf_name VARCHAR(100), -- What appears on Kobo device - sync_direction VARCHAR(20), -- 'bidirectional', 'book_to_hoard', 'device_to_hoard', 'none' - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - UNIQUE(collection_id, device_id) -); - -CREATE INDEX idx_device_shelf_mappings_collection ON device_shelf_mappings(collection_id); -CREATE INDEX idx_device_shelf_mappings_device ON device_shelf_mappings(device_id); -``` - -### Table: device_catalogs (NEW) - -```sql --- Track OPDS downloads and map Bookhoard UUIDs to device ContentIds --- Critical for bidirectional progress sync with format conversion handling - -CREATE TABLE device_catalogs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - device_id UUID REFERENCES devices(id) ON DELETE CASCADE, - media_item_id UUID REFERENCES media_items(id) ON DELETE CASCADE, - bookhoard_uuid UUID NOT NULL, - kobo_content_id VARCHAR(255) NOT NULL, - content_id_type VARCHAR(20), -- 'bookhoard_uuid', 'kobo_generated', 'isbn_based' - available BOOLEAN DEFAULT TRUE, - delivery_date TIMESTAMP WITH TIME ZONE, - delivery_method VARCHAR(20), -- 'wireless', 'usb', 'manual' - UNIQUE(device_id, kobo_content_id) -); - -CREATE INDEX idx_device_catalogs_bookhoard ON device_catalogs(bookhoard_uuid); -CREATE INDEX idx_device_catalogs_kobo ON device_catalogs(kobo_content_id); -``` - -### Table: system_config (NEW) - -```sql --- System-wide configuration (set by admin) --- Critical for flexible deployment (different domains, reverse proxies) - -CREATE TABLE system_config ( - key VARCHAR(100) PRIMARY KEY, - value TEXT NOT NULL, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_by UUID REFERENCES users(id) -); - --- Pre-seeded values -INSERT INTO system_config (key, value) VALUES -('base_url', 'https://bookhoard.example.com'), -('opds_base_url', 'https://bookhoard.example.com/opds'), -('api_base_url', 'https://bookhoard.example.com/api'); -``` - -### Table: opds_tokens (NEW) - -```sql --- Device-specific OPDS access tokens (optional authentication) --- Allows device-level access control without exposing JWT tokens - -CREATE TABLE opds_tokens ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - device_id UUID REFERENCES devices(id) ON DELETE CASCADE, - token VARCHAR(64) UNIQUE NOT NULL, - token_type VARCHAR(20), -- 'device', 'user', 'admin' - expires_at TIMESTAMP WITH TIME ZONE NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - -CREATE INDEX idx_opds_tokens_device ON opds_tokens(device_id); -CREATE INDEX idx_opds_tokens_token ON opds_tokens(token); -``` - -### Table: kobo_shelves (Modified) - -```sql --- Reference collections instead of media_items directly --- Maintains backward compatibility with existing device_id/media_item_id columns - -ALTER TABLE kobo_shelves ADD COLUMN collection_id UUID REFERENCES collections(id); -ALTER TABLE kobo_shelves ADD COLUMN position_in_collection INTEGER; -``` - -### Schema Relationships Summary - -``` -users -├─ devices (one user can have multiple devices) -│ └─ device_file_aliases (tracks file paths per device) -│ └─ device_shelf_mappings (collection → Kobo shelf name) -│ └─ device_catalogs (OPDS + ContentId mapping) -│ └─ opds_tokens (OPDS authentication, optional) -├─ media_items (canonical book records with universal identifiers) -│ ├─ file_sha256, opf_identifier, opf_uuid -│ ├─ hash_confidence -│ ├─ reading_progress (one record per user per book) -│ └─ collection_items (which collections each book belongs to) -└─ collections (device-neutral organization) -└─ collection_items (membership) -``` - ---- - -## API Endpoints - -### Layer 1: Universal Book Identification - -#### POST `/api/sync/books/query` - -Query Bookhoard for a book by multiple identifier types with confidence scoring. - -**Request**: -```json -{ - "identifiers": ["isbn:978-0345391802", "uuid:abc-123", "opf_uuid:def456"], - "sha256": "a1b2c3d4e5f6abc123...", - "title": "The Hobbit", - "author": "J.R.R. Tolkien", - "file_size": 2456789 -} -``` - -**Response**: -```json -{ - "matches": [ - { - "media_item_id": "uuid-123", - "bookhoard_uuid": "uuid-123", - "confidence": 1.0, - "match_method": "uuid_match" - } - ], - "action": "auto_link" // or "multiple_matches", "no_match" -} -``` - -**Matching Priority**: -1. Bookhoard UUID (canonical) - Confidence: 1.0 -2. OPF UUID (from EPUB metadata) - Confidence: 0.95 -3. SHA-256 hash (content-based match) - Confidence: 0.9 -4. OPF identifier (non-UUID) - Confidence: 0.85 -5. ISBN/ASIN (standard identifiers) - Confidence: 0.8 -6. File path (device-specific, fallback) - Confidence: variable -7. Title + author + file size (last resort) - Confidence: 0.5 - -#### POST `/api/sync/link-book` - -Manual linking override for unmatched books. - -**Request**: -```json -{ - "device_file": { - "file_path": "/storage/emulated/0/Books/MyBook.epub", - "sha256": "abc123...", - "title": "My Book" - }, - "media_item_id": "uuid-123", - "confidence_score": 1.0 // User sets this -} -``` - -**Response**: -```json -{ - "status": "linked", - "device_file_alias": { - "id": "alias-id", - "media_item_id": "uuid-123", - "device_id": "kobo-device-id", - "file_path": "/storage/emulated/0/Books/MyBook.epub", - "file_sha256": "abc123...", - "confidence_score": 1.0 - } -} -``` - -#### GET `/api/sync/unlinked-books` - -List progress records that need manual linking. - -**Response**: -```json -{ - "unlinked": [ - { - "progress_id": "progress-uuid", - "device_id": "kobo-device-id", - "device_type": "kobo", - "file_path": "/mnt/sdcard/UnknownBook.epub", - "sha256": "abc123...", - "title_from_device": "Unknown Book", - "last_sync_timestamp": "2026-01-31T12:00:00Z" - } - ], - "total": 1 -} -``` - -#### GET `/api/devices/:id/file-aliases` - -View all file aliases for a specific device. - -**Response**: -```json -{ - "device_id": "device-uuid", - "aliases": [ - { - "id": "alias-id", - "media_item_id": "uuid-123", - "file_path": "/storage/emulated/0/Books/MyBook.epub", - "file_sha256": "abc123...", - "confidence_score": 1.0, - "last_seen_at": "2026-01-31T12:00:00Z" - } - ], - "total": 42 -} -``` - -### Layer 2: Collection Management - -#### POST `/api/collections` - -Create a new collection. - -**Request**: -```json -{ - "name": "Science Fiction", - "description": "My favorite sci-fi books", - "color": "#ff0000", - "icon": "🚀", - "auto_assign_rules": [ - { - "id": "rule-1", - "field": "genre", - "operator": "equals", - "value": "Science Fiction" - } - ] -} -``` - -**Response**: -```json -{ - "id": "collection-uuid", - "name": "Science Fiction", - "description": "My favorite sci-fi books", - "color": "#ff0000", - "icon": "🚀", - "auto_assign_rules": [...], - "book_count": 0, - "created_at": "2026-01-31T12:00:00Z" -} -``` - -#### GET `/api/collections` - -List all collections for current user. - -**Query Parameters**: -- `include_auto`: boolean (include auto-assigned collections) -- `sort_by`: string (name, created_at, book_count) - -**Response**: -```json -{ - "collections": [ - { - "id": "collection-uuid", - "name": "Science Fiction", - "description": "...", - "color": "#ff0000", - "icon": "🚀", - "auto_assign_rules": [...], - "book_count": 15 - } - ], - "total": 1 -} -``` - -#### GET `/api/collections/:id` - -Get single collection details with books. - -**Response**: -```json -{ - "id": "collection-uuid", - "name": "Science Fiction", - "description": "My favorite sci-fi books", - "color": "#ff0000", - "icon": "🚀", - "auto_assign_rules": [...], - "view_settings": { - "kobo": {"shelf_name": "Sci-Fi", "sync": true}, - "koreader": {"enabled": false}, - "web": {"view_mode": "grid"} - }, - "books": [ - { - "media_item_id": "uuid-1", - "title": "Foundation", - "author": "Isaac Asimov" - } - ], - "book_count": 42 -} -``` - -#### PUT `/api/collections/:id` - -Update collection. - -**Request**: -```json -{ - "name": "Sci-Fi Favorites", - "description": "Updated description", - "color": "#00ff00", - "icon": "⭐", - "auto_assign_rules": [ - { - "id": "rule-2", - "field": "series", - "operator": "equals", - "value": "Foundation" - } - ] -} -``` - -#### DELETE `/api/collections/:id` - -Delete collection and all its memberships. - -#### POST `/api/collections/:id/books` - -Add books to collection. - -**Request**: -```json -{ - "book_ids": ["uuid-1", "uuid-2", "uuid-3"], - "added_by_user": true // Manual addition vs. auto -} -``` - -#### DELETE `/api/collections/:id/books/:bookId` - -Remove book from collection. - -#### POST `/api/collections/:id/rules` - -Create auto-assign rule for collection. - -**Rule Schema**: -```json -{ - "field": "genre", // "genre", "series", "author", "language", "publisher", "copyright_year", "tags" - "operator": "equals", // "equals", "contains", "starts_with", "ends_with", "greater_than", "less_than" - "value": "Science Fiction" -} -``` - -#### PUT `/api/collections/:id/rules/:ruleId` - -Update existing rule. - -### Layer 3: Device-Specific Shelf Mappings - -#### GET `/api/devices/:id/collections` - -Get all collection → shelf mappings for a device. - -**Response**: -```json -{ - "device_id": "device-uuid", - "device_name": "My Kobo Clara", - "device_type": "kobo", - "mappings": [ - { - "collection_id": "collection-uuid", - "collection_name": "Science Fiction", - "device_shelf_name": "Sci-Fi", - "sync_direction": "bidirectional", - "created_at": "2026-01-31T12:00:00Z" - } - ], - "total": 1 -} -``` - -#### POST `/api/devices/:id/collections` - -Create new shelf mapping for device. - -**Request**: -```json -{ - "collection_id": "collection-uuid", - "device_shelf_name": "My Books", - "sync_direction": "bidirectional" -} -``` - -#### PUT `/api/devices/:id/collections/:collectionId` - -Update shelf mapping. - -#### DELETE `/api/devices/:id/collections/:collectionId` - -Remove shelf mapping. - -### Layer 3: OPDS Content Delivery - -#### GET `/opds/devices/:deviceId/catalog` - -Main OPDS 1.2 catalog feed. - -**Query Parameters**: -- `page`: integer (default 1) -- `per_page`: integer (default 50) -- `include_format`: string (optional filter) - -**Response (OPDS 1.2 XML)**: -```xml - - - urn:uuid:device-id - Bookhoard Library - 2026-01-31T12:00:00Z - - - - - - - urn:uuid:bookhoard-uuid-123 - The Hobbit - J.R.R. Tolkien - 2026-01-31T10:00:00Z - Book description... - - - - - - - - - - uuid-123 - - - abc123... - - - Science Fiction - Reading - - - - -``` - -#### GET `/opds/devices/:deviceId/search?q=` - -OPDS acquisition search endpoint. - -**Response (OPDS 1.2 XML)**: -```xml - - - urn:uuid:device-id - - - urn:uuid:bookhoard-uuid-123 - The Hobbit - J.R.R. Tolkien - 2026-01-31T10:00:00Z - - - -``` - -#### GET `/opds/devices/:deviceId/nav` - -OPDS navigation feed. - -#### GET `/opds/devices/:deviceId/download/:bookId` - -Download book with optional format conversion. - -**Query Parameters**: -- `format`: string (epub, kepub, pdf, cbz) - default: epub - -**Response Headers**: -- `Content-Type`: application/epub+zip (or format-specific) -- `Content-Disposition`: attachment; filename="The Hobbit.epub" -- `X-Bookhoard-UUID`: uuid-123 -- `X-Bookhoard-SHA256`: abc123... (for format-specific if available) -- `X-Bookhoard-KEPUB-SHA256`: xyz789... (if format=kepub) - -**Format Conversion Logic**: -```go -// Select appropriate format based on format parameter -switch format { -case "kepub": - // Check media_item_formats table for pre-converted KEPUB - if kepubFormat.Exists && kepubFormat.FilePath != "" { - Serve pre-converted file - Set X-Bookhoard-KEPUB-SHA256: kepubFormat.SHA256 - } -case "pdf": - // Serve PDF directly -case "epub": - // Serve original EPUB directly -} -``` - -#### GET `/opds/devices/:deviceId/cover/:bookId` - -Download cover image. - -**Response**: -- `Content-Type`: image/jpeg -- `Cache-Control`: public, max-age=31536000 (1 year) - -#### GET `/opds/devices/:deviceId/formats/:bookId` - -List available formats for a book. - -**Response**: -```json -{ - "media_item_id": "uuid-123", - "formats": [ - { - "format_type": "epub", - "file_path": "/path/to/book.epub", - "file_sha256": "abc123...", - "file_size_bytes": 2456789, - "mime_type": "application/epub+zip", - "available": true - }, - { - "format_type": "kepub", - "file_path": "/cache/book.kepub.epub", - "file_sha256": "xyz789...", - "file_size_bytes": 2478932, - "mime_type": "application/vnd.kobo+xml+zip", - "available": true - }, - { - "format_type": "pdf", - "file_path": "/path/to/book.pdf", - "file_sha256": "def456...", - "file_size_bytes": 5123456, - "mime_type": "application/pdf", - "available": false // Not converted yet - } - ] -} -``` - -#### POST `/api/devices/:deviceId/opds-register` - -Register device for OPDS access (generates token). - -**Request**: -```json -{ - "device_name": "My Kobo Clara", - "device_type": "kobo" -} -``` - -**Response**: -```json -{ - "opds_token": { - "token": "abc-123-def-456...", - "token_type": "device", - "expires_at": "2026-02-28T23:59:59Z", - "created_at": "2026-01-31T12:00:00Z" - }, - "opds_catalog_url": "http://192.168.1.100:8765/opds/devices/kobo-id/catalog", - "refresh_interval": 3600 -} -``` - -### Layer 4: Enhanced Device Sync - -#### POST `/api/sync/kobo/markup` - -Kobo progress sync with ContentId mapping (enhanced). - -**Request (Enhanced)**: -```json -{ - "ReadingSync": [ - { - "ContentId": "kobo_xyz", - "PercentRead": 60.0, - "RemainingTimeMin": 120, - "ReadingEvent": "BookRead" - } - ], - "BookmarkSync": [...], - "Metadata": true // NEW: Include collection metadata -} -``` - -**ContentId Mapping Logic**: -```go -// Step 1: Try direct ContentId lookup -catalog, err := db.GetDeviceCatalogByKoboContentId(ctx, contentId) -if err == nil && catalog.Valid { - // Found! Use canonical Bookhoard UUID - bookhoardUUID = catalog.BookhoardUUID - return bookhoardUUID, nil -} - -// Step 2: ContentId not found - try SHA-256 (if looks like hash) -if len(contentId) == 64 && looksLikeSHA256(contentId) { - mediaItem, err := db.GetMediaItemBySHA256(ctx, contentId) - if err == nil { - return mediaItem.ID, nil - } -} - -// Step 3: Not found - create unlinked entry -return uuid.Nil{}, errors.New("unlinked book") -``` - -#### POST `/api/sync/kobo/bookmark` - -Kobo bookmark sync (enhanced). - -#### GET `/api/sync/kobo/initialization` - -Kobo library sync with collection metadata (enhanced). - -**Response (Enhanced)**: -```json -{ - "LibrarySync": [ - { - "ContentId": "kobo_xyz", - "ContentType": "6", - "Title": "The Hobbit", - "Author": "J.R.R. Tolkien", - "PercentRead": 60.0, - - // NEW: Collection metadata - "Categories": ["Science Fiction", "Reading"], - "BookhoardUUID": "uuid-123" // Canonical ID - } - ] -} -``` - -### Layer 5: Enhanced KOReader Sync - -#### POST `/api/sync/koreader/progress` - -KOReader progress sync with SHA-256 support (enhanced). - -**Request (Enhanced)**: -```json -{ - "sync_mode": "immediate", // or "checkpoint" - "books": [ - { - "uuid": "uuid-123", // Optional: highest priority - "sha256": "abc123...", // NEW: Device can send hash - "file_path": "/storage/emulated/0/Books/MyBook.epub", - "title": "The Hobbit", - "authors": ["J.R.R. Tolkien"], - "percentage": 75.0, - "epubcfi": "/6/4!/2/4[chapter_1]@0:100", - "chapter": 12, - "character": 1234567, - "page": 312, - "total_pages": 416 - } - ] -} -``` - -**SHA-256 Matching Logic**: -```go -// Priority 1: UUID provided (highest confidence) -if book.UUID != "" { - return book.UUID, nil -} - -// Priority 2: SHA-256 provided (medium confidence) -if book.SHA256 != "" { - mediaItem, err := db.GetMediaItemBySHA256(ctx, book.SHA256) - if err == nil { - return mediaItem.ID, nil - } - return mediaItem.ID, nil -} - -// Priority 3: Create device file alias (if file path provided) -if book.FilePath != "" { - // Check if alias exists - alias, err := db.GetDeviceFileAlias(ctx, deviceID, book.FilePath) - if err == nil { - // Create new alias with medium confidence - db.CreateDeviceFileAlias(ctx, CreateDeviceFileAliasParams{ - MediaItemID: mediaItemID, - DeviceID: deviceID, - FilePath: book.FilePath, - FileSHA256: book.SHA256, - ConfidenceScore: 0.7, - }) - return alias.MediaItemID, nil - } - // Use existing alias - return alias.MediaItemID, nil -} - -// Priority 4: Search by title/author + file size (fallback) -return mediaItem.ID, nil -``` - -#### POST `/api/sync/koreader/bookmarks` - -KOReader annotations sync with SHA-256 support. - -**Request (Enhanced)**: -```json -{ - "bookmarks": [ - { - "uuid": "uuid-123", - "sha256": "abc123...", // NEW: For cross-device matching - "file_path": "/storage/emulated/0/Books/MyBook.epub", - "title": "The Hobbit", - "page": 312, - "text": "Great quote on page 312" - } - ] -} -``` - -### Layer 5: Sidecar Configuration - -#### GET `/api/sync/sidecar/:deviceId` - -Download unified `.bookhoard.json` configuration file. - -**Response**: -```json -{ - "version": "1.0", - "bookhoard": { - "opds_catalog": "http://192.168.1.100:8765/opds/devices/kobo-id/catalog", - "sync_api": "http://192.168.1.100:8765/api/sync/kobo", - "opds_base_url": "http://192.168.1.100:8765/opds", - "api_base_url": "http://192.168.1.100:8765/api", - "device_id": "kobo-device-uuid" - }, - "books": { - "sha256:abc123...": { - "bookhoard_uuid": "uuid-123", - "title": "The Hobbit", - "author": "J.R.R. Tolkien", - "available_formats": ["epub", "kepub"] - } - }, - "collections": [ - { - "name": "Sci-Fi", - "shelf_mapping": "Science Fiction", - "book_ids": ["uuid-1", "uuid-2", "uuid-3"] - } - ], - "last_updated": "2026-01-31T12:00:00Z" -} -``` - -#### POST `/api/sync/sidecar/:deviceId/register` - -Validate sidecar file upload from device. - -### Layer 6: System Configuration - -#### GET `/api/admin/system-config` - -Get system-wide configuration. - -**Response**: -```json -{ - "config": { - "base_url": "https://bookhoard.example.com", - "opds_base_url": "https://bookhoard.example.com/opds", - "api_base_url": "https://bookhoard.example.com/api", - "auto_convert_kepub": true, - "default_opds_refresh_interval": 3600 - } -} -``` - -#### PUT `/api/admin/system-config` - -Update system configuration. - ---- - -## Implementation Phases - -### Phase 1: Database Schema (Week 1) - -**Deliverables**: -- Create SQL schema file for all new tables -- Add columns to existing tables -- Run schema migrations on development database -- Update sqlc code generation -- Write rollback migration script - -**Tasks**: -1.1 Create migration SQL file `database/schema/001_universal_identifiers.sql` -1.2 Update database models -1.3 Write database queries -1.4 Test database queries manually -1.5 Write rollback migration script - -### Phase 2: Scanner Enhancement (Week 1-2) - -**Deliverables**: -- Enhanced scanner code -- OPF parser implementation -- Unit tests for hash calculation - -**Tasks**: -2.1 Implement SHA-256 calculation in `ebook_scanner.go` - - Stream file reading (don't load entire file into memory) - - Algorithm: crypto/sha256 from Go standard library -2.2 Implement OPF parser - - Extract `` tags from EPUB OPF files - - Parse both OEBPS and OPF 2.0 formats - - Extract UUIDs from `` attributes - - Handle multiple identifiers per file -2.3 Implement format detection - - Detect file format based on extension and content -2.4 Pre-convert EPUB to KEPUB during scan - - Use `ebooklib` or similar library for conversion -2.5 Store all format hashes in `media_item_formats` table - -**Hash Confidence Logic**: -``` -HIGH (confidence = 1.0): - - EPUB with valid `` (UUID format) - - ISBN found (standard format) - -MEDIUM (confidence = 0.7): - - OPF identifier present (non-UUID custom format) - - ISBN/ASIN matched via metadata sources - -LOW (confidence = 0.5): - - Only title/author match available -``` - -**Deliverables**: -- Enhanced scanner code -- OPF parser implementation -- KEPUB conversion utility -- Unit tests for hash calculation - -### Phase 3: Universal Book Matching Engine (Week 2) - -**Deliverables**: -- Matching engine implementation -- Book query API handlers -- Manual linking API endpoints -- Unit tests for matching logic - -**Matching Algorithm**: -``` -Priority 1: Bookhoard UUID (canonical) - - If device sends UUID, use directly - - Confidence = 1.0 - -Priority 2: OPF UUID (from EPUB metadata) - - Match against `opf_uuid` column - - Confidence = 0.95 - -Priority 3: SHA-256 hash - - Match against `file_sha256` column - - Confidence = 0.9 - -Priority 4: OPF identifier (non-UUID) - - Match against `opf_identifier` column - - Confidence = 0.85 - -Priority 5: ISBN/ASIN (standard identifiers) - - Match against `isbn` and `asin` columns - - Confidence = 0.8 - -Priority 6: File path (device-specific) - - Match via `device_file_aliases` table - - Confidence = from alias record - -Priority 7: Title + author + file size (fallback) - - Fuzzy search on title - - Exact match on author - - Within 10% file size variance - - Confidence = 0.5 - -Priority 8: Title only (last resort) - - Fuzzy title match - - Confidence = 0.3 -``` - -**Deliverables**: -- Matching engine implementation -- Book query API handlers -- Manual linking API -- Unlinked books API -- Unit tests for matching logic - -### Phase 4: Collection Management System (Week 2-3) - -**Deliverables**: -- Collections CRUD handlers -- Auto-assign rules engine -- Device shelf mapping handlers -- Add collection book management -- Per-device view settings - -**Auto-Assign Rules Engine**: -```go -type Rule struct { - ID string - Field string // "genre", "series", "author", "language", "publisher", "copyright_year", "tags" - Operator string // "equals", "contains", "starts_with", "ends_with", "greater_than", "less_than" - Value string // Exact value to match -} - -type RuleEvaluation struct { - RuleID string - Matches bool - Confidence float -} - -func EvaluateRules(mediaItem MediaItem, rules []Rule) []RuleEvaluation { - // Evaluate each rule against mediaItem metadata - // Return which rules match and overall confidence - // Higher-priority rules take precedence -} -``` - -**Rule Priority System**: -1. Rule with `priority` field (1-10, higher first) -2. Multiple rules can apply to same book -3. User can configure logical operators (AND, OR) - -**Deliverables**: -- Collections API handlers -- Rules engine implementation -- Database queries for collections -- Unit tests for rule evaluation - -### Phase 5: OPDS Implementation (Week 3) - -**Deliverables**: -- OPDS XML serializer -- OPDS catalog feed handler -- OPDS search endpoint -- Book download with format support -- Cover image serving -- ContentId mapping to OPDS responses -- On-the-fly KEPUB conversion -- Device authorization checks -- OPDS token management - -**OPDS Response Structure (OPDS 1.2)**: -```xml - - urn:uuid:bookhoard-uuid-123 - The Hobbit - J.R.R. Tolkien - 2026-01-31T10:00:00Z - - - - - - - - - - uuid-123 - - - abc123... - xyz789... - - - Science Fiction - Reading - -``` - -**Format Conversion Strategy**: -``` -When user downloads with ?format=kepub: - -1. Check media_item_formats table -2. If KEPUB exists and is recent: - - Serve pre-converted file - - Set X-Bookhoard-SHA256: kepubFormat.SHA256 -3. If KEPUB doesn't exist: - - Convert EPUB to KEPUB on-the-fly - - Cache in media_item_formats table - - Serve converted file - - Set X-Bookhoard-SHA256: kepubFormat.SHA256 -4. Serve PDF directly -``` - -**Deliverables**: -- OPDS handlers implementation -- OPDS XML serializers -- KEPUB conversion utility -- OPDS authentication -- Integration with device_catalogs table - -### Phase 6: Enhanced Kobo Sync (Week 3-4) - -**Deliverables**: -- Updated Kobo handler to use ContentId mapping -- Bidirectional ContentId lookup -- Add unlinked book detection -- Integrate collection metadata into library sync -- Support for legacy API endpoints - -**ContentId Mapping Logic**: -```go -// Step 1: Try direct ContentId lookup -catalog, err := db.GetDeviceCatalogByKoboContentId(ctx, contentId) -if err == nil && catalog.Valid { - // Found! Use canonical Bookhoard UUID - return catalog.BookhoardUUID, nil -} - -// Step 2: ContentId not found - try SHA-256 -if len(contentId) == 64 && looksLikeSHA256(contentId) { - mediaItem, err := db.GetMediaItemBySHA256(ctx, contentId) - if err == nil { - return mediaItem.ID, nil - } -} - -// Step 3: Not found - create unlinked entry -return uuid.Nil{}, errors.New("unlinked book") -``` - -**Deliverables**: -- Updated Kobo sync handlers -- ContentId mapping system -- Unlinked book tracking -- Integration with collection metadata - -### Phase 7: Enhanced KOReader Sync (Week 4) - -**Deliverables**: -- Updated KOReader handler to accept SHA-256 -- Implement device file alias creation -- Integrate auto-linking with confidence thresholds -- Add SHA-256 matching for annotations - -**SHA-256 Matching for Progress Sync**: -```go -// Priority 1: UUID provided (highest confidence) -if book.UUID != "" { - return book.UUID, nil -} - -// Priority 2: SHA-256 provided (medium confidence) -if book.SHA256 != "" { - mediaItem, err := db.GetMediaItemBySHA256(ctx, book.SHA256) - if err == nil { - return mediaItem.ID, nil - } -} - -// Priority 3: Create device file alias -if book.FilePath != "" { - alias, err := db.GetDeviceFileAlias(ctx, deviceID, book.FilePath) - if err == nil { - // Create new alias - db.CreateDeviceFileAlias(ctx, CreateDeviceFileAliasParams{ - MediaItemID: mediaItemID, - DeviceID: deviceID, - FilePath: book.FilePath, - FileSHA256: book.SHA256, - ConfidenceScore: 0.7, - }) - return alias.MediaItemID, nil - } - return alias.MediaItemID, nil -} -``` - -**Deliverables**: -- Enhanced KOReader handlers -- SHA-256 matching integration -- Device file alias system integration -- Auto-linking with configurable thresholds - -### Phase 8: Sidecar Configuration System (Week 4) - -**Deliverables**: -- Sidecar JSON generation -- Sidecar download/upload handlers -- System configuration support - -**Sidecar File Format (Enhanced)**: -```json -{ - "version": "1.0", - "bookhoard": { - "opds_catalog": "http://192.168.1.100:8765/opds/devices/kobo-id/catalog", - "sync_api": "http://192.168.1.100:8765/api/sync/kobo", - "opds_base_url": "http://192.168.1.100:8765/opds", - "api_base_url": "http://192.168.1.100:8765/api", - "device_id": "kobo-device-uuid" - }, - "books": { - "sha256:abc123...": { - "bookhoard_uuid": "uuid-123", - "title": "The Hobbit", - "author": "J.R.R. Tolkien", - "available_formats": ["epub", "kepub"] - } - }, - "collections": [ - { - "name": "Sci-Fi", - "shelf_mapping": "Science Fiction", - "book_ids": ["uuid-1", "uuid-2", "uuid-3"] - } - ], - "opds_enabled": true, - "sidecar_enabled": true, - "last_updated": "2026-01-31T12:00:00Z" -} -``` - -**Deliverables**: -- Sidecar generation system -- System configuration support -- Admin UI for system settings - -### Phase 9: Frontend Implementation (Week 5-6) - -**Deliverables**: -- Collections management pages -- Device configuration pages -- Enhanced progress visualization with sync sources -- Unlinked books resolution UI -- Collection rule builder UI -- Device-specific view settings UI - -**Deliverables**: -- Collections list/detail pages -- Device management interface -- Progress sync dashboard with device indicators -- Book matching UI with confidence indicators - -### Phase 10: Documentation & Testing (Week 6) - -**Deliverables**: -- Updated device setup guides -- Complete API documentation -- Test suite covering all scenarios -- User acceptance testing - -**Deliverables**: -- KOBO_SETUP.md update with OPDS workflow -- KOREADER_SETUP.md new file with OPDS instructions -- Complete API reference documentation -- User guides for all device types - ---- - -## Device Setup Instructions - -### Kobo E-Reader Setup (OPDS Primary Method) - -#### Option 1: OPDS Catalog (Recommended - Wireless Delivery + Progress Sync) - -**Step 1: Download Configuration File** -``` -1. Log into Bookhoard web UI -2. Go to Device Management → Your Kobo device -3. Click "Download Configuration" button -4. File downloads as `.bookhoard.json` -``` - -**Step 2: Configure Kobo for OPDS** -``` -1. On Kobo, go to Settings → Sync & Backup -2. Tap "Add Content Server" or "Add OPDS Feed" -3. Enter URL from `.bookhoard.json`: - http://192.168.1.100:8765/opds/devices/YOUR_DEVICE_ID/catalog -4. Kobo will automatically: - - Connect to Bookhoard - - Browse your library wirelessly - - Download books directly - - Sync reading progress back to Bookhoard -``` - -**Step 3: Wireless Book Acquisition** -``` -1. On Kobo, go to "My Books" section -2. Browse Bookhoard catalog via OPDS -3. Tap on any book to download wirelessly -4. Book appears on Kobo device -5. Start reading - progress syncs automatically -``` - -**How Progress Sync Works**: -- Kobo generates ContentId for each book -- ContentId mapped to Bookhoard UUID in device_catalogs table -- When Kobo syncs progress, Bookhoard uses canonical UUID -- Format conversion (KEPUB) doesn't break progress tracking - -### KOReader Setup - -#### Option 1: OPDS Catalog (Recommended) - -**Step 1: Download Configuration File** -``` -Same as Kobo setup above -``` - -**Step 2: Configure KOReader for OPDS** -``` -1. Open KOReader settings -2. Enable "OPDS catalog" in network/synchronization section -3. Enter OPDS URL from `.bookhoard.json`: - http://192.168.1.100:8765/opds/devices/YOUR_DEVICE_ID/catalog -4. KOReader will automatically: - - Connect to Bookhoard catalog - - Browse and download books wirelessly - - Sync progress using SHA-256 matching - - Create file aliases automatically -``` - -**Step 3: Wireless Book Acquisition** -``` -1. Open KOReader file browser -2. Tap "+" button to add OPDS catalog -3. Browse Bookhoard catalog -4. Download books directly -5. Start reading -``` - -#### Option 2: Sidecar File (Alternative - Enhanced Progress Sync) - -**For offline or simple setup** - -**Step 1: Download Sidecar** -``` -Same as Kobo setup above -``` - -**Step 2: Place Sidecar on KOReader** -``` -Place in KOReader's config directory -``` - -**Step 3: Use Sidecar for Progress Sync** -``` -KOReader plugin reads .bookhoard.json -→ Matches local files to Bookhoard UUIDs via SHA-256 -→ Syncs progress using canonical UUIDs -→ Works offline -``` - -### Web & Mobile Setup - -``` -OPDS catalog automatically available at: -/opds/devices/:deviceId/catalog - -Apps can: -- Browse entire library wirelessly -- Download books directly -- See collection metadata -- Sync progress via existing internal APIs -``` - ---- - -## Security Considerations - -### Authentication Layers - -**Layer 1: Web & Mobile (Internal API)** -``` -Uses: JWT tokens -Issued by: POST /api/auth/login, /api/auth/refresh -Validated: On each request via middleware -Revoked by: POST /api/auth/logout -Stored in: refresh_tokens table (not devices table) -``` - -**Layer 2: Sync APIs (Device Tokens)** -``` -Issued by: Device registration endpoint -Stored in: devices.auth_token field -Validated by: Device authentication middleware -``` - -**Layer 3: OPDS (Device Tokens, Optional)** -``` -Issued by: /api/devices/:id/opds-register -Stored in: opds_tokens table -Scope: Device-specific access to catalog - -Can be: Public (no authentication required) -``` - -### Data Privacy - -1. **Progress & Annotations**: Always associated with user_id in database -2. **Collections**: User-scoped - each user sees only their collections -3. **File Aliases**: Device-specific - never shared across users -4. **Device Catalogs**: Links stored per-device - no cross-user leakage -5. **Sidecar Files**: Contain only user's device token and book mappings - -### Access Control - -**OPDS Authorization Flow:** -``` -1. OPDS request includes device_id in URL path -2. Server validates: - a. Device exists - b. Device belongs to requesting user - c. Book is in user's visible library -3. If validation passes: Serve OPDS feed -``` - -**Public Catalog Option**: -- Can be enabled in system_config -- Allows guest users to browse without device registration -- Still respects library visibility per user - ---- - -## Testing Strategy - -### Unit Tests - -**Coverage Areas**: -1. Hash calculation accuracy (SHA-256, OPF extraction) -2. Matching algorithm priorities -3. Collection rule evaluation -4. OPDS XML serialization -5. Format conversion integrity - -### Integration Tests - -**Test Scenarios**: -1. Cross-device book matching (same book, different paths) -2. Format conversion (EPUB → KEPUB) with hash integrity -3. Collection auto-assign (rules fire correctly) -4. Bidirectional progress sync (Kobo ↔ KOReader) -5. OPDS catalog generation and pagination -6. Sidecar file generation and validation - -### Manual Testing Checklist - -**Kobo Workflow**: -- [ ] Download `.bookhoard.json` from web UI -- [ ] Transfer to Kobo via USB -- [ ] Configure OPDS URL on Kobo -- [ ] Browse catalog wirelessly -- [ ] Download book -- [ ] Read 50% of book -- [ ] Verify progress syncs to Bookhoard - -**KOReader Workflow**: -- [ ] Download `.bookhoard.json` from web UI -- [ ] Configure OPDS URL in KOReader -- [ ] Browse catalog wirelessly -- [ ] Download book -- [ ] Read 75% of book -- [ ] Verify progress syncs to Bookhoard - -**Cross-Device Scenario**: -- [ ] Add book to Bookhoard (EPUB scanned) -- [ ] Download to Kobo via OPDS -- [ ] Sync progress (60%) from Kobo -- [ ] Open same book on KOReader (side-loaded) -- [ ] Read to 75% on KOReader -- [ ] Verify progress shows 75% (latest from either device) -- [ ] Verify sync sources tracked correctly - ---- - -## Glossary - -- **Bookhoard UUID**: Canonical identifier for a book in Bookhoard system (from `media_items.id`). Always used for progress tracking, never SHA-256. SHA-256 is only for matching books across devices. -- **ContentId**: Device-generated identifier (e.g., Kobo's "kobo_abc"). Mapped to Bookhoard UUID in `device_catalogs` table. Used for progress sync after OPDS downloads. -- **SHA-256**: Cryptographic hash of file contents. Used for content-based matching across devices. Critical for identifying same book on different devices. -- **OPF UUID**: Unique identifier from EPUB metadata ``. High-confidence identifier format. -- **OPF Identifier**: Any identifier from EPUB OPF file (custom format). Medium-confidence identifier format. -- **ISBN**: International Standard Book Number (13 digits). Medium-confidence standard identifier. -- **ASIN**: Amazon Standard Identification Number (10 characters). Medium-confidence standard identifier. -- **Collections**: Device-neutral organizational groups in Bookhoard (e.g., "Science Fiction", "Reading"). Books can be in collections without being on any device. Collections organize library, not track device inventory. -- **Shelves**: Device-specific organization (e.g., Kobo's terminology). Map Collections to device-specific "Shelves" only at device-level. Bookhoard UI always uses "Collections" terminology. -- **OPDS**: Open Publication Distribution System. Industry standard for book catalogs. All e-reader platforms have OPDS clients. Kobo, KOReader, Aldiko, FBReader, Web browsers can use OPDS catalogs. -- **Internal APIs**: Bookhoard's private REST/WebSocket endpoints for state management. Web and mobile apps use these directly. Used for two-way sync, collections, WebSocket real-time updates. -- **Device File Alias**: Mapping of device-specific file paths to Bookhoard UUIDs. Enables cross-device matching when same book has different file paths. -- **Hash Confidence**: Scoring system (0.0-1.0) for automatic book matching reliability. Higher values = more reliable match. -- **Dual Hash Storage**: Storing both original EPUB hash (`epub_sha256`) and converted KEPUB hash (`kepub_sha256`). Preserves hash integrity when files are converted. OPDS responses include format-specific hash for sidecar matching. -- **Format Conversion**: Transcoding between book formats (EPUB → KEPUB). KEPUB adds Kobo-specific markup. Critical for Kobo optimization but shouldn't break progress tracking. -- **Media Item Formats**: Tracks all format versions with their hashes. Pre-convert EPUB to KEPUB during scan for optimal performance. -- **System Config**: Key-value store for system-wide settings (base_url, opds_base_url, api_base_url). Enables flexible deployment. -- **OPDS Tokens**: Per-device access tokens for OPDS catalog browsing. Optional - can also support user-scoped and admin tokens. -- **Sidecar File**: `.bookhoard.json` - Unified configuration file for devices. Contains OPDS URLs, sync API endpoints, book mappings, collection mappings. -- **Auto-Assign Rules**: Configurable criteria for automatically adding books to collections. Fields: genre, series, author, language, publisher, copyright_year, tags. Operators: equals, contains, starts_with, ends_with, greater_than, less_than. -- **Sync Direction**: For device shelf mappings. 'bidirectional' (sync both ways), 'book_to_hoard' (send to device), 'device_to_hoard' (read from device), 'none' (no sync). -- **View Settings**: Per-device preferences for how collections are displayed (grid vs list, which collections are visible). -- **Unlinked Book**: Progress record without proper media_item_id or failed ContentId lookup. Needs manual user resolution. - ---- - -## Summary - -This comprehensive implementation plan provides: - -- **7 new database tables** with proper indexing -- **50+ API endpoints** across 6 layers (identification, collections, OPDS, sync, configuration) -- **10-week phased implementation** with clear deliverables -- **Complete device setup guides** for Kobo, KOReader, Web, and Mobile -- **Three-tier authentication model** for security (JWT, device tokens, OPDS optional) -- **Glossary** of all terminology and concepts -- **Testing strategies** covering unit, integration, and manual validation - -The plan is designed for systematic execution while maintaining architectural consistency and enabling human oversight throughout the development process. All decisions from our conversations have been incorporated, providing a complete roadmap for implementing Bookhoard as a comprehensive cross-device ebook management system. \ No newline at end of file diff --git a/docs/DEVICE_CAP_IMPLEMENTATION.md b/docs/DEVICE_CAP_IMPLEMENTATION.md deleted file mode 100644 index eda8542..0000000 --- a/docs/DEVICE_CAP_IMPLEMENTATION.md +++ /dev/null @@ -1,396 +0,0 @@ -# Device Cap Implementation - Task 2 - -**Date**: February 1, 2026 -**Status**: ✅ COMPLETE - ---- - -## Overview - -Implemented admin-configurable device cap per user as specified in the session requirements. This allows administrators to control the maximum number of devices each user can register. - ---- - -## Changes Made - -### 1. Database Schema - -**File**: `database/schema/schema.sql` - -Added `max_devices` column to `users` table: -```sql -max_devices INTEGER DEFAULT 10 -``` - -- **Default Value**: 10 devices per user -- **Constraints**: 1-100 devices (validated in handler) -- **Purpose**: Prevent excessive device registrations per user - -### 2. Database Queries - -**File**: `internal/database/queries/queries.sql` - -Added two new queries: - -#### UpdateUserMaxDevices -```sql --- name: UpdateUserMaxDevices :exec -UPDATE users SET max_devices = $2, updated_at = NOW() WHERE id = $1; -``` -- Updates max devices limit for a specific user -- Parameters: user_id (UUID), max_devices (integer) - -#### CountUserDevices -```sql --- name: CountUserDevices :one -SELECT COUNT(*) FROM devices WHERE user_id = $1; -``` -- Counts current devices for a user -- Useful for validation and display - -### 3. Handler Implementation - -**File**: `internal/handlers/auth.go` - -Added new handler method: - -#### UpdateUserMaxDevicesRequest -```go -type UpdateUserMaxDevicesRequest struct { - MaxDevices int32 `json:"max_devices" validate:"required,min=1,max=100"` -} -``` - -#### UpdateUserMaxDevices Handler -```go -func (h *AuthHandler) UpdateUserMaxDevices(c echo.Context) error { - userID := c.Param("id") - if userID == "" { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "user id required"}) - } - - var req UpdateUserMaxDevicesRequest - if err := c.Bind(&req); err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) - } - - if err := c.Validate(&req); err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) - } - - userUUID, err := uuid.Parse(userID) - if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"}) - } - - err = h.db.UpdateUserMaxDevices(c.Request().Context(), database.UpdateUserMaxDevicesParams{ - ID: pgtype.UUID{Bytes: userUUID, Valid: true}, - MaxDevices: pgtype.Int4{Int32: req.MaxDevices, Valid: true}, - }) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) - } - - return c.JSON(http.StatusOK, map[string]string{"message": "max devices updated"}) -} -``` - -**Features**: -- Validates user ID format (UUID) -- Validates max_devices range (1-100) -- Requires admin authentication -- Updates user's max_devices in database -- Returns success/error messages - -### 4. UserList Update - -**File**: `internal/handlers/auth.go` - -Updated `UserList` struct to include max_devices: - -```go -type UserList struct { - ID string `json:"id"` - Email string `json:"email"` - Username string `json:"username"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Theme string `json:"theme"` - Role string `json:"role"` - MaxDevices int32 `json:"max_devices"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` -} -``` - -### 5. Route Registration - -**File**: `cmd/server/main.go` - -Added new admin route: - -```go -admin.PUT("/users/:id/max-devices", authHandler.UpdateUserMaxDevices) -``` - -- **Path**: `/api/auth/users/:id/max-devices` -- **Method**: PUT -- **Auth**: Admin only (uses AdminMiddleware) -- **Validation**: 1-100 devices - -### 6. SQLC Code Generation - -**File**: `internal/database/sqlc.yaml` - -- Regenerated database code using `sqlc generate` -- Created `UpdateUserMaxDevices` and `UpdateUserMaxDevicesParams` types -- Created `CountUserDevices` function - -### 7. Bruno API Collection - -Created 4 Bruno files for API testing: - -#### 1. Update User Max Devices (Documentation) -- **Path**: `bruno/user/admin/Update User Max Devices.bru` -- Contains complete API documentation -- Includes all validation rules -- Example payloads for common values - -#### 2. Update User Max Devices - Success -- **Path**: `bruno/user/admin/Update User Max Devices - Success.bru` -- Tests successful update to 5 devices -- Expected: 200 OK - -#### 3. Update User Max Devices - Invalid Zero -- **Path**: `bruno/user/admin/Update User Max Devices - Invalid Zero.bru` -- Tests validation of zero devices (below minimum) -- Expected: 400 Bad Request - -#### 4. Update User Max Devices - Exceeds Maximum -- **Path**: `bruno/user/admin/Update User Max Devices - Invalid Too High.bru` -- Tests validation of 101 devices (above maximum) -- Expected: 400 Bad Request - -#### 5. Update User Max Devices - Missing ID -- **Path**: `bruno/user/admin/Update User Max Devices - Missing ID.bru` -- Tests missing user ID in URL -- Expected: 400 Bad Request - -### 8. Go Tests - -**File**: `cmd/server/tests/device_cap_test.go` - -Created comprehensive test suite with 7 test functions: - -#### TestUpdateUserMaxDevices -Tests successful updates: -- Update to 5 devices -- Update to 10 devices (default) -- Update to 50 devices -- Update to 100 devices (maximum) - -#### TestUpdateUserMaxDevicesValidation -Tests validation rules: -- Zero devices (below minimum) -- Negative devices -- 101 devices (above maximum) -- 1000 devices (far above maximum) - -#### TestUpdateUserMaxDevicesAuth -Tests authentication: -- No authorization token -- Non-admin user attempting to access endpoint -- Expected: 401 Unauthorized or 403 Forbidden - -#### TestUpdateUserMaxDevicesNonExistentUser -Tests with non-existent user ID: -- Expected: 500 Internal Server Error or 404 Not Found - -#### TestUpdateUserMaxDevicesMissingUserID -Tests with missing user ID in URL: -- Expected: 400 Bad Request - -#### TestListUsersIncludesMaxDevices -Tests that max_devices field is included in user list response: -- Ensures backward compatibility -- Validates new field is present in API response - -#### Helper Functions -- `createAdminUser`: Creates admin user for testing -- `createTestUserForMaxDevices`: Creates regular user for testing -- `getAdminToken`: Retrieves admin JWT token -- `loginTestUserByCredentials`: Logs in user with credentials - ---- - -## API Specification - -### PUT /api/auth/users/:id/max-devices - -Updates the maximum number of devices a user can register. - -**Authentication**: Required (Admin only) - -**URL Parameters**: -- `id` (string, required): User ID (UUID) - -**Request Body**: -```json -{ - "max_devices": 10 -} -``` - -**Request Validation**: -- `max_devices` (integer, required): Must be between 1 and 100 - -**Response** (Success): -```json -{ - "message": "max devices updated" -} -``` - -**Response** (Error): -```json -{ - "error": "validation error" -} -``` - -**Status Codes**: -- `200`: Success -- `400`: Bad Request (missing id, invalid UUID, validation error) -- `401`: Unauthorized (missing or invalid token) -- `403`: Forbidden (non-admin user) -- `500`: Internal Server Error - -### GET /api/auth/users - -Updated to include `max_devices` field in response: - -**Response**: -```json -[ - { - "id": "uuid", - "email": "user@example.com", - "username": "username", - "first_name": "John", - "last_name": "Doe", - "role": "user", - "theme": "tokyo-night", - "max_devices": 10, - "created_at": "2026-01-31T12:00:00Z", - "updated_at": "2026-01-31T12:00:00Z" - } -] -``` - ---- - -## Testing - -### Unit Tests -- ✅ Created comprehensive test suite -- ✅ All tests compile successfully -- ✅ Tests cover success cases -- ✅ Tests cover validation -- ✅ Tests cover authentication -- ✅ Tests cover edge cases - -### Bruno Tests -- ✅ Created 4 test scenarios -- ✅ Success case -- ✅ Validation failure cases -- ✅ Missing parameters - -### Manual Testing Checklist -- [ ] Admin can update max devices to valid values -- [ ] Non-admin users cannot update max devices -- [ ] Validation rejects values < 1 -- [ ] Validation rejects values > 100 -- [ ] Invalid user ID returns appropriate error -- [ ] Missing user ID returns 400 error -- [ ] User list includes max_devices field -- [ ] Default value of 10 is enforced for new users - ---- - -## Integration Notes - -### Device Registration Enforcement - -The `max_devices` setting should be enforced during device registration: - -**In `InitiateRegistration` handler** (`internal/handlers/devices.go`): -```go -// Count user's current devices -deviceCount, err := h.db.CountUserDevices(ctx, userID) - -if deviceCount >= user.MaxDevices { - return c.JSON(http.StatusForbidden, map[string]string{ - "error": "device limit reached", - "max_devices": user.MaxDevices, - }) -} -``` - -### Backward Compatibility - -- ✅ Default value of 10 maintains existing behavior -- ✅ Existing users without max_devices set use default -- ✅ User list response enhanced with new field -- ✅ No breaking changes to existing endpoints - ---- - -## Security Considerations - -1. **Admin-Only Access**: Endpoint protected by AdminMiddleware -2. **Input Validation**: Strict validation of max_devices range (1-100) -3. **UUID Validation**: User ID validated as proper UUID format -4. **SQL Injection Protection**: Uses sqlc parameterized queries -5. **Rate Limiting**: Inherits existing rate limiting from middleware - ---- - -## Performance Considerations - -1. **Database Indexes**: Consider adding index on (user_id) for CountUserDevices -2. **Caching**: User max_devices could be cached for frequent checks -3. **Batch Operations**: Consider batch updates for multiple users - ---- - -## Future Enhancements - -1. **Per-Device-Type Caps**: Allow different limits for different device types -2. **Time-Based Limits**: Device limits that expire after time period -3. **Plan-Based Limits**: Different device caps based on user subscription tier -4. **Audit Logging**: Log when max_devices is changed (who changed, from, to, when) - ---- - -## Summary - -✅ **Complete**: -- Database schema updated with max_devices column -- Database queries added (UpdateUserMaxDevices, CountUserDevices) -- Handler implemented with full validation -- Route registered as admin-only -- Bruno API collection created (4 files) -- Go test suite created (7 test functions, 20+ test cases) -- User list updated to include new field - -**Production Ready**: Yes -**Breaking Changes**: None -**Backward Compatible**: Yes - ---- - -**Next Steps**: -1. Add device limit enforcement in device registration flow -2. Update user management UI to display/edit max_devices -3. Consider adding audit logging for admin actions -4. Add user notifications when device limit is reached diff --git a/docs/INDEX.md b/docs/INDEX.md new file mode 100644 index 0000000..eb327a7 --- /dev/null +++ b/docs/INDEX.md @@ -0,0 +1,229 @@ +# Bookhoard Documentation Index + +Complete guide to Bookhoard documentation. Find what you need quickly. + +--- + +## 🚀 Quick Links + +### For New Users +1. [README.md](../README.md) - **Start here!** Project overview and quick start +2. [docs/SYNC_USER_GUIDE.md](SYNC_USER_GUIDE.md) - Understanding and using sync +3. [docs/devices/KOBO_SETUP.md](devices/KOBO_SETUP.md) - Kobo e-reader setup +4. [docs/devices/KOREADER_SETUP.md](devices/KOREADER_SETUP.md) - KOReader setup + +### For Self-Hosting +1. [docs/TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Deployment and troubleshooting +2. [.env.example](../.env.example) - Secrets configuration (JWT and DB password) + +### For Contributors +1. [docs/contributing/DEVELOPMENT.md](contributing/DEVELOPMENT.md) - Development workflow and architecture +2. [docs/API_REFERENCE.md](API_REFERENCE.md) - Complete API documentation +3. [docs/COLLECTIONS_API.md](COLLECTIONS_API.md) - Collections API +4. [docs/api/WEBSOCKET_API.md](api/WEBSOCKET_API.md) - WebSocket protocol + +--- + +## 📚 Documentation by Topic + +### Getting Started +- **[README.md](../README.md)** - Project overview, features, quick start guide +- **[docs/contributing/DEVELOPMENT.md](contributing/DEVELOPMENT.md)** - Development environment setup + +### Deployment & Operations +- **[docs/TROUBLESHOOTING.md](TROUBLESHOOTING.md)** - Common deployment issues and solutions +- **[.env.example](../.env.example)** - Required secrets (JWT_SECRET, DBPASS) +- **[docker-compose.yml](../docker-compose.yml)** - Operational configuration with defaults +- **[Makefile](../Makefile)** - Build and test commands + +### Using Sync Features +- **[docs/SYNC_USER_GUIDE.md](SYNC_USER_GUIDE.md)** - Universal sync user guide + - Understanding sync + - Book matching and auto-linking + - Conflict resolution + - Best practices + +### Device Setup +- **[docs/devices/KOBO_SETUP.md](devices/KOBO_SETUP.md)** - Kobo e-reader configuration + - Device registration + - Sync configuration + - OPDS wireless book delivery + - Troubleshooting + +- **[docs/devices/KOREADER_SETUP.md](devices/KOREADER_SETUP.md)** - KOReader configuration + - Installation on Kindle/Kobo/PocketBook + - Sync setup + - OPDS catalog access + - Troubleshooting + +### API Documentation +- **[docs/API_REFERENCE.md](API_REFERENCE.md)** - Complete REST API reference + - Authentication + - User management + - Libraries + - Media items + - Reading progress + - Notes & highlights + - Analytics + - Book matching + - OPDS + - Sync protocols (KOReader, Kobo) + - WebSocket + +- **[docs/COLLECTIONS_API.md](COLLECTIONS_API.md)** - Collections API + - Create and manage collections + - Auto-assign rules + - Test rules + - Bulk operations + - Device shelf mappings + +- **[docs/api/WEBSOCKET_API.md](api/WEBSOCKET_API.md)** - WebSocket protocol + - Connection flow + - Message format + - Real-time sync broadcasts + - Authentication + +### Contributing +- **[docs/contributing/DEVELOPMENT.md](contributing/DEVELOPMENT.md)** - Development guide + - Architecture overview + - Directory structure + - Local development setup + - Testing guidelines + - Code style + - Deployment + +- **[PROJECT_GUIDELINES.md](../PROJECT_GUIDELINES.md)** - Development rules and standards + - Critical prohibitions + - Mandatory requirements + - Error recovery protocol + +### Reference +- **[go.mod](../go.mod)** - Go dependencies +- **[database/schema/schema.sql](../database/schema/schema.sql)** - Database schema +- **[bruno/](../bruno/)** - API test collections + +--- + +## 📖 Reading Path by Role + +### Self-Hoster / End User +**Goal**: Set up and use Bookhoard for reading + +1. Start with [README.md](../README.md) - Understand what Bookhoard is +2. Follow quick start in README.md to get running +3. Set up your device: + - Kobo: [docs/devices/KOBO_SETUP.md](devices/KOBO_SETUP.md) + - KOReader: [docs/devices/KOREADER_SETUP.md](devices/KOREADER_SETUP.md) +4. Learn about sync: [docs/SYNC_USER_GUIDE.md](SYNC_USER_GUIDE.md) +5. If issues arise: [docs/TROUBLESHOOTING.md](TROUBLESHOOTING.md) + + + +### Developer +**Goal**: Contribute to Bookhoard or integrate with it + +1. Start with [README.md](../README.md) - Project overview +2. Read [docs/contributing/DEVELOPMENT.md](contributing/DEVELOPMENT.md) - Architecture and setup +3. Review [docs/API_REFERENCE.md](API_REFERENCE.md) - API endpoints +4. Check [PROJECT_GUIDELINES.md](../PROJECT_GUIDELINES.md) - Development rules +5. Explore codebase and contribute! + +### API Integrator +**Goal**: Build integration with Bookhoard + +1. Review [README.md](../README.md) - Feature overview +2. Study [docs/API_REFERENCE.md](API_REFERENCE.md) - All endpoints +3. Check specialized docs: + - Collections: [docs/COLLECTIONS_API.md](COLLECTIONS_API.md) + - WebSocket: [docs/api/WEBSOCKET_API.md](api/WEBSOCKET_API.md) + - Sync: [docs/SYNC_USER_GUIDE.md](SYNC_USER_GUIDE.md) +4. Test with [bruno/](../bruno/) collections + +--- + +## 🔍 Quick Find + +### "How do I..." +| ...do this? | See this document | +|-------------|------------------| +| ...install Bookhoard? | [README.md](../README.md) - Quick Start | +| ...set up my Kobo? | [docs/devices/KOBO_SETUP.md](devices/KOBO_SETUP.md) | +| ...set up KOReader? | [docs/devices/KOREADER_SETUP.md](devices/KOREADER_SETUP.md) | +| ...understand sync? | [docs/SYNC_USER_GUIDE.md](SYNC_USER_GUIDE.md) | +| ...resolve conflicts? | [docs/SYNC_USER_GUIDE.md](SYNC_USER_GUIDE.md) - Managing Conflicts | +| ...match books? | [docs/SYNC_USER_GUIDE.md](SYNC_USER_GUIDE.md) - Book Matching | +| ...troubleshoot deployment? | [docs/TROUBLESHOOTING.md](TROUBLESHOOTING.md) | +| ...use the API? | [docs/API_REFERENCE.md](API_REFERENCE.md) | +| ...set up development? | [docs/contributing/DEVELOPMENT.md](contributing/DEVELOPMENT.md) | +| ...contribute code? | [docs/contributing/DEVELOPMENT.md](contributing/DEVELOPMENT.md) - Contributing | + +### "Where is..." +| ...this information? | See this document | +|-------------------|------------------| +| ...features list? | [README.md](../README.md) | +| ...database schema? | [database/schema/schema.sql](../database/schema/schema.sql) | +| ...API endpoints? | [docs/API_REFERENCE.md](API_REFERENCE.md) | +| ...secrets config? | [.env.example](../.env.example) | +| ...operational config? | [docker-compose.yml](../docker-compose.yml) | +| ...deployment issues? | [docs/TROUBLESHOOTING.md](TROUBLESHOOTING.md) | + +--- + +## 📊 Documentation Statistics + +| File | Lines | Purpose | Audience | +|------|-------|---------|----------| +| README.md | 150 | Overview & quick start | Everyone | +| contributing/DEVELOPMENT.md | 450 | Development workflow | Contributors | +| API_REFERENCE.md | 1,300+ | Complete REST API | Developers, integrators | +| COLLECTIONS_API.md | 494 | Collections API | Developers, integrators | +| SYNC_USER_GUIDE.md | 350+ | Sync usage guide | End users | +| TROUBLESHOOTING.md | 300 | Deployment troubleshooting | Self-hosters | +| KOBO_SETUP.md | 598 | Kobo setup | Kobo users | +| KOREADER_SETUP.md | 504 | KOReader setup | KOReader users | +| WEBSOCKET_API.md | 676 | WebSocket protocol | Developers | +| PROJECT_GUIDELINES.md | 250 | Development rules | Developers | + +**Total**: ~5,000 lines of comprehensive documentation + +--- + +## 🎯 Common Tasks + +### Set up a new device +1. Device setup guide: [docs/devices/KOBO_SETUP.md](devices/KOBO_SETUP.md) or [docs/devices/KOREADER_SETUP.md](devices/KOREADER_SETUP.md) +2. Sync overview: [docs/SYNC_USER_GUIDE.md](SYNC_USER_GUIDE.md) +3. Troubleshooting: Device-specific setup guides + +### Troubleshoot sync issues +1. Check [docs/SYNC_USER_GUIDE.md](SYNC_USER_GUIDE.md) - "Managing Conflicts" and "Best Practices" +2. Review device-specific guide for common issues +3. Check [docs/TROUBLESHOOTING.md](TROUBLESHOOTING.md) for general issues + +### Integrate with Bookhoard API +1. Start with [docs/API_REFERENCE.md](API_REFERENCE.md) - Complete API reference +2. Check [docs/COLLECTIONS_API.md](COLLECTIONS_API.md) for collections +3. Review [docs/api/WEBSOCKET_API.md](api/WEBSOCKET_API.md) for real-time updates +4. Use [bruno/](../bruno/) test collections as examples + +### Deploy to production +1. Follow [README.md](../README.md) quick start +2. Configure environment: [.env.example](../.env.example) +3. Review [docs/TROUBLESHOOTING.md](TROUBLESHOOTING.md) for common issues +4. Check [docs/contributing/DEVELOPMENT.md](contributing/DEVELOPMENT.md) for performance tuning + +--- + +## 📝 Contributing to Documentation + +When adding new features: +1. Update [README.md](../README.md) - Add to features list if user-facing +2. Update [docs/API_REFERENCE.md](API_REFERENCE.md) - Document new endpoints +3. Add/update tests in [bruno/](../bruno/) +4. Update relevant guides (SYNC_USER_GUIDE.md, device guides, etc.) +5. Keep [PROJECT_GUIDELINES.md](../PROJECT_GUIDELINES.md) in mind + +--- + +**Last Updated**: 2026-02-01 +**Bookhoard Version**: 1.0 diff --git a/docs/LEGACY_CLEANUP_PHASES_1-3_COMPLETE.md b/docs/LEGACY_CLEANUP_PHASES_1-3_COMPLETE.md deleted file mode 100644 index 1b62aa7..0000000 --- a/docs/LEGACY_CLEANUP_PHASES_1-3_COMPLETE.md +++ /dev/null @@ -1,192 +0,0 @@ -# Legacy Code Cleanup - Phases 1-3 Complete - -## Summary - -Successfully completed Phases 1-3 of the legacy migration code cleanup for Bookhoard. - ---- - -## ✅ Phase 1: Documentation Cleanup (Complete) - -### Changes Made: - -1. **internal/handlers/ebook.go** - - Removed misleading backward compatibility comments (lines 1294-1297) - - Cleaned up references to non-existent `GetEbookNotes` and `GetEbookHighlights` handlers - -2. **database/schema/schema.sql** - - Removed historical migration comments (lines 370-371) - - Deleted reference to `user_ebook_folders` table replacement - -3. **README.md** - - Removed "Ebook Compatibility (Backward Compatible)" section (lines 180-189) - - Removed backward compatibility bullet point from Database Schema section (line 366) - - Removed "Backward Compatibility Views" section from Database documentation (lines 590-592) - -**Impact**: Cleaner documentation, no behavioral changes - ---- - -## ✅ Phase 2: Dead Code Removal (Complete) - -### Changes Made: - -1. **internal/handlers/auth.go** - - Deleted `AddEbookFolder` handler function (lines 566-569) - - Deleted `GetEbookFolders` handler function (lines 571-574) - - Deleted `DeleteEbookFolder` handler function (lines 576-579) - - Deleted `DeleteEbookFolderRequest` struct (lines 562-564) - -**Total**: ~20 lines of dead code removed - -**Impact**: No behavioral changes (routes already unregistered, returning HTTP 410 Gone) - ---- - -## ✅ Phase 3: Test Suite Cleanup (Complete) - -### Files Deleted: - -1. **cmd/server/tests/isbn_and_library_test.go** (507 lines) - - All tests used deprecated `/api/ebooks` endpoint - - No equivalent library-related tests to preserve - - Tests covered: - - ISBN normalization (8 test cases) - - Library requirement validation - - ISBN edge cases - - Library auto-selection - -2. **cmd/server/tests/edge_cases_test.go** (82 lines removed) - - Removed `TestPaginationAndFiltering` function - - Deleted 4 pagination test cases using `/api/ebooks` endpoint: - - Negative limit - - Negative offset - - Very large limit - - Valid pagination - -**Total**: 589 lines of outdated tests removed - -**Impact**: Cleaner test suite, no failing tests - ---- - -## ✅ Phase 3: Equivalent Tests Created (Complete) - -### New Test File: **cmd/server/tests/media_item_isbn_test.go** (467 lines) - -Created comprehensive replacement tests using `/api/media-items` endpoint: - -1. **TestMediaItemISBNNormalization** - - 8 ISBN-10/ISBN-13 normalization test cases - - Tests hyphens, spaces, mixed formats - - Uses real API calls (not mocks) - -2. **TestMediaItemISBNEdgeCases** - - Empty ISBN handling - - Multiple hyphens normalization - - Trailing/leading hyphen removal - -3. **TestMediaItemsPagination** - - Valid pagination parameters - - Pagination with offset - - Negative limit validation - - Negative offset validation - - Maximum limit enforcement (1000 cap) - -4. **TestMediaItemLibraryRequirement** - - Media-item creation without library (should fail) - - Media-item creation with existing library (should succeed) - -5. **TestUpdateMediaItemISBN** - - Update media-item with ISBN normalization - -**Helper Function Added**: -- `createTestLibrary(t, ts, token, name)` - Creates test library and returns ID - -**Impact**: Modern, working tests that exercise actual API functionality - ---- - -## 📊 Overall Statistics - -| Category | Files Modified | Files Deleted | Files Created | Lines Removed | Lines Added | -|----------|----------------|----------------|----------------|---------------|-------------| -| Documentation | 3 | 0 | 0 | ~30 | 0 | -| Dead Code | 1 | 0 | 0 | ~20 | 0 | -| Old Tests | 1 | 1 | 0 | ~82 | 0 | -| New Tests | 0 | 0 | 1 | 507 | 467 | -| **TOTAL** | **5** | **1** | **1** | **~639** | **467** | - -**Net Result**: -172 lines of code, significantly cleaner codebase - ---- - -## 🧪 Testing Status - -### Tests Deleted: -- ✅ `isbn_and_library_test.go` - All using `/api/ebooks` (deprecated) -- ✅ `edge_cases_test.go` - Pagination tests using `/api/ebooks` (deprecated) - -### Tests Created: -- ✅ `media_item_isbn_test.go` - Comprehensive replacement using `/api/media-items` - -### Tests Preserved: -- ✅ `library_test.go` - Contains equivalent pagination tests for `/api/media-items` -- ✅ All other test files remain unchanged - ---- - -## ⏭️ Next Steps: Phase 4 (Not Implemented Yet) - -### Database Views Removal - -**5 backward compatibility views to potentially drop**: -1. `ebooks` view (lines 122-128) -2. `ebook_reading_progress` view (lines 160-168) -3. `ebook_ratings` view (lines 340-348) -4. `ebook_notes` view (lines 350-358) -5. `ebook_highlights` view (lines 360-368) - -**Prerequisites**: -1. ✅ User has requested verification of view usage first -2. Search codebase for view references -3. Run full test suite to ensure no dependencies -4. Check Bruno API collections -5. Verify no direct SQL queries use views - -**Action Items** (When approved): -1. Grep codebase for view names -2. Check application logs -3. Run integration tests -4. If safe, drop views from schema.sql - ---- - -## 🎯 Success Criteria - All Met - -- ✅ Documentation cleaned up (no backward compatibility mentions) -- ✅ Dead code removed (unreachable handlers deleted) -- ✅ Outdated tests removed (no `/api/ebooks` references remain) -- ✅ Equivalent tests created (modern `/api/media-items` tests) -- ✅ No behavioral changes (only cleanup, no functional modifications) -- ✅ Code is cleaner and easier to maintain -- ✅ Tests are more realistic (use actual API instead of mocks) - ---- - -## 📝 Notes - -- All changes are backward compatible (we only removed deprecated code) -- No database schema changes required in Phases 1-3 -- Test file is syntactically correct (helper functions will be available in full test suite) -- Ready to run full test suite to verify all changes - ---- - -## 🚀 Ready for Next Phase - -Phases 1-3 are complete and tested. Ready to proceed with Phase 4 (Database Views Removal) when you approve the verification plan. - -**Total legacy migration code removed**: ~639 lines -**New modern tests added**: 467 lines -**Net improvement**: Cleaner, more maintainable codebase with better test coverage diff --git a/docs/PHASE1_COMPLETION_SUMMARY.md b/docs/PHASE1_COMPLETION_SUMMARY.md deleted file mode 100644 index 53f0baa..0000000 --- a/docs/PHASE1_COMPLETION_SUMMARY.md +++ /dev/null @@ -1,255 +0,0 @@ -# Phase 1 Implementation Summary: File Conversion Pipeline - -## Completed Tasks - -### 1. Conversion Service Implementation ✅ -**File**: `internal/services/conversion_service.go` - -Created a complete EPUB→KEPUB conversion service with: -- On-demand conversion triggered by OPDS requests -- Dual hash storage (EPUB and KEPUB hashes) in `media_item_formats` table -- Conversion caching (24-hour TTL by default) -- Support for kepubify (preferred) and ebook-convert (fallback) -- SHA-256 hash calculation for converted files - -**Key Methods**: -- `ConvertEPUBToKEPUB(ctx, mediaItemID, epubPath)`: Main conversion method -- `convertEPUB(epubPath, kepubPath)`: Executes conversion tool -- `calculateSHA256(filePath)`: Computes file hash - -### 2. OPDS Handler Updates ✅ -**File**: `internal/handlers/opds.go` - -Updated the OPDS handler to integrate with conversion service: -- Modified `NewOPDSHandler` to accept conversion service dependency -- Enhanced `DownloadBook` method to support on-the-fly KEPUB conversion -- Updated response headers to include `X-Bookhoard-KEPUB-SHA256` for KEPUB downloads -- Properly handles format-specific hash headers - -**Behavior**: -- When `?format=kepub` is requested: - 1. Checks for cached KEPUB (serves if < 24 hours old) - 2. If not cached, converts EPUB→KEPUB on-the-fly - 3. Stores converted file with dual hash in database - 4. Serves converted file with KEPUB-specific hash header - -### 3. Service Registration in Main ✅ -**File**: `cmd/server/main.go` - -Integrated conversion service into server initialization: -- Added `services` package import -- Created `conversionService` instance with cache directory configuration -- Updated `opdsHandler` initialization to include conversion service -- Registered all OPDS routes (`/opds/devices/*`) - -**New Routes**: -- `GET /opds/devices/:deviceId/catalog` - OPDS catalog feed -- `GET /opds/devices/:deviceId/search` - OPDS search endpoint -- `GET /opds/devices/:deviceId/nav` - OPDS navigation feed -- `GET /opds/devices/:deviceId/download/:bookId` - Book download with format conversion -- `GET /opds/devices/:deviceId/cover/:bookId` - Cover image serving -- `GET /opds/devices/:deviceId/formats/:bookId` - List available formats - -### 4. Testing Infrastructure ✅ -**File**: `internal/services/conversion_service_test.go` - -Created comprehensive unit tests: -- `TestConvertEPUBToKEPUB`: Tests basic conversion and dual hash storage -- `TestConvertCaching`: Verifies cache hit for recent conversions -- `TestConversionChain`: Ensures conversion chain integrity - -**File**: `bruno/opds/Download Book KEPUB (On-the-fly Conversion).bru` - -Created Bruno API test that validates: -- KEPUB hash header presence and format -- Bookhoard UUID header -- Correct Content-Type for KEPUB format - -### 5. Documentation ✅ -**File**: `docs/CONVERSION_SERVICE.md` - -Comprehensive documentation covering: -- Architecture overview with flow diagrams -- Configuration options (environment variables, Dockerfile) -- API usage examples -- Database schema details -- Implementation details -- Troubleshooting guide -- Performance considerations -- Security considerations - -### 6. Configuration Updates ✅ - -**File**: `.env.example` -Added conversion service configuration variables: -- `BOOKHOARD_CONVERSION_CACHE_DIR` - Cache directory path -- `BOOKHOARD_CONVERSION_TOOL` - Conversion tool to use -- `BOOKHOARD_CONVERSION_CACHE_TTL` - Cache time-to-live - -**File**: `Dockerfile` -Added kepubify installation in final stage: -```dockerfile -RUN wget -O /usr/bin/kepubify https://github.com/pgaskin/kepubify/releases/latest/download/kepubify-linux-64bit \ - && chmod +x /usr/bin/kepubify -``` - -## Technical Implementation Details - -### Dual Hash Storage Strategy - -The conversion service maintains hash integrity for cross-device matching: - -1. **Original EPUB Hash**: Stored in `media_item_formats` with `format_type='epub'` -2. **Converted KEPUB Hash**: Stored in new row with `format_type='kepub'` -3. **Conversion Chain**: KEPUB row references EPUB row via `converted_from_format_id` - -**Example Database State**: -```sql --- EPUB format (original) -INSERT INTO media_item_formats (media_item_id, format_type, file_sha256, ...) -VALUES (uuid-123, 'epub', 'abc123...', ...); - --- KEPUB format (converted) -INSERT INTO media_item_formats (media_item_id, format_type, file_sha256, converted_from_format_id, ...) -VALUES (uuid-123, 'kepub', 'xyz789...', , ...); -``` - -### Conversion Process Flow - -``` -OPDS Request: GET /opds/devices/{id}/download/{bookId}?format=kepub - ↓ -Check media_item_formats for existing KEPUB - ↓ - ┌────┴────┐ - │ │ - Found Not Found - │ │ - │ ├─ Is recent (< 24h)? ── No ──► Convert EPUB→KEPUB - │ │ ↓ - │ │ Calculate SHA-256 - │ │ ↓ - │ │ Store in database - │ │ ↓ - │ └───────────────────────── Serve converted file - │ - └─ Serve cached file - ↓ -Set X-Bookhoard-KEPUB-SHA256 header - ↓ -Stream file to client -``` - -### Error Handling - -The conversion service handles multiple failure scenarios: - -1. **EPUB Not Found**: Returns 404 error -2. **Conversion Failure**: Returns 500 with error message -3. **Hash Calculation Error**: Returns 500, prevents serving unhashed file -4. **Database Storage Error**: Returns 500, preserves converted file for retry -5. **Cache Directory Error**: Creates directory if missing, fails if permissions insufficient - -### Performance Characteristics - -- **First Conversion**: 2-5 seconds (file size dependent) -- **Cached Conversion**: < 100ms (database lookup + file serve) -- **Storage Overhead**: ~10% per converted file (KEPUB vs EPUB) -- **Cache Hit Rate**: Expected > 95% after initial library conversion - -## Verification Steps - -### Build Verification -```bash -go build -o /tmp/bookhoard-test ./cmd/server -# Success: Exit code 0 -``` - -### Manual Testing -1. Start server with conversion service enabled -2. Register a device and obtain device ID -3. Add a book to library (EPUB format) -4. Request KEPUB download via OPDS: - ```bash - curl "http://localhost:8765/opds/devices/{deviceId}/download/{bookId}?format=kepub" \ - -I | grep -i "X-Bookhoard-KEPUB-SHA256" - ``` -5. Verify response headers: - - `X-Bookhoard-KEPUB-SHA256` present (64-character hash) - - `X-Bookhoard-UUID` present - - `Content-Type: application/vnd.kobo+xml+zip` - -### Automated Testing -```bash -# Run unit tests -go test ./internal/services/... -v - -# Run Bruno tests (via Bruno CLI or UI) -bruno run "bruno/opds/Download Book KEPUB (On-the-fly Conversion).bru" -``` - -## Integration Points - -### Existing Codebases -- **OPDS Handler**: Enhanced with conversion service dependency -- **Database Queries**: Uses existing `CreateMediaItemFormat` and `GetMediaItemFormatByType` -- **Media Item Model**: Leverages existing `MediaItemFormats` struct -- **Configuration System**: Integrates with existing `.env` pattern - -### Future Enhancements -The conversion service is designed to support: -1. Additional format conversions (PDF→EPUB, CBZ→EPUB) -2. Async/batch conversion queues -3. Pre-conversion during library scan -4. Distributed caching across multiple instances -5. Custom conversion quality settings - -## Compliance with Project Guidelines - -✅ **Podman Only**: No Docker-specific code (kepubify works with any container runtime) -✅ **No Local Builds**: Conversion happens via container, not local binary -✅ **pgx v5 Standards**: Uses existing database queries with pgx types -✅ **Atomic Changes**: Conversion doesn't modify original EPUB, creates new KEPUB -✅ **Functional Programming**: Service uses pure functions for hash calculation -✅ **TypeScript Only**: No new JavaScript (service is pure Go) -✅ **Minimal Structure Changes**: Only adds new service file, updates existing handler -✅ **Multiple Logical Commits**: Can be split into separate commits if desired - -## Next Steps - -### Immediate (Phase 1 Complete) -1. ✅ Conversion service implemented -2. ✅ OPDS handler integrated -3. ✅ Routes registered -4. ✅ Tests created -5. ✅ Documentation written -6. ✅ Configuration updated - -### Follow-up (Optional Enhancements) -1. Add Prometheus metrics for conversion performance -2. Implement async conversion queue for bulk operations -3. Add conversion progress tracking via WebSocket -4. Support for additional formats (PDF, CBZ) -5. Pre-conversion during library scan - -## Deployment Checklist - -Before deploying to production: -- [ ] Verify kepubify is installed in container -- [ ] Set `BOOKHOARD_CONVERSION_CACHE_DIR` to persistent volume -- [ ] Configure `BOOKHOARD_CONVERSION_CACHE_TTL` appropriately -- [ ] Test conversion with actual EPUB files -- [ ] Monitor cache directory size and set up cleanup -- [ ] Verify database has `media_item_formats` table -- [ ] Test dual hash storage with device sync -- [ ] Document cache storage requirements (1.1x library size) -- [ ] Set up monitoring for conversion failures - -## Rollback Plan - -If issues arise: -1. Set `BOOKHOARD_CONVERSION_TOOL=""` to disable conversion -2. Remove `conversionService` parameter from `NewOPDSHandler` -3. OPDS handler will fall back to serving EPUB only -4. No database schema changes required (schema already existed) -5. No data migration needed (new rows are additive only) diff --git a/docs/PHASE2_COMPLETION_SUMMARY.md b/docs/PHASE2_COMPLETION_SUMMARY.md deleted file mode 100644 index 2f861d5..0000000 --- a/docs/PHASE2_COMPLETION_SUMMARY.md +++ /dev/null @@ -1,411 +0,0 @@ -# Phase 2: Advanced Unlinked Book Resolution - Implementation Summary - -## Overview - -Successfully implemented bulk resolution workflows and automated matching suggestions for unlinked books. This enhances the existing unlinked book tracking system with user-friendly bulk operations. - -## Completed Tasks - -### 1. Database Queries ✅ - -**File**: `internal/database/queries/queries.sql` - -Added three new queries: -- `GetUnlinkedBookByID` - Retrieve single unlinked book by ID -- `DeleteUnlinkedBook` - Remove unlinked book entry -- `ListUnresolvedUnlinkedBooks` - List unresolved books with pagination - -### 2. Bulk Resolution API Endpoints ✅ - -**File**: `internal/handlers/book_matching.go` - -#### POST `/api/sync/bulk-link-books` - -Bulk link multiple unlinked books at once. - -**Request Body**: -```json -{ - "links": [ - { - "unlinked_book_id": "uuid-1", - "media_item_id": "uuid-2", - "confidence_score": 1.0 - }, - { - "unlinked_book_id": "uuid-3", - "media_item_id": "uuid-4", - "confidence_score": 0.9 - } - ] -} -``` - -**Response**: -```json -{ - "results": [ - { - "unlinked_book_id": "uuid-1", - "status": "success", - "media_item_id": "uuid-2" - } - ], - "total": 2, - "successful": 1, - "failed": 1 -} -``` - -**Status Values**: -- `success` - Book linked successfully -- `error` - Linking failed (book not found, alias creation failed) -- `warning` - Linked but failed to mark as resolved - -#### POST `/api/sync/auto-link-books` - -Automatically attempt to link unlinked books using matching algorithm with confidence threshold. - -**Request Body**: -```json -{ - "confidence_threshold": 0.8, - "limit": 50 -} -``` - -**Response**: -```json -{ - "auto_linked": 15, - "results": [ - { - "unlinked_book_id": "uuid-1", - "title": "The Hobbit", - "matched_media_item_id": "uuid-2", - "confidence": 0.95, - "match_method": "sha256_match" - } - ] -} -``` - -**Behavior**: -1. Fetches unresolved unlinked books (up to `limit`) -2. Queries book matching service for each book -3. Auto-links books with confidence ≥ threshold -4. Creates device file aliases and marks as resolved -5. Returns count and details of auto-linked books - -#### GET `/api/sync/unlinked-books/:id/suggestions` - -Get matching suggestions for a specific unlinked book. - -**Response**: -```json -{ - "unlinked_book_id": "uuid-1", - "title_from_device": "The Hobbit", - "sha256": "", - "suggestions": [ - { - "media_item_id": "uuid-2", - "bookhoard_uuid": "uuid-2", - "confidence": 0.95, - "match_method": "sha256_match" - } - ], - "total_suggestions": 1, - "action": "auto_link" -} -``` - -### 3. Frontend Template Enhancement ✅ - -**File**: `templates/unlinked_books.templ` - -Added bulk operations UI: - -**Bulk Actions Toolbar**: -- Select All checkbox with count display -- Auto-Link Selected button (high confidence, ≥80%) -- Get Suggestions button (fetches matches for selected) -- Bulk Manual Link button (initiates manual linking workflow) - -**Per-Book Checkboxes**: -- Each unlinked book card now has a checkbox -- Checkboxes track `progress-id` and `title` for bulk operations -- Real-time count of selected books - -**JavaScript Functions**: -- `toggleAllUnlinked()` - Select/deselect all books -- `getSelectedUnlinked()` - Get selected books data -- `updateSelectedCount()` - Update count display -- `bulkAutoLink()` - Auto-link selected with confirmation -- `bulkGetSuggestions()` - Fetch and display suggestions -- `displaySuggestions()` - Render suggestions in UI -- `showBulkManualLink()` - Initiate manual linking - -### 4. Bruno API Tests ✅ - -Created three Bruno API test files: - -1. **`bruno/sync-kobo/Bulk Link Books.bru`** - - Tests bulk linking endpoint - - Includes multiple books in single request - - Verifies response structure - -2. **`bruno/sync-kobo/Auto Link Books.bru`** - - Tests auto-linking with confidence threshold - - Configurable limit and threshold - - Checks auto-linked count - -3. **`bruno/sync-kobo/Get Unlinked Book Suggestions.bru`** - - Tests suggestion retrieval - - Uses unlinked book ID parameter - - Validates suggestion structure - -### 5. Route Registration ✅ - -**File**: `cmd/server/main.go` - -Added protected routes: -```go -sync := protected.Group("/sync") -sync.POST("/bulk-link-books", h.BulkLinkBooks) -sync.POST("/auto-link-books", h.AutoLinkBooks) -sync.GET("/unlinked-books/:id/suggestions", h.GetUnlinkedBookSuggestions) -``` - -## Technical Implementation Details - -### Database Schema Compatibility - -The implementation works with the existing `unlinked_books` table: -- Uses `id`, `device_id`, `content_id`, `file_path`, `title` fields -- Links to `device_file_aliases` and `media_items` tables -- Maintains `resolved` flag and `resolution_method` - -**Note**: SHA-256 is not stored in `unlinked_books` table (not in original schema), so auto-linking relies on title matching primarily. - -### Error Handling - -Each bulk operation includes comprehensive error handling: - -1. **Bulk Link**: - - Validates each unlinked book exists - - Creates device file alias for each link - - Marks books as resolved - - Returns individual status per book - - Continues processing even if individual links fail - -2. **Auto-Link**: - - Fetches unlinked books with pagination - - Queries matching service for each - - Only auto-links if confidence ≥ threshold - - Skips books on errors (continues processing) - - Returns count of successful auto-links - -3. **Suggestions**: - - Validates unlinked book ID - - Queries matching service - - Returns all potential matches - - Includes confidence scores and match methods - -### Type Conversions - -Helper function added to `book_matching.go`: -```go -func toFloat8(f float64) pgtype.Float8 { - var result pgtype.Float8 - result.Scan(f) - return result -} -``` - -Ensures proper type conversion for pgx v5 `Float8` type. - -## API Usage Examples - -### Example 1: Bulk Link Multiple Books - -```bash -curl -X POST http://localhost:8765/api/sync/bulk-link-books \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "links": [ - { - "unlinked_book_id": "123e4567-e89b-12d3-a456-426614174000", - "media_item_id": "987fcdeb-51a2-f43c-8877-123456789abc", - "confidence_score": 1.0 - } - ] - }' -``` - -### Example 2: Auto-Link with High Confidence - -```bash -curl -X POST http://localhost:8765/api/sync/auto-link-books \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "confidence_threshold": 0.8, - "limit": 50 - }' -``` - -### Example 3: Get Suggestions - -```bash -curl -X GET http://localhost:8765/api/sync/unlinked-books/123e4567-e89b-12d3-a456-426614174000/suggestions \ - -H "Authorization: Bearer $TOKEN" -``` - -## Frontend Workflow - -### User Experience Flow - -1. **View Unlinked Books Page** (`/unlinked`) - - Lists all unresolved unlinked books - - Shows bulk actions toolbar at top - -2. **Select Books**: - - Click individual checkboxes OR - - Click "Select All" to select all books - - Selected count updates in real-time - -3. **Choose Action**: - - **Auto-Link**: One-click automatic linking (high confidence only) - - **Get Suggestions**: Fetches potential matches for each book - - **Bulk Manual Link**: Initiates manual selection workflow - -4. **Review Results**: - - Success/error status for each book - - Toast notifications for overall status - - Automatic page reload after successful bulk operations - -### Matching Priority (Auto-Link) - -The auto-link feature uses the existing book matching algorithm with priority: -1. Bookhoard UUID (canonical) - 1.0 confidence -2. OPF UUID - 0.95 confidence -3. SHA-256 hash - 0.9 confidence -4. OPF identifier - 0.85 confidence -5. ISBN/ASIN - 0.8 confidence -6. Title + author + file size - 0.5 confidence - -With default threshold of 0.8, only matches with 80%+ confidence are auto-linked. - -## Testing & Verification - -### Unit Tests -- Database query functions work correctly -- Type conversions are proper -- Error handling covers edge cases - -### Integration Testing (Bruno) -- Bulk link endpoint handles multiple books -- Auto-link respects confidence threshold -- Suggestions endpoint returns proper data - -### Manual Testing -1. Create unlinked book entries (via device sync or manual) -2. Navigate to `/unlinked` page -3. Select books using checkboxes -4. Test each bulk action: - - Auto-link with high confidence - - Get suggestions and review matches - - Manual link via suggestions - -### Build Verification -```bash -cd /home/nymusicman/Code/bookhoard -go build ./cmd/server # ✅ Successful -cd internal/database && sqlc generate # ✅ Successful -cd templates && templ generate # ✅ Successful -``` - -## Performance Considerations - -### Bulk Link -- **Complexity**: O(n) where n = number of books -- **Database**: N+1 queries (could be optimized in future) -- **Time**: ~50ms per book (includes alias creation + resolution) -- **Recommendation**: Limit to 50 books per request - -### Auto-Link -- **Complexity**: O(n*m) where n = books, m = matches checked -- **Database**: 1 query + n matching queries -- **Time**: ~100ms per book (includes matching service) -- **Optimization**: Pagination prevents loading all books at once - -### Get Suggestions -- **Complexity**: O(1) for single book -- **Database**: 1 query + 1 matching query -- **Time**: ~50-100ms -- **Caching**: Could be cached in future (TTL: 1 hour) - -## Security & Permissions - -All endpoints require: -- JWT authentication (user must be logged in) -- User can only link their own unlinked books -- Device ownership verified via `device_id` -- Media item access verified via library visibility - -No cross-user data access possible. - -## Future Enhancements - -Potential improvements: -1. **Optimized Bulk Link**: Batch database operations instead of N+1 queries -2. **Background Processing**: Auto-link large datasets asynchronously -3. **Confidence Learning**: Adjust thresholds based on user feedback -4. **Suggestions Caching**: Cache suggestions to reduce load -5. **Export/Import**: Export unlinked list for offline review -6. **Bulk Delete**: Delete multiple unlinked entries at once - -## Rollback Plan - -If issues arise: -1. Comment out route registrations in `main.go` -2. Remove bulk actions toolbar from template -3. Keep database queries (backward compatible) -4. No data migration needed (no schema changes) - -## Compliance with Project Guidelines - -✅ **No Backend for Frontend Tasks**: Full-stack feature with API + UI -✅ **pgx v5 Standards**: Uses generated queries with proper types -✅ **Multiple Logical Commits**: Can be split into 3 commits -✅ **Functional Programming**: Pure functions, no OOP patterns -✅ **TypeScript Only**: Frontend uses vanilla JS (can convert later) -✅ **KISS/DRY/YAGNI**: Minimal changes, reuses existing services -✅ **Bruno Tests**: All endpoints tested with `.bru` files -✅ **No Schema Changes**: Uses existing tables only - -## Deployment Checklist - -Before deploying to production: -- [ ] Test bulk operations with sample unlinked books -- [ ] Verify confidence thresholds work as expected -- [ ] Check that suggestions return relevant matches -- [ ] Test with 50+ unlinked books (performance) -- [ ] Verify error messages are user-friendly -- [ ] Test with multiple users (no cross-user data leakage) -- [ ] Monitor database performance during bulk operations -- [ ] Set up logging for bulk operations (audit trail) - -## Summary - -Phase 2 successfully adds bulk resolution capabilities to the unlinked books system: -- ✅ 3 new API endpoints for bulk operations -- ✅ Enhanced frontend with bulk actions UI -- ✅ Comprehensive error handling and validation -- ✅ Bruno API tests for all endpoints -- ✅ Backward compatible with existing code -- ✅ Ready for production use - -The implementation makes it significantly easier for users to resolve large numbers of unlinked books efficiently. diff --git a/docs/PHASE2_QUICK_SUMMARY.md b/docs/PHASE2_QUICK_SUMMARY.md deleted file mode 100644 index e56dcd4..0000000 --- a/docs/PHASE2_QUICK_SUMMARY.md +++ /dev/null @@ -1,170 +0,0 @@ -# Phase 2: Advanced Unlinked Book Resolution - COMPLETE ✅ - -## Summary - -Successfully implemented bulk resolution workflows for unlinked books with automated matching suggestions and user-friendly bulk operations. - -## Files Created (6) - -1. **`bruno/sync-kobo/Bulk Link Books.bru`** - Bruno test for bulk linking API -2. **`bruno/sync-kobo/Auto Link Books.bru`** - Bruno test for auto-linking API -3. **`bruno/sync-kobo/Get Unlinked Book Suggestions.bru`** - Bruno test for suggestions API -4. **`docs/PHASE2_COMPLETION_SUMMARY.md`** - Comprehensive documentation - -## Files Modified (7) - -1. **`internal/database/queries/queries.sql`** - - Added `GetUnlinkedBookByID` query - - Added `DeleteUnlinkedBook` query - - Added `ListUnresolvedUnlinkedBooks` query - -2. **`internal/handlers/book_matching.go`** - - Added `BulkLinkBooks()` handler - - Added `AutoLinkBooks()` handler - - Added `GetUnlinkedBookSuggestions()` handler - - Added `toFloat8()` helper function - - Added `BulkLinkBooksRequest` and `AutoLinkBooksRequest` types - -3. **`cmd/server/main.go`** - - Added bulk resolution routes under `/sync` group - -4. **`templates/unlinked_books.templ`** - - Added bulk actions toolbar with Select All - - Added checkboxes to each book card - - Added JavaScript functions for bulk operations - - Enhanced UI with selected count display - -5. **`internal/database/queries.sql.go`** (auto-generated) - - Regenerated with new queries - -6. **`internal/database/querier.go`** (auto-generated) - - Updated interface with new methods - -7. **`templates/unlinked_books_templ.go`** (auto-generated) - - Regenerated template Go code - -## New API Endpoints (3) - -### 1. POST `/api/sync/bulk-link-books` -Bulk link multiple unlinked books to media items. - -**Features**: -- Links multiple books in single request -- Creates device file aliases -- Marks books as resolved -- Returns individual status per book -- Continues on errors (partial success) - -### 2. POST `/api/sync/auto-link-books` -Automatically link unlinked books using matching algorithm. - -**Features**: -- Configurable confidence threshold (default 0.8) -- Paginated processing (default 50 books) -- Uses existing book matching service -- Only links high-confidence matches -- Returns count and details - -### 3. GET `/api/sync/unlinked-books/:id/suggestions` -Get matching suggestions for a specific unlinked book. - -**Features**: -- Returns all potential matches -- Includes confidence scores -- Shows match methods -- Enables informed manual linking - -## Frontend Enhancements - -### Bulk Actions Toolbar -- **Select All** checkbox with real-time count -- **Auto-Link Selected** - One-click high-confidence linking -- **Get Suggestions** - Fetch matches for selected books -- **Bulk Manual Link** - Initiate manual workflow - -### Per-Book Checkboxes -- Individual selection control -- Tracks progress ID and title -- Updates selected count dynamically - -### JavaScript Functions -- `toggleAllUnlinked()` - Select/deselect all -- `bulkAutoLink()` - Auto-link with confirmation -- `bulkGetSuggestions()` - Fetch and display matches -- `displaySuggestions()` - Render suggestions in UI -- `updateSelectedCount()` - Update count display - -## Database Queries Added - -```sql --- Get unlinked book by ID -GetUnlinkedBookByID(ctx, id) -> UnlinkedBooks - --- Delete unlinked book -DeleteUnlinkedBook(ctx, id) -> exec - --- List unresolved unlinked books -ListUnresolvedUnlinkedBooks(ctx, {limit, offset}) -> []UnlinkedBooksRow -``` - -## Key Features - -✅ **Bulk Linking** - Link multiple books in one API call -✅ **Auto-Linking** - Automatic high-confidence matching -✅ **Suggestions API** - Get potential matches for manual review -✅ **Error Resilience** - Continues processing on individual failures -✅ **User-Friendly UI** - Checkboxes, select all, real-time count -✅ **Comprehensive Testing** - Bruno tests for all endpoints -✅ **Backward Compatible** - No schema changes, uses existing tables - -## Testing & Verification - -### Build Status -```bash -✅ go build ./cmd/server - Successful -✅ sqlc generate - Successful -✅ templ generate - Successful -``` - -### Manual Testing Checklist -- [ ] View unlinked books page -- [ ] Select individual books -- [ ] Use "Select All" checkbox -- [ ] Test auto-link with high confidence -- [ ] Get suggestions for selected books -- [ ] Verify suggestions display correctly -- [ ] Test bulk manual link workflow -- [ ] Verify error handling for invalid IDs - -### API Testing -Use Bruno tests in `bruno/sync-kobo/`: -- Bulk Link Books.bru -- Auto Link Books.bru -- Get Unlinked Book Suggestions.bru - -## Performance - -| Operation | Time | Complexity | Notes | -|-----------|------|------------|-------| -| Bulk Link (50 books) | ~2.5s | O(n) | ~50ms per book | -| Auto-Link (50 books) | ~5s | O(n*m) | Includes matching | -| Get Suggestions | ~100ms | O(1) | Single book | - -## Security - -- ✅ All endpoints require JWT authentication -- ✅ User can only access their own unlinked books -- ✅ Device ownership verified -- ✅ No cross-user data access - -## Next Steps - -Phase 2 is complete and ready for: -1. ✅ Manual testing with real unlinked books -2. ✅ Integration testing with device sync -3. ✅ Deployment to staging environment -4. Ready for Phase 3: Conflict Resolution UI & API - -## Summary - -Phase 2 successfully adds **bulk resolution capabilities** to the unlinked books system, making it significantly easier for users to resolve large numbers of unlinked books efficiently. The implementation includes three new API endpoints, enhanced frontend with bulk operations UI, comprehensive error handling, and full test coverage. diff --git a/docs/PROGRESS_ROUTES_ANALYSIS.md b/docs/PROGRESS_ROUTES_ANALYSIS.md deleted file mode 100644 index 6b5c7bd..0000000 --- a/docs/PROGRESS_ROUTES_ANALYSIS.md +++ /dev/null @@ -1,423 +0,0 @@ -# Progress Routes Analysis & Thoughts - -## Overview - -This document explores the current state of progress tracking in Bookhoard, the migration from legacy media-item-specific routes to universal cross-device progress, and considerations for the future. - ---- - -## Current State - -### Legacy Routes (Marked as Deprecated) - -Located in `internal/handlers/ebook.go:114-117`: - -```go -// Legacy progress routes (deprecated - use universal progress instead) -g.GET("/api/media-items/:id/progress", h.GetMediaReadingProgress) -g.PUT("/api/media-items/:id/progress", h.UpdateMediaReadingProgress) -g.DELETE("/api/media-items/:id/progress", h.DeleteMediaReadingProgress) -``` - -**Purpose**: These routes handle progress tracking for a specific media item from the `media_items` table. - -**Data Source**: Likely queries the `reading_progress` table filtered by `media_item_id`. - -**Current Status**: Explicitly marked as "legacy" and "deprecated" in code comments. - ---- - -### Universal Progress Routes (Phase 1 Implementation) - -Located in `internal/handlers/ebook.go:119-122`: - -```go -// Universal Progress routes (Phase 1) -g.GET("/api/progress/:id", h.GetUniversalProgress) -g.POST("/api/progress/:id", h.UpdateUniversalProgress) -g.GET("/api/progress/:id/history", h.GetProgressHistory) -``` - -**Purpose**: These routes provide "universal" progress tracking that works across devices and media types. - -**Data Source**: Uses enhanced `reading_progress` table with additional fields: -- `percentage` - Universal percentage (0-1) -- `character_offset` - Character-based positioning -- `epubcfi` - EPUB Canonical Fragment Identifier -- `chapter` + `chapter_progress` - Chapter-based tracking -- Viewport coordinates (viewport_x, viewport_y, zoom_level) -- Scroll positions (scroll_position_x, scroll_position_y) -- Panel number for comics/manga -- Reading mode indicator - -**Device Sync Metadata**: -- `last_sync_device` - Which device last updated -- `last_sync_source` - Source type (koreader, kobo, web, etc.) -- `last_sync_timestamp` - When sync occurred -- `conflict_detected` - Boolean flag for conflicts -- `conflict_resolved` - Boolean flag for resolution status - ---- - -## Why the Migration Happened - -### 1. **Cross-Platform Kindle Ecosystem Vision** - -Bookhoard aims to replace the Kindle ecosystem, which requires: -- Syncing progress across multiple devices (Kindle, Kobo, phone, web) -- Handling different progress formats (page numbers, percentages, CFI, character offsets) -- Maintaining reading state across different device types -- Supporting offline reading with sync queues - -### 2. **Format Diversity** - -Different e-readers and formats use different progress indicators: - -| Format/Device | Progress Type | Example | -|---------------|---------------|---------| -| EPUB (KOReader) | EPUBCFI | `epubcfi(/6/4[chap1ref]!/4/2/1:0)` | -| EPUB (Kobo) | Page # + Total | `page 234 of 456` | -| PDF | Page # | `page 45` | -| Web Reader | Percentage | `0.45 (45%)` | -| TXT/Mobi | Character Offset | `offset 12345` | -| Comics/Manga | Panel # | `panel 7` | -| Kindle | Location # | `location 1234` | - -The legacy `media-items/:id/progress` routes couldn't handle this diversity. - -### 3. **Device Sync Architecture** - -Universal progress enables: -- Real-time sync via WebSocket (`/ws/sync`) -- Offline queue support (`/api/queue/*`) -- Conflict detection and resolution -- Checkpoint mode for battery optimization -- Progress history tracking - ---- - -## Current Database Schema - -From `database/schema/schema.sql:130-158`: - -```sql -CREATE TABLE reading_progress ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - - -- Legacy fields - current_page INTEGER DEFAULT 0, - total_pages INTEGER, - last_read_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - - -- Universal Progress Tracking (Phase 1) - percentage FLOAT CHECK (percentage >= 0 AND percentage <= 1), - character_offset BIGINT, - epubcfi TEXT, - chapter INTEGER, - chapter_progress FLOAT CHECK (chapter_progress >= 0 AND chapter_progress <= 1), - viewport_x FLOAT DEFAULT 0, - viewport_y FLOAT DEFAULT 0, - zoom_level FLOAT DEFAULT 1.0, - scroll_position_x FLOAT DEFAULT 0, - scroll_position_y FLOAT DEFAULT 0, - panel_number INTEGER, - reading_mode VARCHAR(20), - - -- Device Sync Metadata (Phase 1) - last_sync_device VARCHAR(50), - last_sync_source VARCHAR(20), - last_sync_timestamp TIMESTAMP WITH TIME ZONE, - conflict_detected BOOLEAN DEFAULT FALSE, - conflict_resolved BOOLEAN DEFAULT TRUE, - - UNIQUE(media_item_id, user_id) -); -``` - -**Backward Compatibility View** (Line 160-168): - -```sql -CREATE VIEW ebook_reading_progress AS -SELECT rp.*, - mi.id as ebook_id -- Map media_item_id to ebook_id for compatibility -FROM reading_progress rp -JOIN media_items mi ON rp.media_item_id = mi.id -JOIN libraries l ON mi.library_id = l.id -JOIN library_types lt ON l.library_type_id = lt.id -WHERE lt.name = 'ebooks'; -``` - ---- - -## The Migration Challenge - -### Issue: Two Parallel Systems - -Currently, **both** systems exist side-by-side: - -1. **Legacy routes** (`/api/media-items/:id/progress`) - - Likely use simple `current_page` / `total_pages` fields - - Media-item scoped - - No device sync metadata - -2. **Universal routes** (`/api/progress/:id`) - - Use rich progress tracking with multiple formats - - Device-aware - - Include sync metadata - -### Question: What Does `:id` Mean? - -**Legacy**: `:id` = `media_item_id` (UUID of the book) - -**Universal**: `:id` = ??? (Could be same media_item_id, or could be a different identifier) - -**Ambiguity**: The routes use the same parameter name but might mean different things. - -### Potential Problems - -1. **Data Duplication**: If both systems write to `reading_progress` table, they might overwrite each other -2. **Client Confusion**: Which endpoint should clients use? -3. **Migration Path**: How do existing clients using legacy endpoints transition? -4. **API Consistency**: Having two different endpoints for similar functionality is confusing - ---- - -## Observations & Concerns - -### 1. **Incomplete Migration** - -The legacy routes are marked as deprecated but **still active**. This suggests: -- Migration is ongoing, not complete -- Some clients might still depend on legacy routes -- Fear of breaking existing integrations - -### 2. **Backward Compatibility View** - -The `ebook_reading_progress` view exists to maintain compatibility with the old `ebooks` table. This adds: -- Query overhead (JOINs to filter by library type) -- Developer confusion (which table/view to query?) -- Technical debt (maintaining two ways to access data) - -### 3. **Route Naming Inconsistency** - -- Legacy: `/api/media-items/:id/progress` (RESTful, nested under media-item) -- Universal: `/api/progress/:id` (flat structure, not nested) - -**Question**: Should universal progress be under `/api/media-items/:id/universal-progress` for consistency? - -### 4. **HTTP Method Mismatch** - -Legacy routes use: -- `PUT /api/media-items/:id/progress` (update progress) - -Universal routes use: -- `POST /api/progress/:id` (update progress) - -**REST convention**: `PUT` is idempotent, `POST` is not. For progress updates, `PUT` might be more appropriate since setting the same progress twice should have the same effect. - -### 5. **Missing Delete Operation** - -Universal routes don't have a `DELETE /api/progress/:id` endpoint. Legacy does: -- `DELETE /api/media-items/:id/progress` (clear progress) - -**Question**: Should there be a way to reset progress via universal routes? - ---- - -## Potential Future Directions - -### Option 1: Full Migration (Clean Break) - -**Action**: Remove all legacy routes and views. - -**Steps**: -1. Deprecate legacy routes in API documentation (return `Warning` header) -2. Add a 6-month migration timeline -3. Remove `/api/media-items/:id/progress` routes -4. Drop `ebook_reading_progress` view -5. Update all clients to use universal routes - -**Pros**: -- Cleaner API surface -- Single source of truth -- Less maintenance burden -- Clearer documentation - -**Cons**: -- Breaking change for existing clients -- Mobile apps might need updates -- External integrations could break - -### Option 2: Compatibility Layer (Adapter Pattern) - -**Action**: Keep legacy routes but make them thin wrappers around universal routes. - -**Implementation**: -```go -// Legacy route calls universal route internally -func (h *Handler) GetMediaReadingProgress(c echo.Context) error { - mediaItemID := c.Param("id") - // Extract user_id from JWT - // Call h.GetUniversalProgress with same IDs - // Transform response if needed -} -``` - -**Pros**: -- No breaking changes -- Gradual migration path -- Single implementation (universal routes) - -**Cons**: -- Maintains API surface area -- Slight performance overhead (function call) -- Still confusing to have two endpoints - -### Option 3: Unified Endpoint (Best of Both) - -**Action**: Create a single endpoint that handles both use cases. - -**Proposed**: -``` -GET /api/media-items/:id/progress?format=universal -PUT /api/media-items/:id/progress?format=universal -DELETE /api/media-items/:id/progress -``` - -The `format` query parameter determines: -- `format=simple` (default): Returns basic page/percentage (legacy behavior) -- `format=universal`: Returns full device-aware progress with metadata - -**Pros**: -- Single endpoint -- Backward compatible -- Clear migration path via query parameter -- RESTful structure (nested under media-items) - -**Cons**: -- More complex handler logic -- Need to maintain both formats in response - -### Option 4: Versioned API (Cleanest Long-Term) - -**Action**: Use API versioning to separate old and new. - -**Proposed**: -``` -# v1 (Legacy) -GET /api/v1/media-items/:id/progress -PUT /api/v1/media-items/:id/progress -DELETE /api/v1/media-items/:id/progress - -# v2 (Universal) -GET /api/v2/media-items/:id/progress -PUT /api/v2/media-items/:id/progress -GET /api/v2/media-items/:id/progress/history -``` - -**Pros**: -- Clean separation -- Can deprecate v1 independently -- Standard industry practice -- Clear migration documentation - -**Cons**: -- Need to implement version routing -- More upfront work -- Maintenance of two versions temporarily - ---- - -## Unanswered Questions for Discussion - -1. **Are any clients currently using the legacy progress routes?** - - If yes, which ones? (mobile app, web app, third-party integrations?) - - Can they be updated easily? - -2. **What does the `:id` parameter represent in universal progress routes?** - - Is it still `media_item_id`? - - Or is it a `reading_progress` record ID? - - Need to check implementation to confirm - -3. **Why was `/api/progress/:id` chosen instead of `/api/media-items/:id/universal-progress`?** - - Flat structure vs nested structure design decision - - Might indicate plans for progress to exist independently of media items? - -4. **Is the legacy route implementation actually different, or just deprecated?** - - Need to read the handler implementations to compare - - They might be calling the same underlying code - -5. **Should we maintain progress deletion functionality?** - - Universal routes don't have DELETE - - Is deleting progress a necessary feature? - -6. **What's the timeline for removing legacy routes?** - - Already marked deprecated, but when can we delete them? - - Need to coordinate with mobile app releases - -7. **How does the backward compatibility view affect performance?** - - The `ebook_reading_progress` view requires JOINs - - Is it used anywhere, or can it be dropped? - ---- - -## Recommendations - -### Immediate Actions (Discussion Phase) - -1. **Audit Current Usage** - - Search codebase for references to legacy routes - - Check if any external documentation mentions these endpoints - - Identify all clients (web, mobile, third-party) - -2. **Compare Implementations** - - Read handler code for both legacy and universal routes - - Document differences in behavior - - Determine if they're truly different or just deprecated wrappers - -3. **Clarify API Contract** - - Define what `:id` means in universal routes - - Document expected request/response formats - - Add examples for different device types - -4. **Performance Analysis** - - Query database to see how many records use legacy fields vs universal - - Check if backward compatibility view is actually used - - Benchmark query performance with/without views - -### Future Considerations - -1. **Choose a Migration Strategy** - - Review Options 1-4 above - - Consider breaking changes vs compatibility - - Plan timeline based on client usage - -2. **API Versioning Decision** - - Decide if we want `/api/v1/` and `/api/v2/` structure - - Or use different approach (headers, content negotiation) - -3. **Documentation Updates** - - Update API_REFERENCE.md with clear deprecation notices - - Add migration guide for clients - - Document best practices for progress tracking - -4. **Test Coverage** - - Ensure both legacy and universal routes have comprehensive tests - - Add integration tests for cross-device sync scenarios - - Test conflict resolution workflows - ---- - -## Next Steps for Discussion - -1. **Review handler implementations** to understand actual differences -2. **Check client usage** (web app, mobile apps, Bruno tests) -3. **Decide on migration timeline** and breaking change tolerance -4. **Choose unified strategy** (Options 1-4 or hybrid) -5. **Plan implementation** with backward compatibility in mind - ---- - -*Document created for future discussion. No changes to be made without review.* diff --git a/docs/SECURITY_AUDIT.md b/docs/SECURITY_AUDIT.md deleted file mode 100644 index 8127cde..0000000 --- a/docs/SECURITY_AUDIT.md +++ /dev/null @@ -1,518 +0,0 @@ -# Bookhoard Security Audit Report -## Universal Sync Implementation (Phases 1-7) - -**Date**: January 31, 2026 -**Version**: 1.0.0 -**Auditor**: Bookhoard Security Team - ---- - -## Executive Summary - -This security audit covers the Universal Cross-Platform Sync implementation, including device authentication, wireless sync protocols, queue management, and offline recovery mechanisms. - -### Overall Security Rating: **A- (Recommended for Production with Minor Enhancements)** - ---- - -## 1. Authentication & Authorization - -### 1.1 Device Registration Flow ✅ SECURE - -**Implementation**: `internal/handlers/devices.go` - -**Flow**: -``` -1. Device generates unique identifier (hardware ID) -2. Device POST /api/devices/register/initiate -3. Server creates pending registration (5 min expiry) -4. User visits auth URL in web browser -5. User logs in and approves device -6. Server generates device-specific JWT token -7. Device polls for token approval -8. Device receives token and begins syncing -``` - -**Security Strengths**: -- ✅ No API keys on devices (prevents credential exposure) -- ✅ User approval required via web interface -- ✅ Short-lived registration sessions (5 minutes) -- ✅ Device-specific JWT tokens with limited permissions -- ✅ Token revocation support - -**Recommendations**: -- ⚠️ Add rate limiting on registration endpoint (10 req/min per IP) -- ⚠️ Implement device cap per user (max 10 devices) -- ⚠️ Add notification when new device registered - -### 1.2 Device Authentication Middleware ✅ SECURE - -**Implementation**: `internal/middleware/device_auth.go` - -**Security Features**: -- ✅ Bearer token validation on every request -- ✅ Device ownership verification -- ✅ Token expiry checking -- ✅ Permission validation per endpoint -- ✅ Device revocation support - -**Code Review**: -```go -// Validates device token and ownership -func (m *DeviceAuthMiddleware) Authenticate(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - device, err := m.validateToken(token) - if err != nil || !device.SyncEnabled.Bool { - return ErrUnauthorized - } - c.Set("device", device) - return next(c) - } -} -``` - -### 1.3 User JWT Authentication ✅ SECURE - -**Implementation**: Existing user authentication system - -**Security Features**: -- ✅ bcrypt password hashing (cost 10) -- ✅ JWT with short expiry (15 minutes) -- ✅ Refresh token rotation -- ✅ Secure password complexity requirements -- ✅ Login attempt rate limiting (5 attempts / 15 min lockout) - ---- - -## 2. Wireless Sync Protocols - -### 2.1 KOReader Sync Protocol ✅ SECURE - -**Implementation**: `internal/handlers/koreader.go` - -**Endpoints**: -``` -POST /api/sync/koreader/progress -GET /api/sync/koreader/metadata/:uuid -POST /api/sync/koreader/bookmarks -``` - -**Security Analysis**: -- ✅ Requires device authentication -- ✅ Input validation on all fields -- ✅ Media item ownership verification -- ✅ SQL injection protection (parameterized queries) -- ✅ No arbitrary file access - -**Potential Issues**: -- ⚠️ Large sync payloads could cause DoS (add size limits) -- ⚠️ No request signing (add HMAC for integrity) - -**Recommendations**: -```go -// Add payload size limit -const MaxSyncPayloadSize = 10 * 1024 * 1024 // 10MB - -func validatePayloadSize(r *http.Request) error { - r.Body = http.MaxBytesReader(nil, r.Body, MaxSyncPayloadSize) - return nil -} -``` - -### 2.2 Kobo Sync Protocol ✅ SECURE - -**Implementation**: `internal/handlers/kobo.go` - -**Security Features**: -- ✅ Device authentication required -- ✅ x-kobo-device header validation -- ✅ Content-Type validation -- ✅ Input sanitization - ---- - -## 3. Data Protection - -### 3.1 Sensitive Data Storage ✅ SECURE - -**Password Storage**: -- ✅ bcrypt with cost factor 10 -- ✅ No plaintext storage -- ✅ No password logging - -**Device Tokens**: -- ✅ Unique per device -- ✅ Cryptographically random (UUID v4) -- ✅ Revocable -- ⚠️ Stored in plaintext (consider encryption at rest) - -**Sync Data**: -- ✅ JSONB stored in PostgreSQL -- ✅ No SQL injection vectors -- ✅ Media item ownership verification - -### 3.2 Data Transmission ✅ SECURE - -**HTTPS Enforcement**: -```go -// Recommended: Force HTTPS in production -if !cfg.TestMode { - e.Pre(echomiddleware.HTTPSRedirect()) -} -``` - -**WebSocket Security**: -- ✅ Token validation on connection -- ✅ Origin checking -- ✅ Automatic disconnection on token expiry - ---- - -## 4. Rate Limiting & DoS Prevention - -### 4.1 Current Implementation ⚠️ NEEDS ENHANCEMENT - -**Existing**: `internal/middleware/rate_limiter.go` - -**Per-Endpoint Limits**: -``` -General: 100 req/min (configurable) -Auth: 10 req/min -``` - -**Sync-Specific Limits Needed**: -```go -const ( - SyncProgressRateLimit = 120 / time.Minute // Page turns - SyncMetadataRateLimit = 30 / time.Minute // Metadata fetches - SyncBookmarkRateLimit = 60 / time.Minute // Bookmarks/notes - DeviceRegistrationLimit = 10 / time.Minute // Device registrations -) -``` - -### 4.2 Resource Limits - -**Queue Processing**: -- ✅ Batch size limit (50 items) -- ✅ Concurrent worker limit (1 per instance) -- ⚠️ Add per-device queue size limit (100 items max) - -**Database Connections**: -- ✅ Connection pooling (pgxpool) -- ✅ Max connections: 200 -- ✅ Automatic connection reuse - ---- - -## 5. Input Validation - -### 5.1 Sync Data Validation ✅ SECURE - -**Progress Updates**: -```go -type ProgressUpdate struct { - Percentage float64 `validate:"gte=0,lte=1"` - Page *int `validate:"gte=0"` - TotalPages *int `validate:"gte=0,lte=10000"` -} -``` - -**Device Registration**: -```go -type DeviceRegistration struct { - DeviceName string `validate:"required,min=1,max=100"` - DeviceType string `validate:"required,oneof=koreader kobo web mobile"` -} -``` - -**Strengths**: -- ✅ Struct validation using go-playground/validator -- ✅ Type safety via pgx -- ✅ Length constraints -- ✅ Enum validation - ---- - -## 6. SQL Injection Prevention - -### 6.1 Parameterized Queries ✅ SECURE - -**All queries use sqlc-generated code**: -```go -// Generated code uses parameterized queries -func (q *Queries) CreateSyncQueueItem(ctx context.Context, arg CreateSyncQueueItemParams) (SyncQueue, error) { - row := q.db.QueryRow(ctx, CreateSyncQueueItem, - arg.DeviceID, // $1 - Parameterized - arg.MediaItemID, // $2 - Parameterized - arg.SyncType, // $3 - Parameterized - // ... all parameters are safely bound - ) -} -``` - -**No dynamic SQL construction** ✅ - ---- - -## 7. Cross-Site Request Forgery (CSRF) - -### 7.1 State-Changing Operations - -**JWT Authentication**: CSRF protected via JWT -- ✅ All state-changing ops require valid JWT -- ✅ Token stored in memory/secure storage -- ✅ SameSite cookie attribute (when applicable) - -**Device Authentication**: CSRF not applicable -- ✅ Devices use Bearer tokens (no cookies) -- ✅ Origin validation for WebSocket - -**Recommendation**: Add CSRF double-submit tokens for web interface - ---- - -## 8. Authorization Checks - -### 8.1 Media Item Ownership ✅ SECURE - -```go -func (h *Handler) validateOwnership(userID, mediaItemID uuid.UUID) error { - item, err := h.db.GetMediaItem(ctx, mediaItemID) - if err != nil { - return ErrNotFound - } - - library, err := h.db.GetLibrary(ctx, item.LibraryID) - if err != nil { - return ErrNotFound - } - - // Check user has access to library - visible, err := h.db.GetLibraryVisibility(ctx, userID, library.ID) - if !visible.IsVisible { - return ErrForbidden - } - - return nil -} -``` - -**All endpoints verify ownership** ✅ - ---- - -## 9. Error Handling & Information Disclosure - -### 9.1 Error Messages ✅ SECURE - -**Good Examples**: -``` -"Media item not found" // Generic -"Invalid request format" // No details -"Authentication required" // Clear but generic -``` - -**Avoid Information Leakage**: -``` -❌ "User with ID 123 does not exist" -❌ "Password incorrect for user@example.com" -✅ "Invalid credentials" -``` - ---- - -## 10. Cryptographic Practices - -### 10.1 Random Number Generation ✅ SECURE - -```go -// Using crypto/rand (via UUID v4) -deviceID := uuid.New() // Uses crypto/rand -authToken := "device-token-" + uuid.New().String() -``` - -### 10.2 Token Generation ✅ SECURE - -```go -// JWT signing with HS256 -token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) -tokenString, err := token.SignedString([]byte(secret)) -``` - -**Recommendation**: Consider RS256 for production (asymmetric keys) - ---- - -## 11. Dependency Security - -### 11.1 Key Dependencies - -``` -github.com/jackc/pgx/v5 v5.5.0 ✅ Latest stable -github.com/golang-jwt/jwt/v5 v5.2.0 ✅ Latest stable -github.com/labstack/echo/v4 v4.12.0 ✅ Latest stable -golang.org/x/crypto v0.18.0 ✅ Latest stable -``` - -**All dependencies up-to-date** ✅ - ---- - -## 12. Recommended Security Enhancements - -### Priority 1 (Implement Before Production) - -1. **Add Request Signing** ⚠️ HIGH PRIORITY - ```go - // Add HMAC signature to sync requests - signature = HMAC-SHA256(deviceToken, requestBody + timestamp) - ``` - -2. **Increase Rate Limiting** ⚠️ HIGH PRIORITY - ```go - // Per-device rate limits - DeviceRateLimit = 60 req/min - // Per-user rate limits - UserSyncRateLimit = 300 req/min - ``` - -3. **Add Request Size Limits** ⚠️ HIGH PRIORITY - ```go - MaxSyncPayload = 10MB - MaxAnnotationSize = 100KB - ``` - -### Priority 2 (Implement Soon) - -4. **HTTPS Enforcement** 📡 MEDIUM PRIORITY - ```go - e.Pre(echomiddleware.HTTPSRedirect()) - e.Pre(middleware.SecureWithConfig(middleware.SecureConfig{ - XSSProtection: "1; mode=block", - ContentTypeNosniff: "1", - XFrameOptions: "DENY", - })) - ``` - -5. **Device Cap** 📱 MEDIUM PRIORITY - ```go - MaxDevicesPerUser = 10 - ``` - -6. **Security Headers** 🔒 MEDIUM PRIORITY - ```go - // Add to all responses - X-Content-Type-Options: nosniff - X-Frame-Options: DENY - X-XSS-Protection: 1; mode=block - Strict-Transport-Security: max-age=31536000 - ``` - -### Priority 3 (Future Enhancements) - -7. **Audit Logging** 📊 LOW PRIORITY - ```go - type AuditLog struct { - Timestamp time.Time - UserID uuid.UUID - DeviceID uuid.UUID - Action string - ResourceType string - ResourceID uuid.UUID - IPAddress string - UserAgent string - } - ``` - -8. **API Key Rotation** 🔑 LOW PRIORITY - ```go - // Auto-rotate device tokens every 90 days - TokenRotationPeriod = 90 * 24 * time.Hour - ``` - -9. **WebAuthn for Device Registration** 🔐 LOW PRIORITY - ```go - // Use WebAuthn instead of password login for device approval - ``` - ---- - -## 13. Testing & Validation - -### 13.1 Security Test Coverage - -**Existing Tests**: -- ✅ Device authentication flow -- ✅ User authentication -- ✅ Authorization checks -- ✅ Input validation - -**Recommended Security Tests**: -```go -func TestSQLInjectionPrevention(t *testing.T) -func TestAuthenticationBypass(t *testing.T) -func TestRateLimitEnforcement(t *testing.T) -func TestCSRFProtection(t *testing.T) -func TestPrivilegeEscalation(t *testing.T) -func TestDoSProtection(t *testing.T) -``` - ---- - -## 14. Compliance Considerations - -### 14.1 Data Privacy - -**GDPR Compliance**: -- ✅ User data export capability -- ✅ Right to deletion (DELETE /api/users/:id) -- ✅ Data minimization -- ⚠️ Need privacy policy update for sync features - -**Data Retention**: -``` -Sync Queue: 30 days -Reading History: 365 days -Conflict Logs: 90 days -Audit Logs: 180 days -``` - -### 14.2 SOC 2 Considerations - -- ✅ Access control (user + device authentication) -- ✅ Change logging (reading_progress, sync_conflicts) -- ⚠️ Need incident response plan -- ⚠️ Need security monitoring/alerting - ---- - -## 15. Conclusion - -### Security Scorecard - -| Category | Score | Status | -|----------|-------|--------| -| Authentication | 9/10 | ✅ Excellent | -| Authorization | 10/10 | ✅ Excellent | -| Input Validation | 9/10 | ✅ Excellent | -| Data Protection | 8/10 | ✅ Good | -| Rate Limiting | 6/10 | ⚠️ Needs Enhancement | -| Error Handling | 9/10 | ✅ Excellent | -| Cryptography | 8/10 | ✅ Good | -| Dependency Security | 10/10 | ✅ Excellent | - -**Overall: 8.6/10 (A-)** - -### Production Readiness: ✅ APPROVED - -**With Conditions**: -1. Implement Priority 1 enhancements before production -2. Add monitoring for security events -3. Document incident response procedures -4. Perform penetration testing before public release - ---- - -**Audit Completed By**: Bookhoard Security Team -**Next Audit**: Within 3 months of production deployment -**Questions**: security@bookhoard.example.com diff --git a/docs/SECURITY_ENHANCEMENTS.md b/docs/SECURITY_ENHANCEMENTS.md deleted file mode 100644 index 01d240a..0000000 --- a/docs/SECURITY_ENHANCEMENTS.md +++ /dev/null @@ -1,597 +0,0 @@ -# Security Enhancements Implementation Report -## Priority 1 Security Features - COMPLETED - -**Date**: January 31, 2026 -**Version**: 1.0.1 -**Implemented By**: Bookhoard Security Team - ---- - -## Executive Summary - -All **Priority 1** security recommendations from the security audit have been successfully implemented, bringing Bookhoard's security rating from **A- (8.6/10)** to **A+ (9.2/10)**. - -### Security Scorecard Update - -| Category | Before | After | Improvement | -|----------|--------|-------|-------------| -| Authentication | 9/10 | 9.5/10 | +0.5 | -| Authorization | 10/10 | 10/10 | ✓ Maintained | -| Input Validation | 9/10 | 9.5/10 | +0.5 | -| Data Protection | 8/10 | 9/10 | +1.0 | -| Rate Limiting | 6/10 | 9/10 | +3.0 | -| Error Handling | 9/10 | 9/10 | ✓ Maintained | -| Cryptography | 8/10 | 9/10 | +1.0 | -| Dependency Security | 10/10 | 10/10 | ✓ Maintained | - -**Overall Score**: **9.2/10 (A+)** - **Production Ready with No Conditions** - ---- - -## Implemented Enhancements - -### 1. ✅ HMAC Request Signing - -**File**: `internal/middleware/request_signing.go` (240 lines) - -**What Was Implemented**: -- HMAC-SHA256 signature validation for all sync requests -- Timestamp-based replay attack prevention (5-minute window) -- Clock skew detection (±1 minute tolerance) -- Request ID tracing for audit trails -- Device-specific secret keys - -**Security Benefits**: -- ✅ **Request Integrity**: Ensures requests aren't tampered with in transit -- ✅ **Replay Prevention**: Timestamps prevent old requests from being replayed -- �**Audit Trail**: Request IDs enable security monitoring -- �**Tamper Detection**: Any modification invalidates signature - -**How It Works**: -```go -// Client signs request -signingString = requestID + "|" + timestamp + "|" + requestBody -signature = HMAC-SHA256(signingString, deviceSecret) - -// Server validates -expectedSig = HMAC-SHA256(requestID + timestamp + body, deviceSecret) -if !hmac.Equal(signature, expectedSig) { - return "Invalid signature" -} -``` - -**Headers Required**: -``` -X-Request-ID: unique-uuid-v4 -X-Timestamp: Unix timestamp (seconds) -X-Signature: hex-encoded HMAC-SHA256 -``` - -**Configuration**: -```go -type RequestSigningConfig struct { - Enabled: true - TimestampHeader: "X-Timestamp" - SignatureHeader: "X-Signature" - TimestampTolerance: 5 minutes - MaxClockSkew: 1 minute -} -``` - ---- - -### 2. ✅ Request Size Limits - -**File**: `internal/middleware/request_size_limits.go` (150+ lines) - -**What Was Implemented**: -- Payload size validation for all endpoints -- Per-endpoint size limits: - - Sync payloads: 10MB max - - Annotations: 100KB max - - Metadata: 1MB max - - Image uploads: 50MB max -- Real-time size monitoring and logging - -**Security Benefits**: -- ✅ **DoS Prevention**: Prevents memory exhaustion attacks -- ✅ **Resource Protection: Limits server memory usage -- ✅**Abuse Prevention**: Blocks large payload attacks - -**Implementation Details**: -```go -const ( - MaxSyncPayload = 10 * 1024 * 1024 // 10MB - MaxAnnotationSize = 100 * 1024 // 100KB - MaxMetadataSize = 1 * 1024 * 1024 // 1MB - MaxImageUploadSize = 50 * 1024 * 1024 // 50MB -) - -// Applied automatically -c.Request().Body = http.MaxBytesReader(nil, c.Request().Body, limit) -``` - -**Smart Limiting**: -```go -sync endpoints → 10MB limit -annotation endpoints → 100KB limit -metadata endpoints → 1MB limit -upload endpoints → 50MB limit -``` - ---- - -### 3. ✅ Enhanced Rate Limiting - -**File**: `internal/middleware/sync_rate_limiter.go` (160+ lines) - -**What Was Implemented**: -- Per-device rate limiting (60 req/min for sync) -- Per-user combined rate limiting (300 req/min total) -- Global server rate limiting (600 req/min) -- Automatic cleanup of stale limiters -- Memory-efficient implementation - -**Security Benefits**: -- ✅ **DoS Prevention**: Blocks abusive request patterns -- ✅ **Fair Resource Allocation**: Prevents one device from monopolizing resources -- ✅ **Scalability**: Ensures server stability under load -- ✅**Abuse Detection**: Identifies problematic devices - -**Rate Limits Applied**: -```go -const ( - DeviceSyncRatePerSec = 2 // 120 req/min - DeviceMetadataRatePerSec = 0.5 // 30 req/min - UserSyncRatePerSec = 5 // 300 req/min - GlobalRatePerSec = 10 // 600 req/min -) -``` - -**Automatic Cleanup**: -- Removes unused limiters every 5 minutes -- Prevents memory leaks from stale device limiters -- Maintains peak performance - ---- - -### 4. ✅ HTTPS Enforcement - -**File**: `internal/middleware/security.go` (180+ lines) - -**What Was Implemented**: -- Automatic HTTP → HTTPS redirect -- Security headers on all responses -- SSL proxy support for load balancers -- CORS with security best practices - -**Security Headers Added**: -```http -X-Content-Type-Options: nosniff -X-Frame-Options: DENY -X-XSS-Protection: 1; mode=block -Strict-Transport-Security: max-age=31536000; includeSubDomains; preload -Content-Security-Policy: default-src 'self' -Referrer-Policy: strict-origin-when-cross-origin -Permissions-Policy: geolocation=(), microphone=(), camera=() -``` - -**HTTPS Redirect**: -```go -// Automatic redirect in production -if c.Scheme() == "http" { - target.Scheme = "https" - return c.Redirect(http.StatusMovedPermanently, target) -} -``` - -**SSL Proxy Support**: -```go -// Handles X-Forwarded-* headers from load balancers -if proto := c.Request().Header.Get("X-Forwarded-Proto"); proto == "https" { - c.Request().URL.Scheme = "https" -} -``` - ---- - -### 5. ✅ Device Cap Per User - -**File**: `internal/handlers/device_cap.go` (180+ lines) - -**What Was Implemented**: -- Maximum 10 devices per user (configurable) -- Device usage statistics -- Automatic enforcement on registration -- Clear error messages with suggestions -- Admin override capability - -**Security Benefits**: -- ✅ **Attack Surface Reduction**: Limits blast radius of compromised credentials -- ✅ **Resource Protection**: Prevents account abuse -- ✅ **Cost Control**: Manages server resources efficiently -- ✅ **User Safety**: Helps users track their devices - -**Implementation**: -```go -const MaxDevicesPerUser = 10 - -// Check before allowing device registration -func ValidateUserDeviceCount(ctx, db, userID) error { - devices := db.ListDevicesByUser(ctx, userID) - if len(devices) >= MaxDevicesPerUser { - return "Device limit reached" - } - return nil -} -``` - -**Error Response**: -```json -{ - "error": "You have reached your device limit (10 devices)", - "max_devices": 10, - "current_count": 10, - "device_list": [ - "My Kindle (koreader)", - "My Kobo (kobo)", - "Work iPad (web)" - ], - "suggestions": [ - "Remove an unused device from Settings", - "Contact support to increase your limit" - ] -} -``` - ---- - -## Integration Points - -### Middleware Chain (Recommended Order) - -```go -e.Pre( - // Security first - middleware.HTTPSRedirectMiddleware("8443"), - middleware.SecurityHeadersMiddleware(), - - // Rate limiting - middleware.GlobalRateLimiter(config), - middleware.SyncRateLimiterMiddleware(syncLimiter, "sync"), - - // Request limits - middleware.RequestSizeMiddleware(sizeConfig, logger), - - // Device limits - handlers.CheckDeviceCapMiddleware(capConfig, db), - - // Authentication - middleware.JWTMiddleware(jwtConfig), - - // Device auth (if applicable) - middleware.DeviceAuthMiddleware(db), - - // Request signing (for sync endpoints) - middleware.RequestSigningMiddleware(signingConfig, getSecret), - - // CORS - middleware.SecureCORSMiddleware(corsConfig), -) -``` - -### Example Usage in main.go - -```go -import ( - "bookhoard/internal/middleware" - "bookhoard/internal/handlers" -) - -func main() { - // ... setup code ... - - // Security middleware - securityMiddleware := middleware.HTTPSProtectionMiddleware( - true, // enable redirect - "8443", // HTTPS port - ) - - e.Pre(securityMiddleware...) - - // Apply to sync routes - syncGroup := e.Group("/api/sync") - syncGroup.Use( - middleware.RequestSigningMiddleware(signingConfig, getSecret), - ) - - koreaderSync := syncGroup.Group("/koreader") - koreaderSync.POST("/progress", - middleware.SyncRateLimiterMiddleware(limiter, "sync"), - koreaderHandler.SyncProgress, - ) -} -``` - ---- - -## Testing Security Enhancements - -### Unit Tests Required - -**HMAC Signing**: -```go -func TestRequestSigning_ValidRequest(t *testing.T) -func TestRequestSigning_InvalidSignature(t *testing.T) -func TestRequestSigning_ReplayAttack(t *testing.T) -func TestRequestSigning_ClockSkew(t *testing.T) -``` - -**Request Size Limits**: -```go -func TestRequestSizeLimit_SyncPayload(t *testing.T) -func TestRequestSizeLimit_ExceedsLimit(t *testing.T) -func TestRequestSizeLimit_DifferentEndpoints(t *testing.T) -``` - -**Rate Limiting**: -```go -func TestRateLimiting_DeviceLimit(t *testing.T) -func TestRateLimiting_UserLimit(t *testing.T) -func TestRateLimiting_GlobalLimit(t *testing.T) -func TestRateLimiting_Cleanup(t *testing.T) -``` - -**Device Cap**: -```go -func TestDeviceCap_UnderLimit(t *testing.T) -func TestDeviceCap_AtLimit(t *testing.T) -func TestDeviceCap_ExceedsLimit(t *testing.T) -func TestDeviceCap_AdminOverride(t *testing.T) -``` - ---- - -## Performance Impact - -### Overhead Analysis - -| Feature | CPU Overhead | Memory Overhead | Network Impact | -|---------|-------------|----------------|---------------| -| HMAC Signing | ~0.5ms per request | ~100 bytes/device | +40 bytes/req | -| Size Limits | ~0.1ms per request | Minimal | None | -| Enhanced Rate Limiting | ~0.2ms per request | ~1KB total | None | -| Device Cap | ~1ms per registration | Minimal | None | -| HTTPS Headers | <0.1ms per request | ~200 bytes | +500 bytes/req | - -**Total Overhead**: ~1.9ms per request, ~1.3KB memory, +540 bytes/req - -**Trade-offs**: Minimal overhead for significantly enhanced security - ---- - -## Configuration - -### Environment Variables - -```bash -# Security settings -ENABLE_REQUEST_SIGNING=true -SIGNATURE_TIMESTAMP_TOLERANCE=300 # seconds -SIGNATURE_MAX_CLOCK_SKEW=60 # seconds - -# Rate limiting -DEVICE_SYNC_RATE_LIMIT=120 # req/min -DEVICE_METADATA_RATE_LIMIT=30 # req/min -USER_SYNC_RATE_LIMIT=300 # req/min -GLOBAL_RATE_LIMIT=600 # req/min - -# Request size limits -MAX_SYNC_PAYLOAD=10485760 # 10MB -MAX_ANNOTATION_SIZE=102400 # 100KB -MAX_METADATA_SIZE=1048576 # 1MB -MAX_IMAGE_UPLOAD_SIZE=52428800 # 50MB - -# Device limits -MAX_DEVICES_PER_USER=10 - -# HTTPS -HTTPS_PORT=8443 -HTTPS_REDIRECT_ENABLED=true -``` - -### Runtime Configuration - -```go -// In main.go -signingConfig := &middleware.RequestSigningConfig{ - Enabled: true, - TimestampTolerance: 5 * time.Minute, - MaxClockSkew: 1 * time.Minute, -} - -rateConfig := &middleware.SyncRateLimiterConfig{ - DeviceSyncRate: 120 / time.Minute, - DeviceMetadataRate: 30 / time.Minute, - UserSyncRate: 300 / time.Minute, - GlobalRate: 600 / time.Minute, -} - -sizeConfig := &middleware.RequestSizeLimitConfig{ - MaxSyncPayloadSize: 10 * 1024 * 1024, - MaxAnnotationSize: 100 * 1024, - MaxMetadataSize: 1 * 1024 * 1024, - MaxImageUploadSize: 50 * 1024 * 1024, -} - -capConfig := &handlers.DeviceCapConfig{ - MaxDevices: 10, - AllowAdminOverride: true, -} -``` - ---- - -## Migration Guide - -### For Existing Deployments - -**Step 1: Update Dependencies** -```bash -# No new dependencies required -# Uses existing crypto/hmac and uuid packages -``` - -**Step 2: Update Environment Variables** -```bash -# Add to .env or docker-compose.yml -ENABLE_REQUEST_SIGNING=true -MAX_DEVICES_PER_USER=10 -``` - -**Step 3: Update Middleware Chain** -```go -// Add to main.go middleware chain -import "bookhoard/internal/middleware" - -// In main(): -securityMiddleware := middleware.HTTPSProtectionMiddleware(true, "8443") -e.Pre(securityMiddleware...) -``` - -**Step 4: Regenerate Device Secrets** (Optional) -```sql --- For existing devices, generate signing secrets -UPDATE devices -SET auth_token = - auth_token || gen_random_uuid() || - 'device-secret-' || encode(gen_random_bytes(16), 'hex') -WHERE auth_token IS NULL OR auth_token = ''; -``` - -**Step 5: Deploy** -```bash -# Build and restart server -docker-compose down -docker-compose up --build -``` - ---- - -## Monitoring & Alerts - -### Key Metrics to Monitor - -1. **Security Events**: - - Invalid signature attempts - - Rate limit violations - - Device cap rejections - - Request size limit violations - -2. **Performance Metrics**: - - HMAC signing overhead - - Rate limiter hit rates - - Request size distribution - - Device registration trends - -3. **Alerts**: - - > 100 failed signature attempts in 5 minutes - - > 50 rate limit violations in 5 minutes - - Device limit reached (alert admin) - - Large request spike (potential DoS) - -### Log Examples - -**Security Event Log**: -```json -{ - "timestamp": "2026-01-31T12:00:00Z", - "event": "invalid_signature", - "device_id": "device-123", - "request_id": "req-456", - "ip_address": "192.168.1.100", - "signature_provided": "abc123...", - "signature_expected": "def456...", - "user_agent": "KOReader/2024.01" -} -``` - -**Rate Limit Log**: -```json -{ - "timestamp": "2026-01-31T12:00:00Z", - "event": "rate_limit_exceeded", - "device_id": "device-123", - "limit": 120, - "window": "60s", - "current": 150, - "path": "/api/sync/koreader/progress" -} -``` - ---- - -## Compliance - -### GDPR Compliance - -**Data Protection**: -- ✅ Enhanced data integrity via HMAC signing -- ✅ Secure data transmission (HTTPS enforced) -- ✅ Access control (device limits, rate limiting) - -**Privacy**: -- ✅ Request ID tracing without PII -- ✅ No sensitive data in logs -- ✅ Device token protection - -### OWASP Top 10 Coverage - -| Risk | Coverage | Notes | -|------|----------|-------| -| A01 Broken Access Control | ✅ | Device auth + JWT + HMAC | -| A02 Cryptographic Failures | ✅ | HMAC-SHA256 + TLS 1.3 | -| A03 Injection | ✅ | Parameterized queries + validation | -| A04 Insecure Design | ✅ | Rate limiting + size limits | -| A05 Security Misconfiguration | ✅ | Security headers + HTTPS | -| A06 Weak Auth | ✅ | bcrypt + JWT + device tokens | -| A07 ID & Auth Failures | ✅ | Device cap + registration flow | -| A08 Software/Data Integrity | ✅ | HMAC signing + validation | -| A09 Logging & Monitoring | ✅ | Request tracing + audit logs | -| A10 Server-Side Request Forgery | ✅ | CSRF headers + HMAC | - ---- - -## Conclusion - -All **Priority 1** security enhancements from the audit have been successfully implemented. The system is now **production-ready** with significantly improved security posture. - -### Key Achievements - -✅ **Request Integrity**: HMAC signing prevents tampering -✅ **DoS Protection**: Rate limiting + size limits -✅ **HTTPS Enforcement**: Automatic redirects + security headers -✅ **Access Control**: Device limits + enhanced authorization -✅ **Audit Trail**: Request ID tracing for security monitoring - -### Next Steps (Optional) - -While the system is production-ready, you may consider: - -1. **Performance Testing**: Load test with simulated sync traffic -2. **Penetration Testing**: Professional security audit -3. **Monitoring Setup**: Implement security event alerting -4. **Documentation**: Update user docs with security info - ---- - -**Implementation Status**: ✅ **COMPLETE** -**Production Ready**: ✅ **YES** -**Security Score**: **9.2/10 (A+)** -**Recommendation**: **Deploy to Production** - ---- - -**Implementation Completed**: January 31, 2026 -**Next Review**: Within 3 months -**Questions**: security@bookhoard.example.com diff --git a/docs/TESTING.md b/docs/TESTING.md deleted file mode 100644 index a41179e..0000000 --- a/docs/TESTING.md +++ /dev/null @@ -1,553 +0,0 @@ -# Bookhoard Integration Test Suite Documentation - -## Overview - -This document provides comprehensive information about the integration test suite for Bookhoard, including how to run tests, what they cover, and best practices for adding new tests. - -## Test Architecture - -### Location -All integration tests are located in `cmd/server/tests/` - -### Test Structure - -``` -cmd/server/tests/ -├── main_test.go # Framework verification -├── setup_test.go # Test setup and helper functions -├── test_helpers.go # Reusable test helpers -├── testrunner_test.go # Test runner verification -│ -├── analytics_test.go # Analytics endpoints (NEW) -├── auth_test.go # Authentication & authorization -├── book_matching_test.go # Book matching & bulk linking (NEW) -├── collections_bulk_test.go # Bulk collection operations (NEW) -├── conflicts_bulk_test.go # Bulk conflict resolution (NEW) -├── conflicts_test.go # Conflict management -├── device_cap_test.go # Device capability tests -├── device_test.go # Device management -├── edge_cases_test.go # Edge case coverage -├── filtering_test.go # Filtering functionality -├── isbn_and_library_test.go # ISBN & library tests -├── kobo_test.go # Kobo device sync -├── koreader_test.go # KOReader sync -├── library_test.go # Library management -├── library_test_comprehensive.go # Comprehensive library tests -├── media_bulk_test.go # Bulk media operations (NEW) -├── new_fixes_test.go # Recent fixes validation -├── opds_test.go # OPDS endpoints (NEW) -├── phase1_integration_test.go # Phase 1 integration tests -├── queue_test.go # Sync queue management -├── refresh_token_test.go # Token refresh flow (NEW) -├── registration_test.go # Device registration flow -├── search_test.go # Search functionality -├── security_test.go # Security tests -├── sorting_test.go # Sorting functionality -├── user_test.go # User management -└── websocket_test.go # WebSocket connections -``` - -## Running Tests - -### Prerequisites - -1. **Database Setup**: Tests require a running PostgreSQL database - ```bash - # Option 1: Use local database - export DATABASE_PASSWORD=postgres - - # Option 2: Use DATABASE_URL for containerized testing - export DATABASE_URL="postgresql://user:pass@localhost:5432/bookhoard" - ``` - -2. **Dependencies**: Ensure all Go dependencies are installed - ```bash - go mod download - ``` - -### Running All Tests - -```bash -# Run all tests in the test suite -cd cmd/server/tests -go test -v - -# Run with coverage report -go test -v -coverprofile=coverage.out -go tool cover -html=coverage.out -``` - -### Running Specific Test Files - -```bash -# Run only authentication tests -go test -v -run TestAuth - -# Run only analytics tests -go test -v -run TestAnalytics - -# Run specific test function -go test -v -run TestAnalyticsReadingStats -``` - -### Running Tests in Container - -```bash -# Build and run tests in Docker container -podman-compose up -d db -podman build -t bookhoard-test . -podman run --network bookhoard_default -e DATABASE_URL="postgresql://postgres:postgres@db:5432/bookhoard" bookhoard-test go test ./cmd/server/tests/ -v -``` - -### Test Modes - -```bash -# Short mode (skip lengthy tests) -go test -short -v - -# Verbose mode with detailed output -go test -v - -# Race detection -go test -race -v -``` - -## Test Coverage Summary - -### Coverage by Handler - -| Handler | Test File | Coverage | Notes | -|---------|-----------|----------|-------| -| **Analytics** | analytics_test.go | ✅ 100% | All 3 endpoints tested | -| **Auth** | auth_test.go | ✅ 95% | Login, register, profile, tokens | -| **Book Matching** | book_matching_test.go | ✅ 100% | Query, bulk link, auto-link, suggestions | -| **Collections** | collections_bulk_test.go | ✅ 100% | Bulk add operations | -| **Conflicts** | conflicts_bulk_test.go | ✅ 100% | Bulk resolve/dismiss operations | -| **Devices** | device_test.go, device_cap_test.go | ✅ 95% | Registration, management, capabilities | -| **Ebook/Scanner** | scanner tests | ✅ 90% | Scan, watch, metadata extraction | -| **KOReader** | koreader_test.go | ✅ 100% | Sync progress, metadata, library | -| **Kobo** | kobo_test.go | ✅ 100% | Initialization, markup, bookmarks | -| **Library** | library_test.go, library_test_comprehensive.go | ✅ 95% | CRUD, folders, visibility, types | -| **Media** | media_bulk_test.go | ✅ 100% | Bulk delete, bulk update | -| **OPDS** | opds_test.go | ✅ 100% | Catalog, search, download, conversion | -| **Progress** | progress tests | ✅ 90% | Universal progress, history | -| **Queue** | queue_test.go | ✅ 100% | Queue management, retry, delete | -| **Refresh Token** | refresh_token_test.go | ✅ 100% | Token refresh, security, edge cases | -| **Search** | search_test.go | ✅ 95% | Media item search, filters | -| **WebSocket** | websocket_test.go | ✅ 100% | Connection, auth, broadcasts | - -### Overall Statistics - -- **Total Test Functions**: 150+ -- **Total Test Cases**: 500+ -- **Code Coverage**: ~95% of backend code -- **Endpoint Coverage**: 100% of all REST and WebSocket endpoints - -## Test Categories - -### 1. Authentication & Authorization Tests - -**File**: `auth_test.go` - -- JWT token validation -- User registration (including first-user-admin) -- Login with rate limiting -- Password complexity requirements -- Profile management -- Token refresh flow -- Account lockout -- Role-based access control - -### 2. Analytics Tests (NEW) - -**File**: `analytics_test.go` - -- Reading statistics with date ranges -- Device usage statistics -- Popular books queries -- Invalid date handling -- Empty data handling -- Response structure validation - -### 3. Book Matching Tests (NEW) - -**File**: `book_matching_test.go` - -- Query books by title/author/identifiers -- Bulk linking operations -- Auto-linking with confidence thresholds -- Unlinked book suggestions -- Device file alias management -- Error handling for invalid IDs - -### 4. Bulk Operations Tests (NEW) - -**Files**: `collections_bulk_test.go`, `conflicts_bulk_test.go`, `media_bulk_test.go` - -- **Collections**: Bulk add books to multiple collections -- **Conflicts**: Bulk resolve with strategies (most_recent, highest_progress, manual) -- **Conflicts**: Bulk dismiss resolved conflicts -- **Media**: Bulk delete books -- **Media**: Bulk update metadata (tags, status, rating) - -### 5. Device Management Tests - -**Files**: `device_test.go`, `device_cap_test.go`, `registration_test.go` - -- Device registration flow -- Device approval/rejection -- Device capabilities detection -- Device metadata management -- Multiple device handling -- Device authentication - -### 6. E-Reader Integration Tests - -**Files**: `kobo_test.go`, `koreader_test.go` - -- **Kobo**: Initialization handshake -- **Kobo**: Markup sync -- **Kobo**: Bookmark sync -- **Kobo**: Analytics endpoint -- **KOReader**: Progress sync -- **KOReader**: Metadata retrieval -- **KOReader**: Library sync -- **KOReader**: Bookmark sync - -### 7. Library Management Tests - -**Files**: `library_test.go`, `library_test_comprehensive.go`, `isbn_and_library_test.go` - -- Library CRUD operations -- Folder management -- Library visibility -- Library types -- ISBN normalization -- Scan settings - -### 8. Media Management Tests - -**Files**: `media_bulk_test.go`, `search_test.go`, `filtering_test.go`, `sorting_test.go` - -- Media item CRUD -- Bulk operations -- Search functionality -- Filtering and sorting -- Progress tracking -- Notes and highlights -- Ratings - -### 9. OPDS Tests (NEW) - -**File**: `opds_test.go` - -- Device catalog retrieval -- Search functionality -- Navigation endpoint -- Book download -- Cover image retrieval -- Format listing -- On-the-fly KEPUB conversion - -### 10. Progress & Queue Tests - -**Files**: `queue_test.go`, progress tests in other files - -- Sync queue management -- Queue retry mechanism -- Progress tracking -- Reading history -- Universal progress - -### 11. Security Tests - -**File**: `security_test.go` - -- SQL injection prevention -- XSS prevention -- CSRF protection -- Rate limiting -- Input validation -- Authorization checks - -### 12. WebSocket Tests - -**File**: `websocket_test.go` - -- WebSocket connection establishment -- Device authentication via WebSocket -- Real-time progress broadcasts -- Ping/pong heartbeat -- Connection limits -- Message handling - -### 13. Token Refresh Tests (NEW) - -**File**: `refresh_token_test.go` - -- Valid token refresh -- Invalid/expired token handling -- Token reuse protection -- Token tampering detection -- Response structure validation -- Edge cases (empty, null, malformed) - -## Test Helper Functions - -### setupTestServer - -Creates a test server with database connection. - -```go -ts, db, cfg, handler := setupTestServer(t) -defer ts.Close() -``` - -**Returns**: -- `ts`: Test HTTP server -- `db`: Database queries interface -- `cfg`: Test configuration -- `handler`: Handler instance - -### loginTestUser - -Logs in a test user and returns JWT token. - -```go -token := loginTestUser(t, ts, db) -``` - -**Returns**: -- `token`: JWT access token - -### getTestUserID - -Gets or creates a test user. - -```go -userID := getTestUserID(t, db) -``` - -**Returns**: -- `userID`: UUID of test user - -### createTestEbookID - -Creates a test ebook and returns its ID. - -```go -bookID := createTestEbookID(t, ts, token) -``` - -**Returns**: -- `bookID`: String ID of created ebook - -## Adding New Tests - -### Template for Endpoint Tests - -```go -package main - -import ( - "bytes" - "encoding/json" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewEndpoint(t *testing.T) { - t.Run("Endpoint_WithoutAuth", func(t *testing.T) { - ts, _, _, _ := setupTestServer(t) - defer ts.Close() - - // Test without authentication - req, _ := http.NewRequest("GET", ts.URL+"/api/new-endpoint", nil) - client := &http.Client{} - resp, err := client.Do(req) - require.NoError(t, err) - defer resp.Body.Close() - - assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) - }) - - t.Run("Endpoint_WithAuth", func(t *testing.T) { - ts, db, _, _ := setupTestServer(t) - defer ts.Close() - - token := loginTestUser(t, ts, db) - - // Test with authentication - req, _ := http.NewRequest("GET", ts.URL+"/api/new-endpoint", nil) - req.Header.Set("Authorization", "Bearer "+token) - - client := &http.Client{} - resp, err := client.Do(req) - require.NoError(t, err) - defer resp.Body.Close() - - assert.Equal(t, http.StatusOK, resp.StatusCode) - - var result map[string]interface{} - json.NewDecoder(resp.Body).Decode(&result) - - // Add assertions for response structure - assert.Contains(t, result, "expected_field") - }) - - t.Run("Endpoint_InvalidInput", func(t *testing.T) { - ts, db, _, _ := setupTestServer(t) - defer ts.Close() - - token := loginTestUser(t, ts, db) - - // Test with invalid input - req := map[string]interface{}{ - "invalid": "data", - } - body, _ := json.Marshal(req) - - httpReq, _ := http.NewRequest("POST", ts.URL+"/api/new-endpoint", bytes.NewBuffer(body)) - httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) - - client := &http.Client{} - resp, err := client.Do(httpReq) - require.NoError(t, err) - defer resp.Body.Close() - - assert.Equal(t, http.StatusBadRequest, resp.StatusCode) - }) -} -``` - -### Best Practices - -1. **Use Table-Driven Tests** for multiple similar test cases -2. **Test All Error Paths**: Not just success cases -3. **Validate Response Structure**: Check all expected fields -4. **Test Edge Cases**: Empty inputs, invalid IDs, boundary values -5. **Use Subtests**: For organizing related test cases -6. **Clean Up Resources**: Always close response bodies -7. **Use require.NoError** for setup, assert.NoError for test conditions -8. **Create Isolated Tests**: Each test should be independent - -## CI/CD Integration - -### GitHub Actions Example - -```yaml -name: Integration Tests - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:15 - env: - POSTGRES_DB: bookhoard - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 - with: - go-version: '1.25' - - - name: Run integration tests - env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/bookhoard - run: | - cd cmd/server/tests - go test -v -race -coverprofile=coverage.out - - - name: Upload coverage - uses: codecov/codecov-action@v3 -``` - -## Troubleshooting - -### Common Issues - -1. **Database Connection Errors** - ```bash - # Ensure database is running - podman ps | grep postgres - - # Check connection string - echo $DATABASE_URL - ``` - -2. **Port Already in Use** - ```bash - # Tests use random ports (port 0), so this shouldn't happen - # If it does, check for running processes - lsof -i :8765 - ``` - -3. **Test Data Cleanup** - - Tests use automatic cleanup via `defer ts.Close()` - - Manual cleanup may be needed for complex scenarios - - Consider using database transactions for rollback - -4. **Time-Dependent Tests** - - Use fixed time values in tests - - Mock time functions if necessary - - Add tolerance for timestamp comparisons - -## Performance Considerations - -### Test Execution Time - -- Total suite: ~2-3 minutes -- Individual test files: 5-30 seconds -- Use `-short` flag for faster CI runs -- Parallel test execution with `-parallel` flag - -### Optimization Tips - -1. **Use Test Caching**: Go 1.18+ caches test results -2. **Minimize Database Calls**: Create test data once -3. **Parallelize Independent Tests**: Use `t.Parallel()` -4. **Avoid Sleep**: Use channels for synchronization - -## Future Improvements - -### Planned Enhancements - -- [ ] Add property-based testing with `github.com/stretchr/testify` -- [ ] Implement fuzzing for input validation -- [ ] Add performance benchmarks -- [ ] Contract testing for API compatibility -- [ ] Visual regression testing for UI endpoints - -### Coverage Goals - -- **Current**: ~95% backend coverage -- **Target**: 98% backend coverage -- **Frontend**: Add integration tests for frontend components - -## References - -- [Go Testing Guide](https://golang.org/doc/tutorial/add-a-test) -- [Testify Documentation](https://github.com/stretchr/testify) -- [Go Concurrency Testing](https://go.dev/doc/articles/race_detector) -- [API Testing Best Practices](https://martinfowler.com/articles/practical-test-pyramid.html) - ---- - -**Last Updated**: 2025-02-01 -**Maintained By**: Bookhoard Development Team diff --git a/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md similarity index 75% rename from TROUBLESHOOTING.md rename to docs/TROUBLESHOOTING.md index 79227d6..d964b81 100644 --- a/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -15,13 +15,17 @@ cp .env.example .env # 2. Edit with secure values nano .env -# Required: -JWT_SECRET="your-secure-jwt-secret-key-here" # 64+ char random string -DBPASS="your-secure-database-password" # Strong password -# Optional: -SERVER_PORT=8765 -DATABASE_HOST=localhost # For local development +# Required variables: +JWT_SECRET="your-secure-jwt-secret-key-here" # 64+ char random string +DBPASS="your-secure-database-password" # Strong password + +# Optional variables (with defaults in docker-compose.yml): +# TEST_MODE=false # Disables rate limiting (NEVER in production) +# RATE_LIMIT_ENABLED=true # Enable/disable rate limiting +# REQUESTS_PER_MINUTE=10 # Rate limit per IP +# BOOKHOARD_CONVERSION_TOOL=/usr/bin/ebook-convert +# BOOKHOARD_CONVERSION_CACHE_TTL=48h ``` ### 2. **Port Conflicts** @@ -40,18 +44,24 @@ sudo kill -9 $(lsof -t -i:8765) SERVER_PORT=8766 ``` -### 3. **Docker Engine Compatibility** +### 3. **Container Runtime - Podman vs Docker** -**Issue:** Using Podman instead of Docker +**Issue:** Container runtime compatibility **Solution:** ```bash -# Both work, but for full Docker compatibility: -# Install Docker Desktop -# or use Docker instead of podman command +# Podman is recommended (podman-compose works with docker-compose.yml) +# Install podman-compose: +sudo apt install podman-compose # Debian/Ubuntu -# Podman users: ensure podman-compose is installed -# Docker and Podman can both use the same compose file +# Docker also works (use docker-compose with docker-compose.yml) +# Both runtimes use the same docker-compose.yml file + +# Podman users: +podman-compose up -d + +# Docker users: +docker compose up -d ``` ### 4. **Database Permissions** @@ -61,12 +71,18 @@ SERVER_PORT=8766 **Solution:** ```bash # Clean database volume and restart: +# Podman: +podman-compose down -v +podman volume rm bookhoard_postgres_data 2>/dev/null +podman-compose up -d + +# Docker: docker compose down -v docker volume rm bookhoard_postgres_data 2>/dev/null docker compose up -d # Check database logs for errors: -docker compose logs db +podman-compose logs db # or: docker compose logs db ``` ### 5. **Build Dependencies** @@ -81,10 +97,35 @@ which sqlc # Check if sqlc is accessible which templ # Check if templ is accessible # Rebuild if tools are missing: -docker compose build --no-cache +podman-compose build --no-cache # or: docker compose build --no-cache ``` -### 6. **Platform-Specific Issues** +### 6. **Conversion Cache Issues** + +**Issue:** KEPUB conversion fails or cache problems + +**Solution:** +```bash +# Check cache directory exists and is writable +ls -la /var/bookhoard/cache/kepub + +# Create cache directory if missing +sudo mkdir -p /var/bookhoard/cache/kepub +sudo chmod 755 /var/bookhoard/cache/kepub + +# Clear conversion cache (safe - will reconvert on next download) +sudo rm -rf /var/bookhoard/cache/kepub/* + +# Verify kepubify is installed +which kepubify +# or: which ebook-convert + +# Conversion service defaults are in docker-compose.yml +# Check if you're overriding them in .env: +grep BOOKHOARD_CONVERSION .env +``` + +### 7. **Platform-Specific Issues** **Issue:** Different OS architectures (ARM vs x86) @@ -103,17 +144,17 @@ FROM golang:1.25-alpine AS builder # ... rest of Dockerfile remains same ``` -### 7. **Network Connectivity** +### 8. **Network Connectivity** **Issue:** Can't connect to localhost **Solution:** ```bash # Check if containers are running: -docker compose ps +podman-compose ps # or: docker compose ps # Test database connection: -docker compose exec db psql -U postgres -d bookhoard -c "SELECT 1;" +podman-compose exec db psql -U postgres -d bookhoard -c "SELECT 1;" # or: docker compose exec db ... # Test API endpoint: curl -s http://localhost:8765/api/libraries/visible @@ -127,10 +168,10 @@ curl -s http://SERVER_IP:8765/api/libraries/visible ### Basic Health Checks: ```bash # Check container status -docker compose ps +podman-compose ps # or: docker compose ps # Test database connection -docker compose exec db psql -U postgres -d bookhoard -c "SELECT 1;" +podman-compose exec db psql -U postgres -d bookhoard -c "SELECT 1;" # or: docker compose exec db ... # Test API endpoint curl -s http://localhost:8765/api/libraries/visible diff --git a/docs/contributing/DEVELOPMENT.md b/docs/contributing/DEVELOPMENT.md new file mode 100644 index 0000000..eb2f469 --- /dev/null +++ b/docs/contributing/DEVELOPMENT.md @@ -0,0 +1,442 @@ +# Bookhoard Development Guide + +This guide is for developers contributing to Bookhoard or setting up a development environment. + +## 🏗 Architecture + +### Directory Structure + +``` +bookhoard/ +├── cmd/server/ # Application entry point +│ ├── main.go # Server initialization, route registration +│ └── tests/ # Integration tests (30+ test files) +├── internal/ +│ ├── config/ # Configuration management +│ ├── database/ # Database layer (SQLC generated) +│ ├── handlers/ # HTTP request handlers (18 files) +│ ├── middleware/ # HTTP middleware (9 files) +│ ├── services/ # Business logic (7 files) +│ ├── sync/ # Sync framework (5 files) +│ ├── opds/ # OPDS feed generation +│ └── utils/ # Utility functions +├── templates/ # UI templates (17 .templ files) +├── web/src/ # Frontend TypeScript +├── database/schema/ # Database schema +├── docs/ # Documentation +└── bruno/ # API test collections +``` + +### Backend Components + +**Handlers** (`internal/handlers/`): +- `auth.go` - Authentication & user management +- `library.go` - Library CRUD operations +- `ebook.go` - Media item operations +- `media.go` - Media downloads, shelves +- `koreader.go` - KOReader sync protocol +- `kobo.go` - Kobo sync protocol +- `collections.go` - Collection management +- `devices.go` - Device registration/management +- `conflicts.go` - Sync conflict resolution +- `queue.go` - Sync queue management +- `progress.go` - Reading progress tracking +- `analytics.go` - Usage analytics +- `opds.go` - OPDS feed generation +- `websocket.go` - WebSocket connections +- `sync.go` - Sync orchestration +- `book_matching.go` - Book linking/matching +- `sidecar.go` - Sidecar file handling +- `refresh_token.go` - Token refresh logic +- `context.go` - Handler context utilities + +**Middleware** (`internal/middleware/`): +- `device_auth.go` - Device authentication +- `device_rate_limiter.go` - Device-specific rate limiting +- `error_handler.go` - Global error handling +- `login_attempts.go` - Login attempt tracking +- `password_validator.go` - Password complexity validation +- `rate_limiter.go` - IP-based rate limiting +- `request_tracing.go` - Request ID tracking +- `security.go` - Security headers +- `transaction.go` - Database transaction middleware + +**Services** (`internal/services/`): +- `library_service.go` - Library operations +- `ebook_scanner.go` - File scanning & metadata extraction +- `worker.go` - Job queue worker pool +- `scheduler.go` - Scheduled task manager +- `collection_service.go` - Collection rules processing +- `conversion_service.go` - EPUB→KEPUB conversion +- `book_matching.go` - Book matching algorithms + +**Sync Framework** (`internal/sync/`): +- `queue.go` - Sync queue processor +- `progress.go` - Universal progress format +- `websocket.go` - Real-time sync broadcast +- `offline.go` - Offline sync support +- `format.go` - Format group conversion + +### Database Schema + +**Core Tables**: +- `users` - User accounts with authentication and settings +- `libraries` - Library definitions +- `library_types` - Media type definitions (ebooks, comics, manga) +- `library_folders` - Multiple folders per library +- `library_visibility` - User-specific library access control +- `media_items` - Universal media storage (replaces ebooks table) +- `media_ratings` - User ratings (1-10 scale for half-star precision) +- `media_notes` - User annotations +- `media_highlights` - User highlights with color customization +- `reading_progress` - Universal progress tracking across devices +- `devices` - Device registry for sync +- `sync_queue` - Offline sync support +- `sync_conflicts` - Conflict resolution tracking +- `collections` - Device-neutral collections +- `collection_items` - Books in collections +- `device_shelf_mappings` - Map collections to device-specific shelves +- `device_catalogs` - Track OPDS downloads and ContentId mappings +- `kobo_shelves` - Kobo-specific shelf management +- `reading_history` - Reading session tracking +- `unlinked_books` - Track books that couldn't be auto-matched +- `media_item_formats` - Track all format versions with hashes +- `device_file_aliases` - Track file paths per device +- `refresh_tokens` - JWT refresh token storage + +**Database Functions**: +- `normalize_isbn()` - ISBN format normalization +- `detect_format_group()` - Detect format group (reflowable, fixed_layout, comic_archive) +- `convert_progress()` - Convert progress between format groups +- `detect_conflict()` - Detect sync conflicts +- `merge_progress()` - Merge progress from multiple sources + +### Technology Stack + +**Backend**: +- Go 1.25+ +- Echo v4 - HTTP framework +- pgx v5 - PostgreSQL driver +- SQLC - SQL code generation +- jwt-go - JWT authentication +- bcrypt - Password hashing + +**Frontend**: +- Templ - HTML templating with Go +- HTMX - Dynamic interactions +- Tailwind CSS - Styling +- TypeScript - Frontend logic + +**Database**: +- PostgreSQL 15+ +- 30+ tables +- 50+ indexes +- JSONB for complex data + +**Testing**: +- Testify - Testing framework +- Bruno - API testing +- 30+ integration test files + +## 🚀 Local Development + +### Prerequisites + +- Go 1.25+ +- Node.js 18+ (for frontend build) +- Podman or Docker +- PostgreSQL 15+ (or use Podman) + +### Setup + +```bash +# 1. Clone repository +git clone https://github.com/yourusername/bookhoard.git +cd bookhoard + +# 2. Install Go dependencies +go mod download + +# 3. Install build tools +go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest +go install github.com/a-h/templ/cmd/templ@latest + +# 4. Set up environment +cp .env.example .env +# Edit .env with your settings + +# 5. Generate database code +cd internal/database +sqlc generate + +# 6. Generate templates +cd ../../templates +templ generate + +# 7. Build frontend +cd ../web +npm install +npm run build + +# 8. Run tests +cd .. +go test ./... -v +``` + +### Running Locally + +```bash +# Option 1: Using containers (recommended) +podman-compose up --build + +# Option 2: Direct Go run (requires local PostgreSQL) +export JWT_SECRET="your-dev-secret" +export DBPASS="your-db-password" +go run cmd/server/main.go +``` + +### Development Workflow + +**Backend Development**: +```bash +# Watch mode for Go (requires air or similar) +air + +# Or manual rebuild +go build -o bookhoard cmd/server/main.go +./bookhoard +``` + +**Frontend Development**: +```bash +cd web +npm run dev # Watch mode for TypeScript/CSS +``` + +**Database Changes**: +1. Edit `database/schema/schema.sql` +2. Edit `internal/database/queries/queries.sql` +3. Run: `cd internal/database && sqlc generate` +4. Restart server + +**Template Changes**: +1. Edit `templates/*.templ` +2. Run: `cd templates && templ generate` +3. Restart server (templates auto-reload in dev mode) + +## 🧪 Testing + +### Unit Tests + +```bash +# Run all unit tests +go test ./... -v + +# Run specific package tests +go test ./internal/handlers/... -v + +# Run with coverage +go test ./... -coverprofile=coverage.out +go tool cover -html=coverage.out +``` + +### Integration Tests + +```bash +# Run integration tests (may hit rate limits) +go test ./cmd/server/tests -v + +# Run with test mode (recommended) +TEST_MODE=true RATE_LIMIT_ENABLED=false go test ./cmd/server/tests -v + +# Run specific test +go test ./cmd/server/tests -run TestAuth -v +``` + +### API Testing with Bruno + +```bash +# Install Bruno CLI +npm install -g @usebruno/cli + +# Run all tests +bruno run + +# Run specific collection +bruno run bruno/user/ +bruno run bruno/sync-kobo/ +``` + +### Test Configuration + +Environment variables for testing: +- `TEST_MODE=true` - Enable test mode (disables rate limiting) +- `RATE_LIMIT_ENABLED=false` - Disable rate limiting +- `REQUESTS_PER_MINUTE=1000` - Increase rate limit + +**⚠️ WARNING**: Never enable these in production! + +## 📝 Code Style Guidelines + +### Go Code + +- Follow [Effective Go](https://go.dev/doc/effective_go) guidelines +- Use `gofmt` for formatting +- Use `golangci-lint` for linting +- Use meaningful variable and function names +- Add comments for complex business logic +- Handle errors explicitly - don't ignore them + +### Database Operations + +- **Always use sqlc-generated code** - No raw SQL in handlers +- Use transactions for multi-step operations +- Handle `pgx.ErrNoRows` explicitly +- Use `pgtype.UUID` for UUID parameters +- Validate inputs before database operations + +### Error Handling + +```go +// Good - Explicit error handling +user, err := h.db.GetUser(c.Request().Context(), userID) +if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"}) + } + return c.JSON(http.StatusInternalServerError, map[string]string{"error": "database error"}) +} + +// Bad - Ignoring errors +user, _ := h.db.GetUser(c.Request().Context(), userID) +``` + +### Adding New Features + +1. **Database First**: Add tables/columns to `schema.sql` +2. **Generate Queries**: Add to `queries.sql` and run `sqlc generate` +3. **Handler**: Implement in `internal/handlers/` +4. **Routes**: Register in `cmd/server/main.go` +5. **Tests**: Add integration test in `cmd/server/tests/` +6. **Bruno**: Add API test in `bruno/` +7. **Docs**: Update relevant documentation + +### API Design Principles + +- RESTful naming conventions +- Consistent error responses +- Proper HTTP status codes +- JWT authentication on protected routes +- Input validation with struct tags +- Use echo.Context for request/response + +## 🐳 Deployment + +### Building for Production + +```bash +# Using Makefile +make build-force + +# Or manually +podman-compose build --no-cache +``` + +### Environment Variables + +Required for production: +- `JWT_SECRET` - 64-byte random string +- `DBPASS` - Strong database password +- `BASE_URL` - Public URL (e.g., https://bookhoard.example.com) + +Optional: +- `HTTPS_PROXY` - If behind reverse proxy + +**Note**: Conversion service, rate limiting, and other operational settings have defaults in `docker-compose.yml` and can be overridden via `.env` if needed. + +### Performance Tuning + +**PostgreSQL Settings**: +```sql +-- In postgresql.conf +shared_buffers = 256MB +effective_cache_size = 1GB +maintenance_work_mem = 64MB +checkpoint_completion_target = 0.9 +wal_buffers = 16MB +default_statistics_target = 100 +random_page_cost = 1.1 +effective_io_concurrency = 200 +work_mem = 2621kB +min_wal_size = 1GB +max_wal_size = 4GB +``` + +**Go Settings**: +- GOMAXPROCS = number of CPU cores +- Worker pool concurrency: 3 (configurable in services/worker.go) + +## 🔍 Debugging + +### Enable Debug Logging + +```bash +# Set environment variable +export DEBUG=true + +# Or in .env +DEBUG=true +``` + +### Common Issues + +**Database Connection Errors**: +- Check PostgreSQL is running +- Verify DATABASE_HOST and DATABASE_PORT +- Check firewall settings + +**Rate Limiting During Development**: +- Enable test mode: `TEST_MODE=true RATE_LIMIT_ENABLED=false` +- Or increase limit: `REQUESTS_PER_MINUTE=1000` + +**Template Not Updating**: +- Run `templ generate` in templates/ directory +- Restart server + +**Database Queries Not Working**: +- Run `sqlc generate` in internal/database/ +- Check generated code in `queries.sql.go` +- Verify SQL syntax in `queries.sql` + +## 📚 Additional Resources + +- [Project Guidelines](../../PROJECT_GUIDELINES.md) - Development rules and standards +- [API Reference](../API_REFERENCE.md) - Complete API documentation +- [Troubleshooting](../TROUBLESHOOTING.md) - Deployment issues +- [Go Documentation](https://go.dev/doc/) +- [Echo Framework](https://echo.labstack.com/docs) +- [pgx Documentation](https://pgx.github.io/pgx/) + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Follow code style guidelines +4. Add tests for new features +5. Ensure all tests pass +6. Commit with clear messages +7. Push to branch (`git push origin feature/amazing-feature`) +8. Open a Pull Request + +### Pull Request Checklist + +- [ ] Code follows style guidelines +- [ ] Tests added/updated +- [ ] Documentation updated +- [ ] All tests passing +- [ ] No new warnings +- [ ] Commit messages are clear + +--- + +**Happy Coding!** 🚀