diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index e06864c..ad99807 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -13,6 +13,7 @@ This document provides precise, line-by-line steps to implement the scanner fixe **File:** `internal/services/media_scanner.go` **Current code (around line 348-360):** + ```go func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool, error) { // Get file info @@ -27,6 +28,7 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool, ``` **Add AFTER line 357 (after getting file info, before existingItem check):** + ```go // Get file modification time for created_at fileModTime := info.ModTime() @@ -37,6 +39,7 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool, **File:** `internal/database/queries/queries.sql` **Current code (line 131-133):** + ```sql -- name: CreateMediaItem :one INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, tags_search, asin, date_published, publisher, contributors, contributors_search, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id) @@ -45,6 +48,7 @@ RETURNING *; ``` **Change TO:** + ```sql -- name: CreateMediaItem :one INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, tags_search, asin, date_published, publisher, contributors, contributors_search, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at) @@ -55,6 +59,7 @@ RETURNING *; ### Step 1.3: Regenerate Go code from SQL OR manually update queries.sql.go **Option A - Run SQL generation (recommended):** + ```bash cd internal/database && go generate ./... ``` @@ -66,6 +71,7 @@ cd internal/database && go generate ./... **Find `CreateMediaItemParams` struct (around line 557):** **Add to struct (after AddedByAdminID):** + ```go CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` ``` @@ -73,6 +79,7 @@ CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` **Find `CreateMediaItem` function (around line 588):** **Add to the query parameters (after AddedByAdminID in the VALUES):** + ```go arg.CreatedAt, ``` @@ -84,6 +91,7 @@ arg.CreatedAt, **Find the CreateMediaItem call** - around line 512. **Current code (line 512-532):** + ```go createdItem, err := s.db.CreateMediaItem(ctx, database.CreateMediaItemParams{ LibraryID: libraryID, @@ -93,11 +101,13 @@ createdItem, err := s.db.CreateMediaItem(ctx, database.CreateMediaItemParams{ ``` **Add to the params (after AddedByAdminID):** + ```go CreatedAt: pgtype.Timestamp{Time: fileModTime, Valid: true}, ``` **Note:** You'll need to import `"github.com/jackc/pgx/v5/pgtype"` if not already present. + ```go CreatedAt: pgtype.Timestamp{Time: fileModTime, Valid: true}, ``` @@ -115,6 +125,7 @@ createdItem, err := s.db.CreateMediaItem(ctx, database.CreateMediaItemParams{ **File:** `internal/services/media_scanner.go` **Current code (around lines 365-372):** + ```go if s.forceRescan { fmt.Printf("Force rescan enabled, re-processing existing media item: %s\n", path) @@ -129,6 +140,7 @@ if s.forceRescan { ### Step 2.2: Replace DELETE+INSERT with UPDATE **Replace lines 365-372 with:** + ```go if s.forceRescan { fmt.Printf("Force rescan enabled, updating existing media item: %s\n", path) @@ -155,6 +167,7 @@ if s.forceRescan { **Goal:** Prevent cross-library interference - scanning one library shouldn't affect another library's entries. **Important Context:** The `libraryID` is already available in `StartWatchModeForLibrary` at `scanner.go:385`: + ```go h.StartWatchModeForLibrary(ctx, library.ID, library.CreatedByAdminID) ``` @@ -168,6 +181,7 @@ The function receives `libraryID` but doesn't store it in the scanner. We need t **Find the SetAdminID function (around line 104):** **Add AFTER SetAdminID:** + ```go func (s *MediaScanner) SetLibraryID(libraryID pgtype.UUID) { s.defaultLibraryID = libraryID @@ -179,12 +193,14 @@ func (s *MediaScanner) SetLibraryID(libraryID pgtype.UUID) { **File:** `internal/handlers/scanner.go` **Current code (around line 268):** + ```go scanner.SetAdminID(adminID) scanner.WatchChanges(h.watchModeCtx) ``` **Add AFTER line 268:** + ```go scanner.SetLibraryID(libraryID) ``` @@ -194,12 +210,14 @@ scanner.SetLibraryID(libraryID) **File:** `internal/database/queries/queries.sql` **Current code (line 302-303):** + ```sql -- name: GetMediaItemByFilePath :one SELECT * FROM media_items WHERE file_path = $1; ``` **Change TO:** + ```sql -- name: GetMediaItemByFilePath :one SELECT * FROM media_items WHERE file_path = $1 AND library_id = $2; @@ -210,10 +228,12 @@ SELECT * FROM media_items WHERE file_path = $1 AND library_id = $2; **File:** `internal/database/queries.sql.go` Find `GetMediaItemByFilePath` function and update: + 1. Add `LibraryID pgtype.UUID` parameter to the function and params struct 2. Add the parameter to the query call **OR run SQL generation:** + ```bash cd internal/database && go generate ./... ``` @@ -223,6 +243,7 @@ cd internal/database && go generate ./... **File:** `internal/services/media_scanner.go` **Current code (lines 1395-1397):** + ```go func (s *MediaScanner) getMediaItemByFilePath(ctx context.Context, filePath string) (database.MediaItems, error) { return s.db.GetMediaItemByFilePath(ctx, filePath) @@ -230,6 +251,7 @@ func (s *MediaScanner) getMediaItemByFilePath(ctx context.Context, filePath stri ``` **Change TO:** + ```go func (s *MediaScanner) getMediaItemByFilePath(ctx context.Context, filePath string) (database.MediaItems, error) { return s.db.GetMediaItemByFilePath(ctx, filePath, s.defaultLibraryID) @@ -241,6 +263,7 @@ func (s *MediaScanner) getMediaItemByFilePath(ctx context.Context, filePath stri **File:** `internal/services/media_scanner.go` Update all places that call `getMediaItemByFilePath` to pass the libraryID: + - Line 361: In `processMediaFile` - already has access to libraryID via folder lookup **Note:** The `processMediaFile` function already determines libraryID from the folder path (lines 487-501). Use that libraryID instead of `s.defaultLibraryID` for better accuracy. @@ -288,12 +311,12 @@ func NewScannerLogger() *ScannerLogger { // ensureLogFiles creates/opens log files for today func (l *ScannerLogger) ensureLogFiles() error { today := time.Now().Format("2006-01-02") - + // Check if we need to rotate (new day) if l.currentDate == today && l.deletesFile != nil { return nil // Already have today's files open } - + // Close existing files if l.deletesFile != nil { l.deletesFile.Close() @@ -301,41 +324,41 @@ func (l *ScannerLogger) ensureLogFiles() error { if l.errorsFile != nil { l.errorsFile.Close() } - + // Create log directory if it doesn't exist if err := os.MkdirAll(logDir, 0755); err != nil { return fmt.Errorf("failed to create log directory: %v", err) } - + // Open new files for today deletesPath := filepath.Join(logDir, fmt.Sprintf("scanner-deletes-%s.log", today)) errorsPath := filepath.Join(logDir, fmt.Sprintf("scanner-errors-%s.log", today)) - + deletesFile, err := os.OpenFile(deletesPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("failed to open deletes log file: %v", err) } - + errorsFile, err := os.OpenFile(errorsPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { deletesFile.Close() return fmt.Errorf("failed to open errors log file: %v", err) } - + l.deletesFile = deletesFile l.errorsFile = errorsFile l.currentDate = today - + // Clean up old log files l.cleanupOldLogs() - + return nil } // cleanupOldLogs removes log files older than maxLogAgeDays func (l *ScannerLogger) cleanupOldLogs() { cutoff := time.Now().AddDate(0, 0, -maxLogAgeDays) - + filepath.Walk(logDir, func(path string, info os.FileInfo) error { if err != nil { return err @@ -381,9 +404,11 @@ func (l *ScannerLogger) Close() { ``` **Add to MediaScanner struct:** + - Add `logger *ScannerLogger` field to track logger instance **Update NewMediaScanner function:** + - Initialize logger: `logger: NewScannerLogger()` ### Step 4.1: Ensure libraryID is available in scanner @@ -397,6 +422,7 @@ func (l *ScannerLogger) Close() { **Find:** `WatchChanges` function (around line 1427). **Find the event handling section (around lines 1449-1455):** + ```go // Handle file modifications and creations if (event.Has(fsnotify.Create) || event.Has(fsnotify.Write)) && s.isScannableFile(event.Name) { @@ -408,12 +434,13 @@ if (event.Has(fsnotify.Create) || event.Has(fsnotify.Write)) && s.isScannableFil ``` **Add AFTER that block (before line 1457):** + ```go // Handle file deletions if event.Has(fsnotify.Remove) && s.isScannableFile(event.Name) { // Use file logger for persistence s.logger.LogDelete(fmt.Sprintf("[WATCH-DELETE] File removed from filesystem: %s", event.Name)) - + // Determine libraryID for this file var libraryID pgtype.UUID for _, folder := range s.folders { @@ -431,20 +458,20 @@ if event.Has(fsnotify.Remove) && s.isScannableFile(event.Name) { s.logger.LogError(msg) return } - + // Look up media item BEFORE deleting - log for safety existingItem, err := s.db.GetMediaItemByFilePath(ctx, event.Name, libraryID) if err == nil { - msg := fmt.Sprintf("[WATCH-DELETE] Found media item to delete: ID=%s, Title=%s, Path=%s", + msg := fmt.Sprintf("[WATCH-DELETE] Found media item to delete: ID=%s, Title=%s, Path=%s", existingItem.ID, existingItem.Title.String, existingItem.FilePath.String) s.logger.LogDelete(msg) - + if err := s.db.DeleteMediaItem(ctx, existingItem.ID); err != nil { errMsg := fmt.Sprintf("[WATCH-DELETE] ERROR: failed to delete media item %s: %v", existingItem.ID, err) s.logger.LogDelete(errMsg) s.logger.LogError(errMsg) } else { - s.logger.LogDelete(fmt.Sprintf("[WATCH-DELETE] SUCCESS: deleted media item '%s' (was at %s)", + s.logger.LogDelete(fmt.Sprintf("[WATCH-DELETE] SUCCESS: deleted media item '%s' (was at %s)", existingItem.Title.String, existingItem.FilePath.String)) } } else if err != pgx.ErrNoRows { @@ -468,6 +495,7 @@ if event.Has(fsnotify.Remove) && s.isScannableFile(event.Name) { **Find:** End of `ScanFolders` function (after line 242). **Current code (around line 242-248):** + ```go fmt.Printf("Scan completed: %d total files scanned, %d media files found, %d new items, %d errors\n", processedFiles, mediaFiles, s.newItems, s.errors) @@ -480,6 +508,7 @@ return nil ``` **Add BEFORE `return nil`:** + ```go // Clean up: Find media items in DB that no longer exist on filesystem for _, folder := range s.folders { @@ -512,14 +541,14 @@ for _, folder := range s.folders { for _, item := range dbItems { filePath := item.FilePath.String if filePath != "" && !scannedPaths[filePath] { - msg := fmt.Sprintf("[RESCAN-CLEANUP] Orphaned media item found: ID=%s, Title=%s, Path=%s", + msg := fmt.Sprintf("[RESCAN-CLEANUP] Orphaned media item found: ID=%s, Title=%s, Path=%s", item.ID, item.Title.String, filePath) s.logger.LogDelete(msg) - - delMsg := fmt.Sprintf("[RESCAN-CLEANUP] Deleting orphaned item '%s' (file no longer exists at %s)", + + delMsg := fmt.Sprintf("[RESCAN-CLEANUP] Deleting orphaned item '%s' (file no longer exists at %s)", item.Title.String, filePath) s.logger.LogDelete(delMsg) - + if err := s.db.DeleteMediaItem(ctx, item.ID); err != nil { errMsg := fmt.Sprintf("[RESCAN-CLEANUP] ERROR: failed to delete orphaned item %s: %v", item.Title.String, err) s.logger.LogDelete(errMsg) @@ -533,6 +562,7 @@ for _, folder := range s.folders { ``` **Log Files Location:** `/app/logs/` + - `scanner-deletes-YYYY-MM-DD.log` - All deletion events (watch mode + rescan) - `scanner-errors-YYYY-MM-DD.log` - All error events - Rotation: Daily, keeps 7 days of history @@ -543,11 +573,13 @@ for _, folder := range s.folders { ## Verification Steps After Implementation 1. **Compile the code:** + ```bash go build ./... ``` 2. **Run tests:** + ```bash go test ./... -v ``` @@ -571,14 +603,16 @@ for _, folder := range s.folders { ## Safety Checks (IMPORTANT) ### Prevent deleting ALL books: + - The cleanup logic MUST check `scannedPaths[filePath]` - this ensures we only delete items whose paths were NOT found during the filesystem scan - The key condition is: `if filePath != "" && !scannedPaths[filePath]` - meaning "if this file was NOT found in our scan, delete it" - This is correct because: 1. We scan the filesystem → get all current file paths - 2. We query DB → get all stored file paths + 2. We query DB → get all stored file paths 3. We compare → only delete if DB path is NOT in filesystem paths ### Before running against production: + - Test with a small subset of books first - Verify the delete queries target specific library_id (not all libraries) - Check logs show only expected deletions @@ -587,20 +621,20 @@ for _, folder := range s.folders { ## Files to Modify -| Fix | File | Changes | -|-----|------|---------| -| 1a | `internal/database/queries/queries.sql` | Add created_at to INSERT columns | -| 1b | `internal/database/queries.sql.go` | Add CreatedAt to CreateMediaItemParams struct + query | -| 1c | `internal/services/media_scanner.go` | Get file.ModTime() + pass to CreateMediaItem | -| 2 | `internal/services/media_scanner.go` | Change force rescan from DELETE+INSERT to UPDATE | -| 3a | `internal/services/media_scanner.go` | Add SetLibraryID() method | -| 3b | `internal/handlers/scanner.go` | Call scanner.SetLibraryID(libraryID) | -| 3c | `internal/database/queries/queries.sql` | Add library_id to WHERE clause | -| 3d | `internal/database/queries.sql.go` | Update GetMediaItemByFilePath params | -| 3e | `internal/services/media_scanner.go` | Pass libraryID to getMediaItemByFilePath | -| 4.0 | `internal/services/scanner_logger.go` | **NEW FILE** - File logging infrastructure | -| 4a | `internal/services/media_scanner.go` | Add fsnotify.Remove handler in WatchChanges + use logger | -| 4b | `internal/services/media_scanner.go` | Add cleanup loop in ScanFolders + use logger | +| Fix | File | Changes | +| --- | --------------------------------------- | -------------------------------------------------------- | +| 1a | `internal/database/queries/queries.sql` | Add created_at to INSERT columns | +| 1b | `internal/database/queries.sql.go` | Add CreatedAt to CreateMediaItemParams struct + query | +| 1c | `internal/services/media_scanner.go` | Get file.ModTime() + pass to CreateMediaItem | +| 2 | `internal/services/media_scanner.go` | Change force rescan from DELETE+INSERT to UPDATE | +| 3a | `internal/services/media_scanner.go` | Add SetLibraryID() method | +| 3b | `internal/handlers/scanner.go` | Call scanner.SetLibraryID(libraryID) | +| 3c | `internal/database/queries/queries.sql` | Add library_id to WHERE clause | +| 3d | `internal/database/queries.sql.go` | Update GetMediaItemByFilePath params | +| 3e | `internal/services/media_scanner.go` | Pass libraryID to getMediaItemByFilePath | +| 4.0 | `internal/services/scanner_logger.go` | **NEW FILE** - File logging infrastructure | +| 4a | `internal/services/media_scanner.go` | Add fsnotify.Remove handler in WatchChanges + use logger | +| 4b | `internal/services/media_scanner.go` | Add cleanup loop in ScanFolders + use logger | ### Docker Compose Volume Mount @@ -618,6 +652,7 @@ volumes: After editing `queries.sql`, you MUST regenerate the Go code: **Option A - Run SQL code generation (recommended):** + ```bash cd internal/database && go generate ./... ``` @@ -625,10 +660,12 @@ cd internal/database && go generate ./... **Option B - Manual update (if Option A fails):** If `go generate` fails or is not available, manually update `queries.sql.go`: + 1. Add `LibraryID pgtype.UUID` parameter to `GetMediaItemByFilePathParams` struct 2. Add parameter to the query function call For Fix 1, manually add `CreatedAt` to: + - `CreateMediaItemParams` struct - The query VALUES @@ -648,11 +685,13 @@ For Fix 1, manually add `CreatedAt` to: ### Existing Tests Analysis **Current scanner integration tests** (`cmd/server/tests/scanner_integration_test.go`): + - Uses `/app/uploads` as test folder - Tests scan, progress tracking, watch mode start/stop - **After Fix 3:** Tests can safely use `/app/uploads` because GetMediaItemByFilePath now filters by library_id - test library's entries are isolated from user's library **Current unit tests** (`internal/services/*_test.go`): + - `media_scanner_epub_cover_test.go` - Tests cover extraction - `media_scanner_hash_test.go` - Tests hash calculation - `media_scanner_library_type_test.go` - Tests library type detection @@ -672,20 +711,20 @@ func TestProcessMediaFile_UsesFileMtime(t *testing.T) { // Create test file with specific modification time testFile := createTestEpub(t, "test-book.epub") defer os.Remove(testFile) - + // Set specific mtime pastTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) os.Chtimes(testFile, pastTime, pastTime) - + // Process file scanner := NewMediaScanner(db) scanner.SetFolders([]string{filepath.Dir(testFile)}) scanner.SetLibraryID(testLibraryID) - + wasNew, err := scanner.ProcessMediaFile(ctx, testFile) require.NoError(t, err) require.True(t, wasNew) - + // Verify created_at matches file mtime, not scan time item, err := db.GetMediaItemByFilePath(ctx, testFile, testLibraryID) require.NoError(t, err) @@ -697,24 +736,24 @@ func TestForceRescan_PreservesCreatedAt(t *testing.T) { // Create and process file testFile := createTestEpub(t, "test-book.epub") defer os.Remove(testFile) - + scanner := NewMediaScanner(db) scanner.SetFolders([]string{filepath.Dir(testFile)}) scanner.SetLibraryID(testLibraryID) - + _, _ = scanner.ProcessMediaFile(ctx, testFile) - + // Get original created_at item, _ := db.GetMediaItemByFilePath(ctx, testFile, testLibraryID) originalCreatedAt := item.CreatedAt.Time - + // Wait a moment to ensure time difference time.Sleep(100 * time.Millisecond) - + // Force rescan scanner.SetForce(true) _, _ = scanner.ProcessMediaFile(ctx, testFile) - + // Verify created_at is preserved item, _ = db.GetMediaItemByFilePath(ctx, testFile, testLibraryID) assert.Equal(t, originalCreatedAt, item.CreatedAt.Time) @@ -731,21 +770,21 @@ func TestGetMediaItemByFilePath_FiltersByLibrary(t *testing.T) { // Same file path in two different libraries testFile := createTestEpub(t, "shared-book.epub") defer os.Remove(testFile) - + // Add to library A scannerA := NewMediaScanner(db) scannerA.SetLibraryID(libraryAID) _, _ = scannerA.ProcessMediaFile(ctx, testFile) - + // Add same path to library B (simulating shared folder scenario) scannerB := NewMediaScanner(db) scannerB.SetLibraryID(libraryBID) _, _ = scannerB.ProcessMediaFile(ctx, testFile) - + // Verify each library has its own entry itemA, errA := db.GetMediaItemByFilePath(ctx, testFile, libraryAID) itemB, errB := db.GetMediaItemByFilePath(ctx, testFile, libraryBID) - + require.NoError(t, errA) require.NoError(t, errB) assert.Equal(t, libraryAID, itemA.LibraryID) @@ -765,30 +804,30 @@ func TestGetMediaItemByFilePath_FiltersByLibrary(t *testing.T) { func TestScan_DeletesOrphanedBooks(t *testing.T) { // Create test library pointing to /app/uploads (same as existing tests) testFolder := "/app/uploads" - + // Create test library with test folder libraryID := createTestLibrary(t, s.setup.Token, "Orphan Test Library", testFolder) - + // Add a test file testFile := createTestEpubFile(t, testFolder, "test-orphan-book.epub") defer os.Remove(testFile) // Cleanup after test - + // Add a test file testFile := createTestEpubFile(t, testFolder, "test-book.epub") - + // Initial scan scanLibrary(t, s.setup.Server.URL, libraryID, s.setup.Token) - + // Verify book exists items, _ := s.setup.DB.ListMediaItemsByLibrary(ctx, libraryID) require.Len(t, items, 1) - + // Delete file from filesystem (simulating user deletion) os.Remove(testFile) - + // Rescan scanLibrary(t, s.setup.Server.URL, libraryID, s.setup.Token) - + // Verify book was deleted from DB items, _ = s.setup.DB.ListMediaItemsByLibrary(ctx, libraryID) assert.Len(t, items, 0, "Orphaned book should be removed from database") @@ -804,33 +843,33 @@ func TestScan_DeletesOrphanedBooks(t *testing.T) { func TestWatchMode_DeletesRemovedFile(t *testing.T) { // Use /app/uploads - Fix 3 handles isolation testFolder := "/app/uploads" - + // Create test library libraryID := createTestLibrary(t, s.setup.Token, "Watch Delete Test", testFolder) - + // Add test file testFile := createTestEpubFile(t, testFolder, "watch-test.epub") - + // Start watch mode startWatchMode(t, s.setup.Server.URL, libraryID, s.setup.Token) - + // Wait for initial scan time.Sleep(2 * time.Second) - + // Verify book exists items, _ := s.setup.DB.ListMediaItemsByLibrary(ctx, libraryID) require.Len(t, items, 1) - + // Delete file os.Remove(testFile) - + // Wait for watch mode to detect time.Sleep(2 * time.Second) - + // Verify book was deleted items, _ = s.setup.DB.ListMediaItemsByLibrary(ctx, libraryID) assert.Len(t, items, 0, "Book should be deleted when file removed from filesystem") - + // Stop watch mode stopWatchMode(t, s.setup.Server.URL, libraryID, s.setup.Token) } @@ -845,17 +884,19 @@ func TestWatchMode_DeletesRemovedFile(t *testing.T) { ### Documentation Updates If API behavior changes, update: + - `docs/developer/api/scanner.md` - For any endpoint changes - `docs/user/` - If user-facing behavior changes ### Running Tests After implementation, run: + ```bash # Unit tests go test ./internal/services/... -v -run "TestProcessMediaFile|TestGetMediaItemByFilePath|TestForceRescan" -# Integration tests +# Integration tests go test ./cmd/server/tests/... -v -run "Scanner" # All tests diff --git a/PROJECT_GUIDELINES.md b/PROJECT_GUIDELINES.md index 0da0f26..51b19df 100644 --- a/PROJECT_GUIDELINES.md +++ b/PROJECT_GUIDELINES.md @@ -3,17 +3,19 @@ ## 🚨 CRITICAL PROHIBITIONS (Never violate these) ### Backend & Database + - ❌ **NEVER modify backend code when working on frontend-only tasks** - ❌ **NEVER modify database schema** unless explicitly instructed for full-stack changes - ❌ **NEVER use Docker** - use Podman only - ❌ **NEVER build server binaries locally** - all builds through Dockerfile/docker-compose - ❌ **NEVER create new migration files** - merge changes into current one until release - ❌ **NEVER use `git checkout` on schema files** without checking what will be lost - - ❌ **NEVER break existing functionality** unless explicitly instructed - - ❌ **NEVER duplicate business logic** - keep logic in services, not handlers - - ❌ **NEVER bypass service layer** - all database operations must go through services - +- ❌ **NEVER break existing functionality** unless explicitly instructed +- ❌ **NEVER duplicate business logic** - keep logic in services, not handlers +- ❌ **NEVER bypass service layer** - all database operations must go through services + ### Testing + - ✅ **ALWAYS use `setupTestServer()` helper from `cmd/server/tests/test_helpers.go`** - ✅ **Share one test setup across all subtests** - call `setupTestServer()` once at test function level, not per subtest - ✅ **Prefer table-driven tests** - use `t.Run()` with test cases instead of duplicate test functions @@ -24,6 +26,7 @@ - ✅ **Use `t.Cleanup()` properly** - the `TestServerSetup` pattern automatically handles cleanup via `t.Cleanup()` ### Frontend & Styling + - ❌ **NEVER modify backend/API for frontend features without user confirmation** - ❌ **NEVER use custom CSS** - TailwindCSS classes only - **⚠️ EXCEPTION**: `templates/error.templ` may have inline CSS because error pages must work when main app fails (404, server errors, CSS fails to load) @@ -37,9 +40,10 @@ - ❌ **NEVER fetch initial data via AJAX on page load** - use server-side rendering instead - ❌ **NEVER break progressive enhancement** - pages must work without JavaScript -**Note:** Go methods in the backend are fine and encouraged. This guideline applies to TypeScript/JavaScript frontend code only.** +**Note:** Go methods in the backend are fine and encouraged. This guideline applies to TypeScript/JavaScript frontend code only.\*\* ### General + - ❌ **NEVER skip pre-commit hooks** unless explicitly requested - ❌ **NEVER force push to main/master** branches - ❌ **NEVER commit files with secrets** (.env, credentials.json, etc.) @@ -60,6 +64,7 @@ ### Cascading Fix-up Pattern (PROHIBITED) **WHAT NOT TO DO** - This caused critical bugs: + ```go // ❌ WRONG: Blindly making fixes after compilation error @@ -74,6 +79,7 @@ Edit 3: Try to fix again (worse damage) ``` **CORRECT APPROACH**: + ```go // ✅ CORRECT: Stop, understand, then fix deliberately @@ -86,6 +92,7 @@ VERIFY → Compile successfully ``` **Key Principle**: When compilation errors occur after edits: + 1. STOP - Don't make more edits 2. ANALYZE - Use `git diff` to understand what was changed 3. RECOVER - Restore what was accidentally deleted/broken @@ -96,6 +103,7 @@ VERIFY → Compile successfully ## 🎯 CONTEXT-SPECIFIC RULES ### When Working on Frontend-Only Tasks + - **DO NOT touch backend code** - handlers, services, database layer - **DO NOT modify API routes** - use existing endpoints only - **DO NOT change database schema** - work with existing structure @@ -106,6 +114,7 @@ VERIFY → Compile successfully 4. **ASK FOR USER CONFIRMATION before proceeding** ### When Working on Full-Stack Tasks + - Backend changes are allowed when explicitly part of the task - Still follow all database protocols (atomic changes, validation, etc.) - **If modifying database schema:** Update local database after schema.sql changes (see Database Operations section) @@ -117,6 +126,7 @@ VERIFY → Compile successfully ## ✅ MANDATORY REQUIREMENTS ### Database Operations (Full-Stack Tasks Only) + - ✅ Follow **pgx v5 standards** for all database operations - ✅ Treat schema changes as **ATOMIC** - complete success or complete rejection - ✅ **⚠️ CRITICAL: This is a pre-production application (NO production deployments exist)** @@ -132,16 +142,19 @@ VERIFY → Compile successfully - ✅ **Post-change validation**: ensure schema.sql, models.go, and queries.sql are in sync ### Build & Deployment + - ✅ Use **Podman** exclusively (not Docker) - ✅ All builds through existing **Dockerfile** and **docker-compose.yml** - ✅ Stop building server binaries - everything goes through containers ### API Changes (Full-Stack Tasks Only) + - ✅ Include **Bruno OpenCollection YAML requests** with all API documentation - ✅ Tests must be **comprehensive and cover three contexts**: no user, user, and admin - ✅ Maintain backward compatibility for mobile apps and external consumers ### Frontend & Styling + - ✅ Always use **TailwindCSS classes** for all styling - ✅ Convert all JavaScript to **TypeScript** - ✅ **Never use Object-Oriented Programming** (no classes, inheritance, or this-capture) @@ -154,12 +167,14 @@ VERIFY → Compile successfully - ✅ **Ensure progressive enhancement** - pages work without JavaScript ### Service Layer Architecture + - ✅ **All business logic in services** - never in handlers - ✅ **Services must be reusable** by both SSR handlers and API endpoints - ✅ **Database operations through services only** - never direct from handlers - ✅ **When adding features**: Add service logic → Create API endpoint → Use SSR for initial render → Use JS for updates ### Code Organization + - ✅ Minimize project structure changes - ✅ Place new files in **contextually appropriate directories** - ✅ Follow **KISS**, **DRY**, and **YAGNI** principles @@ -172,10 +187,12 @@ VERIFY → Compile successfully - ❌ **NEVER create conversion helper functions** to map between handler and template types - use handler types directly ### Configuration & Environment + - ✅ If **.env is missing**, auto-generate secure values - ✅ Never commit secrets to repository ### Code Modification Safety + - ✅ **Post-Edit Verification (MANDATORY for ALL file modifications)**: - Run `go build` for affected packages immediately after each edit - Review `git diff filename` to verify only intended changes @@ -194,6 +211,7 @@ VERIFY → Compile successfully ### Documentation **Documentation Structure** (updated with full docs system): + - ✅ **README.md** - Project overview, quick start, and setup instructions only - ✅ **docs/** - Comprehensive documentation system with search - ✅ **docs/developer/api/** - API reference documentation (split by endpoint/category) @@ -203,19 +221,20 @@ VERIFY → Compile successfully **Where to document changes**: -| Change Type | Location | Examples | -|-------------|----------|----------| -| **User-facing features** | `docs/user/` | New features, UI changes, workflows | -| **API endpoints** | `docs/developer/api//.md` | New endpoints, modified responses, authentication changes | -| **API behavior** | Update existing `docs/developer/api/` files | Parameter changes, error codes, rate limits | -| **Device setup** | `docs/user/devices/` | New device support, setup instructions | -| **Development** | `docs/contributing/` | Build changes, architecture decisions | -| **Quick start/setup** | `README.md` | Installation, environment setup, first-run | -| **Breaking changes** | Both `README.md` and relevant `docs/` | Migration guides, deprecation notices | -| **Bug fixes** | Update relevant `docs/` only if user-visible | Clarifications, troubleshooting additions | -| **Bruno OpenCollection YAML tests** | `.yml` files in bruno folder in appropriate folder/sub-folder | API contract testing, examples | +| Change Type | Location | Examples | +| ----------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------- | +| **User-facing features** | `docs/user/` | New features, UI changes, workflows | +| **API endpoints** | `docs/developer/api//.md` | New endpoints, modified responses, authentication changes | +| **API behavior** | Update existing `docs/developer/api/` files | Parameter changes, error codes, rate limits | +| **Device setup** | `docs/user/devices/` | New device support, setup instructions | +| **Development** | `docs/contributing/` | Build changes, architecture decisions | +| **Quick start/setup** | `README.md` | Installation, environment setup, first-run | +| **Breaking changes** | Both `README.md` and relevant `docs/` | Migration guides, deprecation notices | +| **Bug fixes** | Update relevant `docs/` only if user-visible | Clarifications, troubleshooting additions | +| **Bruno OpenCollection YAML tests** | `.yml` files in bruno folder in appropriate folder/sub-folder | API contract testing, examples | **Documentation Update Workflow**: + 1. **Identify the audience** (end users, developers, API consumers) 2. **Choose appropriate location** based on table above 3. **Update documentation** before or with code changes @@ -225,12 +244,14 @@ VERIFY → Compile successfully 7. **Commit separately** with clear message: `docs: ` **When in doubt**: + - End-user visible → `docs/user/` - API reference → `docs/developer/api/` - Setup/onboarding → `README.md` - Development related → `docs/contributing/` ### Process & Continuity + - ✅ If mid-task and receive "no response", **continue the task** - ✅ Verify no regressions before modifying/removing code @@ -239,18 +260,21 @@ VERIFY → Compile successfully ## 🔧 TECHNICAL STANDARDS ### Backend Stack + - **Language**: Go 1.25+ - **Database**: PostgreSQL 15+ with **pgx v5 driver** only - **Authentication**: JWT tokens with bcrypt password hashing - **Architecture**: Service layer pattern (handlers → services → database) ### Frontend Stack + - **Styling**: TailwindCSS (no custom CSS) - **Language**: TypeScript (no JavaScript) - **Templates**: HTMX with server-side rendering - **Patterns**: Procedural/imperative with functional techniques where helpful (no OOP) ### Containerization + - **Runtime**: Podman (not Docker) - **Build**: Existing Dockerfile and docker-compose.yml only - **No local builds** allowed @@ -262,6 +286,7 @@ VERIFY → Compile successfully When code modification mistakes occur (deleted wrong code, broke compilation, etc.): ### Immediate Actions + 1. **STOP** - Don't make more edits 2. **ASSESS** - What was deleted? Is it critical? 3. **REVIEW** - Run `git diff` to see exact changes @@ -273,6 +298,7 @@ When code modification mistakes occur (deleted wrong code, broke compilation, et 6. **DOCUMENT** - Note what went wrong for future reference ### Recovery Examples + ```bash # Recover a deleted function from original file git show HEAD:internal/handlers/auth.go | sed -n '70,275p' > recovery.txt @@ -286,6 +312,7 @@ git checkout -- internal/handlers/auth.go ``` ### Prevention (Learn From Mistakes) + - Why did the mistake happen? - Was it too-broad matching? - Was it insufficient context reading? @@ -297,6 +324,7 @@ git checkout -- internal/handlers/auth.go ## 📋 WORKFLOW CHECKLISTS ### Before Making Frontend-Only Changes + - [ ] Identify if backend modification could make implementation simpler - [ ] Plan to use existing API endpoints only - [ ] If backend change seems necessary, prepare confirmation request: @@ -311,6 +339,7 @@ git checkout -- internal/handlers/auth.go - [ ] Setup instructions → `README.md` ### Before Making Full-Stack Changes + - [ ] Read current schema completely (if database changes) - [ ] Identify all columns that must be preserved - [ ] Plan exact changes needed @@ -324,6 +353,7 @@ git checkout -- internal/handlers/auth.go - [ ] Bruno OpenCollection YAML `.yml` files → Update/create alongside API changes ### During Schema Changes (Full-Stack Only) + - [ ] Read current schema completely - [ ] Identify all columns that must be preserved - [ ] Plan exact changes needed @@ -345,6 +375,7 @@ git checkout -- internal/handlers/auth.go - [ ] Verify database has new schema (check column types, indexes, etc.) ### After API Changes + - [ ] Create/update Bruno OpenCollection YAML requests - [ ] Test with no user context - [ ] Test with regular user context @@ -352,6 +383,7 @@ git checkout -- internal/handlers/auth.go - [ ] Verify backward compatibility ### Before Committing + - [ ] **Run verification script**: `bash scripts/verify-guidelines.sh` - [ ] **Fix any errors** - verification must pass (0 errors) to commit - [ ] **Note warnings** - informational only, do not auto-fix @@ -368,6 +400,7 @@ git checkout -- internal/handlers/auth.go - [ ] **Test docs search** finds new content ### Error Recovery Protocol (If Code Mistakes Occur) + - [ ] **Stop immediately** - don't make more edits - [ ] **Assess impact**: What was deleted? Is it critical? - [ ] **Review git diff**: See exact changes made @@ -378,6 +411,7 @@ git checkout -- internal/handlers/auth.go - [ ] **Document mistake**: Note what went wrong for future reference ### Phase Completion Verification (Before Declaring "Complete") + - [ ] All target code is removed/intact as intended - [ ] No unintended code was deleted - [ ] All affected files compile successfully @@ -392,6 +426,7 @@ git checkout -- internal/handlers/auth.go ## 🏗 ARCHITECTURAL PATTERNS ### Current: Hybrid SSR + ``` Browser → Go template (with data) → Display instantly ↓ diff --git a/README.md b/README.md index 74af2cb..7509cd4 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ A modern self-hosted media library system built with Go, PostgreSQL, HTMX, and T ## 🚀 Quick Start ### Prerequisites + - **Podman** (recommended) or Docker - **5 minutes** of your time @@ -48,6 +49,7 @@ The first user to register automatically becomes an admin. ## 📖 Key Features ### Universal Cross-Platform Sync + - **Real-Time Progress**: Turn a page on your Kindle, see it on your phone - **Format-Aware**: EPUB CFI, page numbers, percentages - all handled correctly - **Offline Queue**: Changes sync when you reconnect, priority-processed @@ -57,6 +59,7 @@ The first user to register automatically becomes an admin. - **Format Conversion**: On-the-fly EPUB→KEPUB for Kobo devices ### Media Management + - **Smart Search**: Partial matching with fuzzy search fallback for typos - **Advanced Filtering**: Filter by author, series, genre, language, year, cover images - **Dynamic Sorting**: By title, author, date added, published date, page count, series @@ -66,11 +69,13 @@ The first user to register automatically becomes an admin. - **Usage Analytics**: Reading statistics, device usage, popular books ### Smart Collections + - **Auto-Assign Rules**: Automatically add books based on genre, author, series, tags, language, publisher, year - **Device Shelf Mappings**: Sync collections to Kobo shelves and KOReader categories - **Test Before Creating**: Preview which books match your rules ### Library Organization + - **Multi-Library Support**: Ebooks, Comics, and Manga with type-specific file formats - **Multiple Folders**: Add multiple scanning folders per library - **Visibility Control**: Admins control which libraries each user can see @@ -78,6 +83,7 @@ The first user to register automatically becomes an admin. - **Watch Mode**: Real-time file system monitoring for instant updates ### Security + - **JWT Authentication**: Short-lived access tokens (1 hour) with refresh tokens (7 days) - **Strong Passwords**: Complexity requirements enforced (8+ chars, uppercase, lowercase, number, special) - **Account Lockout**: 5 failed attempts = 15-minute lockout @@ -90,6 +96,7 @@ The first user to register automatically becomes an admin. ## 📚 Documentation ### For Users & Self-Hosters + - **[docs/user/sync-guide.md](docs/user/sync-guide.md)** - Understanding and using universal sync - **[docs/user/devices/kobo-setup.md](docs/user/devices/kobo-setup.md)** - Kobo e-reader configuration - **[docs/user/devices/koreader-setup.md](docs/user/devices/koreader-setup.md)** - KOReader configuration @@ -98,6 +105,7 @@ The first user to register automatically becomes an admin. - **[docs/user/settings-guide.md](docs/user/settings-guide.md)** - Settings and preferences ### For Developers + - **[docs/developer/api/api-reference.md](docs/developer/api/api-reference.md)** - Complete API documentation - **[docs/contributing/DEVELOPMENT.md](docs/contributing/DEVELOPMENT.md)** - Development workflow @@ -105,12 +113,12 @@ The first user to register automatically becomes an admin. ## 🎯 Supported Devices -| Platform | Sync | OPDS | Status | -|----------|------|------|--------| -| **Web Browser** | ✅ | ✅ | Full support | -| **KOReader** | ✅ | ✅ | Kindle, Kobo, PocketBook | -| **Kobo Devices** | ✅ | ✅ | Clara, Libra, Sage, etc. | -| **Mobile Apps** | 🚧 | 🚧 | Coming Q2 2026 | +| Platform | Sync | OPDS | Status | +| ---------------- | ---- | ---- | ------------------------ | +| **Web Browser** | ✅ | ✅ | Full support | +| **KOReader** | ✅ | ✅ | Kindle, Kobo, PocketBook | +| **Kobo Devices** | ✅ | ✅ | Clara, Libra, Sage, etc. | +| **Mobile Apps** | 🚧 | 🚧 | Coming Q2 2026 | --- diff --git a/SCREENSHOT_AUTOMATION.md b/SCREENSHOT_AUTOMATION.md index b223003..1ffdf87 100644 --- a/SCREENSHOT_AUTOMATION.md +++ b/SCREENSHOT_AUTOMATION.md @@ -8,6 +8,7 @@ This document outlines the complete plan for automatically generating screenshot ## Overview Playwright will be used to: + 1. Navigate the running Bookhoard server 2. Perform key user/admin workflows 3. Capture screenshots at each step @@ -17,6 +18,7 @@ Playwright will be used to: ## Prerequisites (To Verify When Ready) ### Frontend Pages Complete + Verify these pages are fully functional before starting: - [ ] `/` - Login page @@ -34,6 +36,7 @@ Verify these pages are fully functional before starting: - [ ] `/api-explorer` - API testing interface ### Test Environment Ready + - [ ] Bookhoard server running on `http://localhost:8765` - [ ] Test database seeded with sample data (books, collections, devices) - [ ] Test admin account ready (username, password, role=admin) @@ -49,6 +52,7 @@ npx playwright install chromium ``` Project structure after setup: + ``` bookhoard/ ├── docs/ @@ -93,133 +97,151 @@ USER_PASSWORD=SecureUserPass456! Before running the screenshot automation, ask the user: ### 1. Server Access + **Q**: Where is the Bookhoard server running? + - [ ] `localhost:8765` (default) - [ ] Custom port: `________` - [ ] Remote URL: `________` ### 2. Test Credentials + **Q**: What credentials should Playwright use? **Admin Account** (for admin guide screenshots): + - Username: `________` - Password: `________` **Regular User Account** (for user guide screenshots): + - Username: `________` - Password: `________` ### 3. Screenshot Format + **Q**: What format for screenshots? + - [ ] **WebP** (recommended - modern, good compression) - [ ] PNG (highest quality, larger files) - [ ] JPEG (smaller files, compression artifacts) ### 4. Screenshot Dimensions + **Q**: What viewport sizes for screenshots? + - [ ] **Desktop**: 1920x1080 (full-width screenshots) - [ ] **Tablet**: 768x1024 (responsive documentation) - [ ] **Mobile**: 375x667 (mobile documentation) - [ ] All three sizes (comprehensive coverage) ### 5. Theme + **Q**: What theme should screenshots use? + - [ ] **Default** (Tokyo Night theme as seen in templates) - [ ] Light theme (if implemented) - [ ] Multiple themes (document theme switching) ### 6. Language + **Q**: What language/region for the UI? + - [ ] **English** (default) - [ ] Other: `________` ## Screenshot Plan by Documentation Section ### 1. User Guide Screenshots + **File**: `docs/user/user-guide.md` **Screenshots Needed**: -| Screenshot Name | Description | Page/Action | -|-----------------|-------------|-------------| -| `login-page.webp` | Login form with filled credentials | `/` - Login page | -| `dashboard-overview.webp` | Main dashboard showing libraries | `/dashboard` | -| `library-grid.webp` | Media items grid view | `/dashboard` → Click library | -| `book-detail.webp` | Book detail view with metadata | `/dashboard` → Click book | -| `search-results.webp` | Search in action | `/dashboard` → Type in search | -| `filter-panel.webp` | Filter options expanded | `/dashboard` → Open filters | -| `collections-list.webp` | Collections grid view | `/collections` | -| `create-collection.webp` | New collection modal | `/collections` → Click "New Collection" | -| `reading-progress.webp` | Progress tracking view | `/progress` | -| `analytics-view.webp` | User analytics dashboard | `/analytics` | +| Screenshot Name | Description | Page/Action | +| ------------------------- | ---------------------------------- | --------------------------------------- | +| `login-page.webp` | Login form with filled credentials | `/` - Login page | +| `dashboard-overview.webp` | Main dashboard showing libraries | `/dashboard` | +| `library-grid.webp` | Media items grid view | `/dashboard` → Click library | +| `book-detail.webp` | Book detail view with metadata | `/dashboard` → Click book | +| `search-results.webp` | Search in action | `/dashboard` → Type in search | +| `filter-panel.webp` | Filter options expanded | `/dashboard` → Open filters | +| `collections-list.webp` | Collections grid view | `/collections` | +| `create-collection.webp` | New collection modal | `/collections` → Click "New Collection" | +| `reading-progress.webp` | Progress tracking view | `/progress` | +| `analytics-view.webp` | User analytics dashboard | `/analytics` | **Estimated Screenshots**: ~10 ### 2. Admin Guide Screenshots + **File**: `docs/user/admin-guide.md` **Screenshots Needed**: -| Screenshot Name | Description | Page/Action | -|-----------------|-------------|-------------| -| `admin-dashboard.webp` | Admin overview panel | `/admin` | -| `user-management.webp` | User list with actions | `/admin` → Users section | -| `add-user.webp` | Add new user form | `/admin` → Click "Add User" | -| `library-settings.webp` | Library configuration | `/admin/library` | -| `add-library.webp` | Create new library form | `/admin/library` → Click "Add Library" | -| `analytics-admin.webp` | Admin analytics view | `/analytics` (admin view) | -| `sync-queue.webp` | Sync queue monitoring | `/queue` | -| `theme-settings.webp` | Theme selection interface | `/admin/profile` → Theme section | -| `user-profile-edit.webp` | Edit user profile | `/admin/profile` | +| Screenshot Name | Description | Page/Action | +| ------------------------ | ------------------------- | -------------------------------------- | +| `admin-dashboard.webp` | Admin overview panel | `/admin` | +| `user-management.webp` | User list with actions | `/admin` → Users section | +| `add-user.webp` | Add new user form | `/admin` → Click "Add User" | +| `library-settings.webp` | Library configuration | `/admin/library` | +| `add-library.webp` | Create new library form | `/admin/library` → Click "Add Library" | +| `analytics-admin.webp` | Admin analytics view | `/analytics` (admin view) | +| `sync-queue.webp` | Sync queue monitoring | `/queue` | +| `theme-settings.webp` | Theme selection interface | `/admin/profile` → Theme section | +| `user-profile-edit.webp` | Edit user profile | `/admin/profile` | **Estimated Screenshots**: ~9 ### 3. Device Setup Screenshots + **File**: `docs/user/devices/kobo-setup.md` and `koreader-setup.md` **Screenshots Needed**: -| Screenshot Name | Description | Page/Action | -|-----------------|-------------|-------------| -| `device-list.webp` | Device management page | `/devices` | -| `add-device-modal.webp` | Add new device modal | `/devices` → Click "Add New Device" | -| `device-form-kobo.webp` | Kobo device registration form | `/devices` → Select Kobo type | -| `device-form-koreader.webp` | KOReader device registration form | `/devices` → Select KOReader type | -| `device-qr-code.webp` | QR code for device approval | After device registration | -| `device-approved.webp` | Device approved confirmation | After approving device | -| `device-sync-settings.webp` | Sync configuration for device | `/devices` → Click device settings | -| `shelf-mapping.webp` | Collection to shelf mapping | `/devices` → Click shelf mapping | -| `sync-queue-item.webp` | Device sync in queue | `/queue` (device specific) | +| Screenshot Name | Description | Page/Action | +| --------------------------- | --------------------------------- | ----------------------------------- | +| `device-list.webp` | Device management page | `/devices` | +| `add-device-modal.webp` | Add new device modal | `/devices` → Click "Add New Device" | +| `device-form-kobo.webp` | Kobo device registration form | `/devices` → Select Kobo type | +| `device-form-koreader.webp` | KOReader device registration form | `/devices` → Select KOReader type | +| `device-qr-code.webp` | QR code for device approval | After device registration | +| `device-approved.webp` | Device approved confirmation | After approving device | +| `device-sync-settings.webp` | Sync configuration for device | `/devices` → Click device settings | +| `shelf-mapping.webp` | Collection to shelf mapping | `/devices` → Click shelf mapping | +| `sync-queue-item.webp` | Device sync in queue | `/queue` (device specific) | **Estimated Screenshots**: ~9 ### 4. Sync Guide Screenshots + **File**: `docs/user/sync-guide.md` **Screenshots Needed**: -| Screenshot Name | Description | Page/Action | -|-----------------|-------------|-------------| -| `sync-conflicts.webp` | Conflicts list view | `/conflicts` | -| `conflict-resolution.webp` | Resolve conflict dialog | `/conflicts` → Click resolve | -| `unlinked-books.webp` | Unlinked books list | `/unlinked-books` | -| `book-linking.webp` | Link book to metadata | `/unlinked-books` → Click link | -| `sync-success.webp` | Successful sync indicator | Any page after sync | +| Screenshot Name | Description | Page/Action | +| -------------------------- | ------------------------- | ------------------------------ | +| `sync-conflicts.webp` | Conflicts list view | `/conflicts` | +| `conflict-resolution.webp` | Resolve conflict dialog | `/conflicts` → Click resolve | +| `unlinked-books.webp` | Unlinked books list | `/unlinked-books` | +| `book-linking.webp` | Link book to metadata | `/unlinked-books` → Click link | +| `sync-success.webp` | Successful sync indicator | Any page after sync | **Estimated Screenshots**: ~5 ### 5. Settings Guide Screenshots + **File**: `docs/user/settings-guide.md` **Screenshots Needed**: -| Screenshot Name | Description | Page/Action | -|-----------------|-------------|-------------| -| `profile-overview.webp` | Profile settings page | `/admin/profile` | -| `update-username.webp` | Username change form | `/admin/profile` | -| `update-email.webp` | Email change form | `/admin/profile` | -| `change-password.webp` | Password change form | `/admin/profile` | -| `theme-selector.webp` | Theme selection dropdown | `/admin/profile` (if implemented) | +| Screenshot Name | Description | Page/Action | +| ----------------------- | ------------------------ | --------------------------------- | +| `profile-overview.webp` | Profile settings page | `/admin/profile` | +| `update-username.webp` | Username change form | `/admin/profile` | +| `update-email.webp` | Email change form | `/admin/profile` | +| `change-password.webp` | Password change form | `/admin/profile` | +| `theme-selector.webp` | Theme selection dropdown | `/admin/profile` (if implemented) | **Estimated Screenshots**: ~5 @@ -228,24 +250,24 @@ Before running the screenshot automation, ask the user: ### `screenshots/config.ts` - Playwright Configuration ```typescript -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ - testDir: './', + testDir: "./", fullyParallel: false, retries: 1, - reporter: 'list', + reporter: "list", use: { - baseURL: process.env.BASE_URL || 'http://localhost:8765', - trace: 'on-first-retry', - screenshot: 'only-on-failure', + baseURL: process.env.BASE_URL || "http://localhost:8765", + trace: "on-first-retry", + screenshot: "only-on-failure", }, projects: [ { - name: 'chromium-desktop', - use: { - ...devices['Desktop Chrome'], - viewport: { width: 1920, height: 1080 } + name: "chromium-desktop", + use: { + ...devices["Desktop Chrome"], + viewport: { width: 1920, height: 1080 }, }, }, ], @@ -255,70 +277,79 @@ export default defineConfig({ ### `screenshots/auth.spec.ts` - Authentication Screenshots ```typescript -import { test, expect } from '@playwright/test'; +import { test, expect } from "@playwright/test"; -test.describe('Authentication Screenshots', () => { - test('Login page', async ({ page }) => { - await page.goto('/'); - - await page.screenshot({ - path: 'docs/images/user/login-page.webp', - fullPage: true +test.describe("Authentication Screenshots", () => { + test("Login page", async ({ page }) => { + await page.goto("/"); + + await page.screenshot({ + path: "docs/images/user/login-page.webp", + fullPage: true, }); }); - test('User login flow', async ({ page }) => { - await page.goto('/'); - - await page.fill('input[name="login"]', process.env.USER_USERNAME || 'user'); - await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password'); - - await page.screenshot({ - path: 'docs/images/user/login-form-filled.webp', - fullPage: true + test("User login flow", async ({ page }) => { + await page.goto("/"); + + await page.fill('input[name="login"]', process.env.USER_USERNAME || "user"); + await page.fill( + 'input[name="password"]', + process.env.USER_PASSWORD || "password", + ); + + await page.screenshot({ + path: "docs/images/user/login-form-filled.webp", + fullPage: true, }); - + await page.click('button[type="submit"]'); - await page.waitForURL('/dashboard', { timeout: 5000 }); - - await page.screenshot({ - path: 'docs/images/user/dashboard-after-login.webp', - fullPage: true + await page.waitForURL("/dashboard", { timeout: 5000 }); + + await page.screenshot({ + path: "docs/images/user/dashboard-after-login.webp", + fullPage: true, }); }); - test('Admin login flow', async ({ page }) => { - await page.goto('/'); - - await page.fill('input[name="login"]', process.env.ADMIN_USERNAME || 'admin'); - await page.fill('input[name="password"]', process.env.ADMIN_PASSWORD || 'password'); - + test("Admin login flow", async ({ page }) => { + await page.goto("/"); + + await page.fill( + 'input[name="login"]', + process.env.ADMIN_USERNAME || "admin", + ); + await page.fill( + 'input[name="password"]', + process.env.ADMIN_PASSWORD || "password", + ); + await page.click('button[type="submit"]'); - await page.waitForURL('/dashboard', { timeout: 5000 }); - - await page.goto('/admin'); - - await page.screenshot({ - path: 'docs/images/admin/admin-dashboard.webp', - fullPage: true + await page.waitForURL("/dashboard", { timeout: 5000 }); + + await page.goto("/admin"); + + await page.screenshot({ + path: "docs/images/admin/admin-dashboard.webp", + fullPage: true, }); }); - test('Registration page', async ({ page }) => { - await page.goto('/register'); - - await page.screenshot({ - path: 'docs/images/user/registration-page.webp', - fullPage: true + test("Registration page", async ({ page }) => { + await page.goto("/register"); + + await page.screenshot({ + path: "docs/images/user/registration-page.webp", + fullPage: true, }); - - await page.fill('input[name="email"]', 'newuser@example.com'); - await page.fill('input[name="username"]', 'newuser'); - await page.fill('input[name="password"]', 'SecurePass123!'); - - await page.screenshot({ - path: 'docs/images/user/registration-form-filled.webp', - fullPage: true + + await page.fill('input[name="email"]', "newuser@example.com"); + await page.fill('input[name="username"]', "newuser"); + await page.fill('input[name="password"]', "SecurePass123!"); + + await page.screenshot({ + path: "docs/images/user/registration-form-filled.webp", + fullPage: true, }); }); }); @@ -327,112 +358,120 @@ test.describe('Authentication Screenshots', () => { ### `screenshots/user-workflows.spec.ts` - User Guide Screenshots ```typescript -import { test, expect } from '@playwright/test'; +import { test, expect } from "@playwright/test"; -test.describe('User Guide Screenshots', () => { +test.describe("User Guide Screenshots", () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); - await page.fill('input[name="login"]', process.env.USER_USERNAME || 'user'); - await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password'); + await page.goto("/"); + await page.fill('input[name="login"]', process.env.USER_USERNAME || "user"); + await page.fill( + 'input[name="password"]', + process.env.USER_PASSWORD || "password", + ); await page.click('button[type="submit"]'); - await page.waitForURL('/dashboard', { timeout: 5000 }); + await page.waitForURL("/dashboard", { timeout: 5000 }); }); - test('Dashboard overview', async ({ page }) => { - await page.goto('/dashboard'); - - await page.screenshot({ - path: 'docs/images/user/dashboard-overview.webp', - fullPage: true + test("Dashboard overview", async ({ page }) => { + await page.goto("/dashboard"); + + await page.screenshot({ + path: "docs/images/user/dashboard-overview.webp", + fullPage: true, }); }); - test('Library grid view', async ({ page }) => { - await page.goto('/dashboard'); - - await page.waitForSelector('#libraries-container', { timeout: 5000 }); - - const firstLibrary = page.locator('[data-library]').first(); + test("Library grid view", async ({ page }) => { + await page.goto("/dashboard"); + + await page.waitForSelector("#libraries-container", { timeout: 5000 }); + + const firstLibrary = page.locator("[data-library]").first(); if (await firstLibrary.isVisible()) { await firstLibrary.click(); await page.waitForURL(/\/dashboard/, { timeout: 5000 }); - - await page.screenshot({ - path: 'docs/images/user/library-grid.webp', - fullPage: true + + await page.screenshot({ + path: "docs/images/user/library-grid.webp", + fullPage: true, }); } }); - test('Search functionality', async ({ page }) => { - await page.goto('/dashboard'); - - await page.waitForSelector('#search-input', { timeout: 5000 }); - - await page.fill('#search-input', 'science'); + test("Search functionality", async ({ page }) => { + await page.goto("/dashboard"); + + await page.waitForSelector("#search-input", { timeout: 5000 }); + + await page.fill("#search-input", "science"); await page.waitForTimeout(1000); - - await page.screenshot({ - path: 'docs/images/user/search-results.webp', - fullPage: true + + await page.screenshot({ + path: "docs/images/user/search-results.webp", + fullPage: true, }); }); - test('Filter panel', async ({ page }) => { - await page.goto('/dashboard'); - - const filterPanel = page.locator('.filter-panel details'); + test("Filter panel", async ({ page }) => { + await page.goto("/dashboard"); + + const filterPanel = page.locator(".filter-panel details"); if (await filterPanel.isVisible()) { await filterPanel.click(); await page.waitForTimeout(500); - - await page.screenshot({ - path: 'docs/images/user/filter-panel-open.webp', - fullPage: true + + await page.screenshot({ + path: "docs/images/user/filter-panel-open.webp", + fullPage: true, }); } }); - test('Collections list', async ({ page }) => { - await page.goto('/collections'); - - await page.screenshot({ - path: 'docs/images/user/collections-list.webp', - fullPage: true + test("Collections list", async ({ page }) => { + await page.goto("/collections"); + + await page.screenshot({ + path: "docs/images/user/collections-list.webp", + fullPage: true, }); }); - test('Create collection modal', async ({ page }) => { - await page.goto('/collections'); - - await page.click('button:has-text("New Collection"), button:has-text("Create Your First Collection")'); - - await page.waitForSelector('#create-modal', { state: 'visible', timeout: 5000 }); - - await page.fill('#collection-name', 'My Reading List'); - await page.fill('#collection-description', 'Books I want to read'); - - await page.screenshot({ - path: 'docs/images/user/create-collection-modal.webp', - fullPage: true + test("Create collection modal", async ({ page }) => { + await page.goto("/collections"); + + await page.click( + 'button:has-text("New Collection"), button:has-text("Create Your First Collection")', + ); + + await page.waitForSelector("#create-modal", { + state: "visible", + timeout: 5000, + }); + + await page.fill("#collection-name", "My Reading List"); + await page.fill("#collection-description", "Books I want to read"); + + await page.screenshot({ + path: "docs/images/user/create-collection-modal.webp", + fullPage: true, }); }); - test('Reading progress view', async ({ page }) => { - await page.goto('/progress'); - - await page.screenshot({ - path: 'docs/images/user/reading-progress.webp', - fullPage: true + test("Reading progress view", async ({ page }) => { + await page.goto("/progress"); + + await page.screenshot({ + path: "docs/images/user/reading-progress.webp", + fullPage: true, }); }); - test('Analytics view', async ({ page }) => { - await page.goto('/analytics'); - - await page.screenshot({ - path: 'docs/images/user/analytics-view.webp', - fullPage: true + test("Analytics view", async ({ page }) => { + await page.goto("/analytics"); + + await page.screenshot({ + path: "docs/images/user/analytics-view.webp", + fullPage: true, }); }); }); @@ -441,82 +480,92 @@ test.describe('User Guide Screenshots', () => { ### `screenshots/admin-workflows.spec.ts` - Admin Guide Screenshots ```typescript -import { test, expect } from '@playwright/test'; +import { test, expect } from "@playwright/test"; -test.describe('Admin Guide Screenshots', () => { +test.describe("Admin Guide Screenshots", () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); - await page.fill('input[name="login"]', process.env.ADMIN_USERNAME || 'admin'); - await page.fill('input[name="password"]', process.env.ADMIN_PASSWORD || 'password'); + await page.goto("/"); + await page.fill( + 'input[name="login"]', + process.env.ADMIN_USERNAME || "admin", + ); + await page.fill( + 'input[name="password"]', + process.env.ADMIN_PASSWORD || "password", + ); await page.click('button[type="submit"]'); - await page.waitForURL('/dashboard', { timeout: 5000 }); + await page.waitForURL("/dashboard", { timeout: 5000 }); }); - test('Admin dashboard', async ({ page }) => { - await page.goto('/admin'); - - await page.screenshot({ - path: 'docs/images/admin/admin-dashboard.webp', - fullPage: true + test("Admin dashboard", async ({ page }) => { + await page.goto("/admin"); + + await page.screenshot({ + path: "docs/images/admin/admin-dashboard.webp", + fullPage: true, }); }); - test('User management', async ({ page }) => { - await page.goto('/admin'); - + test("User management", async ({ page }) => { + await page.goto("/admin"); + const userSection = page.locator('[data-section="users"], text="Users"'); if (await userSection.isVisible()) { - await page.screenshot({ - path: 'docs/images/admin/user-management.webp', - fullPage: true + await page.screenshot({ + path: "docs/images/admin/user-management.webp", + fullPage: true, }); } }); - test('Library management', async ({ page }) => { - await page.goto('/admin/library'); - - await page.screenshot({ - path: 'docs/images/admin/library-management.webp', - fullPage: true + test("Library management", async ({ page }) => { + await page.goto("/admin/library"); + + await page.screenshot({ + path: "docs/images/admin/library-management.webp", + fullPage: true, }); }); - test('Add library form', async ({ page }) => { - await page.goto('/admin/library'); - - const addLibraryBtn = page.locator('button:has-text("Add Library"), button:has-text("Create Library")'); + test("Add library form", async ({ page }) => { + await page.goto("/admin/library"); + + const addLibraryBtn = page.locator( + 'button:has-text("Add Library"), button:has-text("Create Library")', + ); if (await addLibraryBtn.isVisible()) { await addLibraryBtn.click(); await page.waitForTimeout(500); - - await page.screenshot({ - path: 'docs/images/admin/add-library-form.webp', - fullPage: true + + await page.screenshot({ + path: "docs/images/admin/add-library-form.webp", + fullPage: true, }); } }); - test('Profile settings', async ({ page }) => { - await page.goto('/admin/profile'); - - await page.screenshot({ - path: 'docs/images/admin/profile-settings.webp', - fullPage: true + test("Profile settings", async ({ page }) => { + await page.goto("/admin/profile"); + + await page.screenshot({ + path: "docs/images/admin/profile-settings.webp", + fullPage: true, }); }); - test('Theme selector', async ({ page }) => { - await page.goto('/admin/profile'); - - const themeSelect = page.locator('select[name="theme"], [data-theme-selector]'); + test("Theme selector", async ({ page }) => { + await page.goto("/admin/profile"); + + const themeSelect = page.locator( + 'select[name="theme"], [data-theme-selector]', + ); if (await themeSelect.isVisible()) { await themeSelect.click(); await page.waitForTimeout(500); - - await page.screenshot({ - path: 'docs/images/admin/theme-selector.webp', - fullPage: true + + await page.screenshot({ + path: "docs/images/admin/theme-selector.webp", + fullPage: true, }); } }); @@ -526,83 +575,97 @@ test.describe('Admin Guide Screenshots', () => { ### `screenshots/device-workflows.spec.ts` - Device Setup Screenshots ```typescript -import { test, expect } from '@playwright/test'; +import { test, expect } from "@playwright/test"; -test.describe('Device Setup Screenshots', () => { +test.describe("Device Setup Screenshots", () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); - await page.fill('input[name="login"]', process.env.USER_USERNAME || 'user'); - await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password'); + await page.goto("/"); + await page.fill('input[name="login"]', process.env.USER_USERNAME || "user"); + await page.fill( + 'input[name="password"]', + process.env.USER_PASSWORD || "password", + ); await page.click('button[type="submit"]'); - await page.waitForURL('/dashboard', { timeout: 5000 }); + await page.waitForURL("/dashboard", { timeout: 5000 }); }); - test('Device list page', async ({ page }) => { - await page.goto('/devices'); - - await page.screenshot({ - path: 'docs/images/devices/device-list.webp', - fullPage: true + test("Device list page", async ({ page }) => { + await page.goto("/devices"); + + await page.screenshot({ + path: "docs/images/devices/device-list.webp", + fullPage: true, }); }); - test('Add device modal', async ({ page }) => { - await page.goto('/devices'); - - await page.click('button:has-text("Add New Device"), button:has-text("Add Your First Device")'); - - await page.waitForSelector('[data-modal="add-device"], #add-device-modal', { state: 'visible', timeout: 5000 }); - - await page.screenshot({ - path: 'docs/images/devices/add-device-modal.webp', - fullPage: true + test("Add device modal", async ({ page }) => { + await page.goto("/devices"); + + await page.click( + 'button:has-text("Add New Device"), button:has-text("Add Your First Device")', + ); + + await page.waitForSelector('[data-modal="add-device"], #add-device-modal', { + state: "visible", + timeout: 5000, + }); + + await page.screenshot({ + path: "docs/images/devices/add-device-modal.webp", + fullPage: true, }); }); - test('Kobo device form', async ({ page }) => { - await page.goto('/devices'); - + test("Kobo device form", async ({ page }) => { + await page.goto("/devices"); + await page.click('button:has-text("Add New Device")'); - - await page.waitForSelector('[data-modal="add-device"]', { state: 'visible', timeout: 5000 }); - + + await page.waitForSelector('[data-modal="add-device"]', { + state: "visible", + timeout: 5000, + }); + const deviceTypeSelect = page.locator('select[name="device_type"]'); if (await deviceTypeSelect.isVisible()) { - await deviceTypeSelect.selectOption('kobo'); + await deviceTypeSelect.selectOption("kobo"); await page.waitForTimeout(500); - - await page.screenshot({ - path: 'docs/images/devices/device-form-kobo.webp', - fullPage: true + + await page.screenshot({ + path: "docs/images/devices/device-form-kobo.webp", + fullPage: true, }); } }); - test('KOReader device form', async ({ page }) => { - await page.goto('/devices'); - + test("KOReader device form", async ({ page }) => { + await page.goto("/devices"); + await page.click('button:has-text("Add New Device")'); - - await page.waitForSelector('[data-modal="add-device"]', { state: 'visible', timeout: 5000 }); - + + await page.waitForSelector('[data-modal="add-device"]', { + state: "visible", + timeout: 5000, + }); + const deviceTypeSelect = page.locator('select[name="device_type"]'); if (await deviceTypeSelect.isVisible()) { - await deviceTypeSelect.selectOption('koreader'); + await deviceTypeSelect.selectOption("koreader"); await page.waitForTimeout(500); - - await page.screenshot({ - path: 'docs/images/devices/device-form-koreader.webp', - fullPage: true + + await page.screenshot({ + path: "docs/images/devices/device-form-koreader.webp", + fullPage: true, }); } }); - test('Sync queue', async ({ page }) => { - await page.goto('/queue'); - - await page.screenshot({ - path: 'docs/images/devices/sync-queue.webp', - fullPage: true + test("Sync queue", async ({ page }) => { + await page.goto("/queue"); + + await page.screenshot({ + path: "docs/images/devices/sync-queue.webp", + fullPage: true, }); }); }); @@ -611,32 +674,35 @@ test.describe('Device Setup Screenshots', () => { ### `screenshots/sync-workflows.spec.ts` - Sync Guide Screenshots ```typescript -import { test, expect } from '@playwright/test'; +import { test, expect } from "@playwright/test"; -test.describe('Sync Guide Screenshots', () => { +test.describe("Sync Guide Screenshots", () => { test.beforeEach(async ({ page }) => { - await page.goto('/'); - await page.fill('input[name="login"]', process.env.USER_USERNAME || 'user'); - await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password'); + await page.goto("/"); + await page.fill('input[name="login"]', process.env.USER_USERNAME || "user"); + await page.fill( + 'input[name="password"]', + process.env.USER_PASSWORD || "password", + ); await page.click('button[type="submit"]'); - await page.waitForURL('/dashboard', { timeout: 5000 }); + await page.waitForURL("/dashboard", { timeout: 5000 }); }); - test('Sync conflicts page', async ({ page }) => { - await page.goto('/conflicts'); - - await page.screenshot({ - path: 'docs/images/sync/sync-conflicts.webp', - fullPage: true + test("Sync conflicts page", async ({ page }) => { + await page.goto("/conflicts"); + + await page.screenshot({ + path: "docs/images/sync/sync-conflicts.webp", + fullPage: true, }); }); - test('Unlinked books page', async ({ page }) => { - await page.goto('/unlinked-books'); - - await page.screenshot({ - path: 'docs/images/sync/unlinked-books.webp', - fullPage: true + test("Unlinked books page", async ({ page }) => { + await page.goto("/unlinked-books"); + + await page.screenshot({ + path: "docs/images/sync/unlinked-books.webp", + fullPage: true, }); }); }); @@ -645,21 +711,25 @@ test.describe('Sync Guide Screenshots', () => { ## Running the Tests ### Run all tests + ```bash npx playwright test ``` ### Run specific test file + ```bash npx playwright test auth.spec.ts ``` ### Run in headed mode (see browser) + ```bash npx playwright test --headed ``` ### Run with debug mode + ```bash npx playwright test --debug ``` @@ -667,10 +737,13 @@ npx playwright test --debug ## Markdown Update Strategy ### Option 1: Create New Markdown + Generate fresh markdown files with embedded screenshots. ### Option 2: Update Existing Markdown + Update existing markdown files by: + 1. Finding section headers 2. Inserting screenshot references after relevant steps 3. Using alt text to describe what's shown @@ -690,25 +763,30 @@ Update existing markdown files by: ## Workflow When Ready ### Step 1: Verify Frontend Complete + - Check all pages listed in "Prerequisites" are working - Confirm no TODO placeholders in templates ### Step 2: Seed Test Data + ```bash # Add sample books, collections, devices # Create test admin and user accounts ``` ### Step 3: Set Up Playwright + ```bash npm install -D @playwright/test npx playwright install chromium ``` ### Step 4: Create Environment File + Create `.env.screenshots` with the test credentials ### Step 5: Run Screenshot Scripts + ```bash # Run all screenshot workflows npx playwright test @@ -719,6 +797,7 @@ npx playwright test user-workflows.spec.ts ``` ### Step 6: Review and Adjust + - Manually review screenshots - Retake any that need adjustment - Update markdown files if needed @@ -742,15 +821,15 @@ When you're ready to run these, you may need to update: ## Estimated Time Investment -| Task | Time | -|------|------| -| Install & configure Playwright | 15 min | -| Seed test database | 30 min | -| Write Playwright scripts | 2-3 hours | -| Run screenshot automation | 10 min | -| Review & retake screenshots | 30-60 min | -| Update markdown files | 30-60 min | -| **Total** | **4-6 hours** | +| Task | Time | +| ------------------------------ | ------------- | +| Install & configure Playwright | 15 min | +| Seed test database | 30 min | +| Write Playwright scripts | 2-3 hours | +| Run screenshot automation | 10 min | +| Review & retake screenshots | 30-60 min | +| Update markdown files | 30-60 min | +| **Total** | **4-6 hours** | ## Future Enhancements diff --git a/TEST_DATA.md b/TEST_DATA.md index 01fbcb7..8b109dd 100644 --- a/TEST_DATA.md +++ b/TEST_DATA.md @@ -5,6 +5,7 @@ This document describes the shared test data used across Go integration tests an ## Test Users ### Main Admin Test User + This is the primary test user used in most integration tests. ```json @@ -19,15 +20,18 @@ This is the primary test user used in most integration tests. ``` **Used in:** + - Go Tests: `cmd/server/tests/test_helpers.go` (getTestUserID, loginTestUser) - Bruno: `user/auth/Login User.yml`, `user/auth/Register User.yml` **Notes:** + - Automatically created if doesn't exist - Deleted and recreated in tests to ensure fresh state - Used for authentication in most test scenarios ### Max Devices Test User + Used specifically for testing device limit functionality. ```json @@ -41,9 +45,11 @@ Used specifically for testing device limit functionality. ``` **Used in:** + - Go Tests: `cmd/server/tests/device_cap_test.go` (createTestUserForMaxDevices) ### Secondary Admin Test User + Used for testing admin creation restrictions and multi-admin scenarios. ```json @@ -58,11 +64,13 @@ Used for testing admin creation restrictions and multi-admin scenarios. ``` **Used in:** + - Bruno: `user/admin/Register Admin User.yml` ## Test Libraries ### Standard Test Library + ```json { "name": "Test Library", @@ -72,10 +80,12 @@ Used for testing admin creation restrictions and multi-admin scenarios. ``` **Used in:** + - Go Tests: `cmd/server/tests/test_helpers.go` (createTestEbookID) - Multiple test files for library management ### Search Test Library + ```json { "name": "Search Test Library", @@ -85,11 +95,13 @@ Used for testing admin creation restrictions and multi-admin scenarios. ``` **Used in:** + - Go Tests: `cmd/server/tests/search_test.go` ## Test Books/Media Items ### Standard Test Ebook + ```json { "title": "Test Ebook", @@ -101,10 +113,13 @@ Used for testing admin creation restrictions and multi-admin scenarios. ``` **Used in:** + - Go Tests: `cmd/server/tests/test_helpers.go` (createTestEbookID) ### Test Book Variants + Multiple test books with different titles for testing: + - "Test Book 1" - "Test Book 2" - "Test Book Title" @@ -113,6 +128,7 @@ Multiple test books with different titles for testing: ## Test Devices Test devices typically follow this pattern: + - Device ID: UUID format - Device Name: "Test Device" or descriptive names - User association: Linked to test users @@ -139,23 +155,28 @@ Test devices typically follow this pattern: ## File Paths ### Container Paths (inside Docker container) + - Uploads: `/app/uploads` - Cache: `/app/cache/kepub` ### Host Paths (when running tests from host) + - Uploads: `./uploads` - Cache: Docker volume (not on host filesystem) ## How to Use This Data ### In Bruno Tests + 1. Start the server: `podman compose up -d` 2. Run "Register User" to create the test admin user 3. Run "Login User" to get the JWT token 4. Use the token for authenticated requests ### In Go Tests + The test helpers automatically create and clean up test data: + ```go ts, db, cfg := setupTestServer(t) token := loginTestUser(t, ts, db) @@ -163,7 +184,9 @@ userID := getTestUserID(t, db) ``` ### Cross-Referencing + When you find a bug in Bruno tests: + 1. Check the same scenario in Go tests using the same credentials 2. Use the same email/password to debug 3. Verify the database state matches expectations @@ -171,6 +194,7 @@ When you find a bug in Bruno tests: ## Resetting Test Data ### Reset Database + ```bash # Stop containers and remove volumes podman compose down -v @@ -180,7 +204,9 @@ podman compose up -d ``` ### Reset Specific Test User + If you need to recreate just the test user: + ```bash # Login to database podman exec -it bookhoard_db psql -U postgres -d bookhoard @@ -202,6 +228,7 @@ DELETE FROM users WHERE email = 'testuser@example.com'; ## Adding New Test Data When adding new test data: + 1. Choose descriptive names following the pattern "Test X" 2. Use consistent email format: `testpurpose@example.com` 3. Document in this file for cross-reference diff --git a/cover_image_serving_plan.md b/cover_image_serving_plan.md index ffa9522..90e9b9a 100644 --- a/cover_image_serving_plan.md +++ b/cover_image_serving_plan.md @@ -3,6 +3,7 @@ ## Overview Fix file and cover image serving to support: + 1. Multiple library folders in docker compose (flexible mount points) 2. Keep files with books (no hardcoded paths) 3. Store relative paths in database (for both files AND covers) @@ -12,12 +13,14 @@ Fix file and cover image serving to support: ## Architecture ### Current Behavior + - File path stored as absolute: `/app/uploads/Jane Austen/Pride and Prejudice/book.epub` - Cover path stored as absolute: `/app/uploads/Jane Austen/Pride and Prejudice/cover.jpg` - Frontend uses path directly - doesn't work (browser can't access container paths) - No route serves `/app/uploads/*` ### Target Behavior + - File path stored as relative: `Jane Austen/Pride and Prejudice/book.epub` - Cover path stored as relative: `Jane Austen/Pride and Prejudice/cover.jpg` - Handler resolves relative path using library folder base path @@ -26,16 +29,21 @@ Fix file and cover image serving to support: - Works with mobile apps, Kobo, KOReader devices via same endpoints ### URL Format + To handle same relative paths in different libraries, use: + ``` /uploads/library-{library_id}/relative/path ``` + - Requires JWT authentication (like API endpoints) - Works for both covers and book files - Single handler handles all file serving ### Universal Path Resolution + All handlers use the same `LibraryService.ResolveMediaPath()` function: + - MediaHandler (downloads) - OPDSHandler (device cover images) - Future handlers @@ -53,16 +61,19 @@ This ensures one source of truth for path resolution. **Location**: In `internal/services/media_scanner.go` - wherever `FilePath` is set in the database insert **Current code** (line 579): + ```go FilePath: path, // path is absolute like /app/uploads/Author/Book/file.epub ``` **New code**: + ```go FilePath: s.getRelativePath(path), ``` **Also update** line 617 for format file paths: + ```go FilePath: pgtype.Text{String: s.getRelativePath(format.FilePath), Valid: true}, ``` @@ -74,6 +85,7 @@ FilePath: pgtype.Text{String: s.getRelativePath(format.FilePath), Valid: tr **Location**: In `internal/services/media_scanner.go` - wherever `metadata.CoverPath` is set **Current code** (example at line 517): + ```go if len(coverImage) > 0 && metadata.CoverPath == "" { coverPath := path + ".cover.jpg" @@ -84,6 +96,7 @@ if len(coverImage) > 0 && metadata.CoverPath == "" { ``` **New code**: + ```go if len(coverImage) > 0 && metadata.CoverPath == "" { coverPath := path + ".cover.jpg" @@ -95,6 +108,7 @@ if len(coverImage) > 0 && metadata.CoverPath == "" { ``` **All locations where metadata.CoverPath is set**: + - Line 517 (main cover) - Line 645 (sidecar cover) - Line 651 (sidecar cover alternative) @@ -187,12 +201,12 @@ func (mh *MediaHandler) getFullFilePath(ctx context.Context, libraryID pgtype.UU if relativePath == "" { return "", fmt.Errorf("no file path") } - + // Check if already absolute (backward compatibility) if filepath.IsAbs(relativePath) { return relativePath, nil } - + // Use service for resolution (one source of truth) return mh.libraryService.ResolveMediaPath(ctx, libraryID, relativePath) } @@ -209,6 +223,7 @@ Note: The handler already has `libraryService` injected, so this just calls thro #### Modify DownloadBook function **Current code** (line 103-144): + ```go func (h *MediaHandler) DownloadBook(c echo.Context) error { // ... @@ -227,6 +242,7 @@ func (h *MediaHandler) DownloadBook(c echo.Context) error { ``` **New code**: + ```go func (h *MediaHandler) DownloadBook(c echo.Context) error { // ... @@ -264,32 +280,32 @@ Create a single handler that serves both covers and book files: func (mh *MediaHandler) ServeFile(c echo.Context) error { // URL format: /uploads/library-{libraryID}/{relativePath} path := c.Param("*") // Gets everything after /uploads/library-{id}/ - + // Extract library ID from path parts := strings.SplitN(path, "/", 2) if len(parts) < 2 { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid path"}) } - + libraryIDStr := strings.TrimPrefix(parts[0], "library-") libraryUUID, err := uuid.Parse(libraryIDStr) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library ID"}) } - + relativePath := parts[1] - + // Resolve using service fullPath, err := mh.getFullFilePath(c.Request().Context(), pgtype.UUID{Bytes: libraryUUID, Valid: true}, relativePath) if err != nil { return c.JSON(http.StatusNotFound, map[string]string{"error": "file not found"}) } - + // Check if file exists if _, err := os.Stat(fullPath); os.IsNotExist(err) { return c.JSON(http.StatusNotFound, map[string]string{"error": "file not found"}) } - + // Determine content type ext := strings.ToLower(filepath.Ext(fullPath)) contentType := "application/octet-stream" @@ -304,7 +320,7 @@ func (mh *MediaHandler) ServeFile(c echo.Context) error { } else if ext == ".pdf" { contentType = "application/pdf" } - + c.Response().Header().Set("Content-Type", contentType) c.Response().Header().Set("Cache-Control", "public, max-age=86400") return c.File(fullPath) @@ -332,16 +348,17 @@ e.GET("/uploads/library-:id/*", createJWTMiddleware(cfg), cfg.MediaHandler.Serve #### Modify GetCoverImage function **Current code** (around line 477-549): + ```go func (h *OPDSHandler) GetCoverImage(c echo.Context) error { // ... coverPath := mediaItem.CoverImagePath.String - + // Check if file exists if _, err := os.Stat(coverPath); os.IsNotExist(err) { return c.NoContent(http.StatusNoContent) } - + // Open file file, err := os.Open(coverPath) // ... @@ -349,22 +366,23 @@ func (h *OPDSHandler) GetCoverImage(c echo.Context) error { ``` **New code**: + ```go func (h *OPDSHandler) GetCoverImage(c echo.Context) error { // ... coverPath := mediaItem.CoverImagePath.String - + // Resolve relative path using library service fullPath, err := h.libraryService.ResolveMediaPath(c.Request().Context(), mediaItem.LibraryID, coverPath) if err != nil { return c.NoContent(http.StatusNoContent) } - + // Check if file exists if _, err := os.Stat(fullPath); os.IsNotExist(err) { return c.NoContent(http.StatusNoContent) } - + // Open file file, err := os.Open(fullPath) // ... @@ -417,7 +435,7 @@ func (mh *MediaHandler) ResolveCoverURL(libraryID pgtype.UUID, coverPath pgtype. if !coverPath.Valid || coverPath.String == "" { return "" } - + return mh.resolveMediaURL(libraryID, coverPath.String) } @@ -426,7 +444,7 @@ func (mh *MediaHandler) ResolveFileURL(libraryID pgtype.UUID, filePath pgtype.Te if !filePath.Valid || filePath.String == "" { return "" } - + return mh.resolveMediaURL(libraryID, filePath.String) } @@ -436,13 +454,13 @@ func (mh *MediaHandler) resolveMediaURL(libraryID pgtype.UUID, relativePath stri if strings.HasPrefix(relativePath, "/uploads/") { return relativePath } - + // Already absolute path? Convert to URL format (backward compatibility) // Note: This loses library ID info, but existing data won't have it if filepath.IsAbs(relativePath) { return relativePath } - + // Resolve relative path to URL format libraryIDStr := libraryID.Bytes.String() return fmt.Sprintf("/uploads/library-%s/%s", libraryIDStr, relativePath) @@ -494,6 +512,7 @@ cfg.CollectionHandler, err = handlers.NewCollectionHandler(cfg.Queries, cfg.Libr **File**: `internal/handlers/collections.go` **Current code** (lines 193-201 in GetCollection function): + ```go bookList := make([]BookInfo, 0, len(books)) for _, book := range books { @@ -507,6 +526,7 @@ for _, book := range books { ``` **New code**: + ```go bookList := make([]BookInfo, 0, len(books)) for _, book := range books { @@ -527,17 +547,17 @@ func (h *CollectionHandler) resolveCoverURL(libraryID pgtype.UUID, coverPath pgt if !coverPath.Valid || coverPath.String == "" { return "" } - + // Already a full URL? Return as-is if strings.HasPrefix(coverPath.String, "/uploads/") { return coverPath.String } - + // Already absolute path? Return as-is (backward compatibility) if filepath.IsAbs(coverPath.String) { return coverPath.String } - + // Resolve relative path to URL format libraryIDStr := libraryID.Bytes.String() return fmt.Sprintf("/uploads/library-%s/%s", libraryIDStr, coverPath.String) @@ -553,6 +573,7 @@ func (h *CollectionHandler) resolveCoverURL(libraryID pgtype.UUID, coverPath pgt **File**: `internal/handlers/collections.go` **Current code** (lines 620-641 in TestRules function): + ```go var matches []BookMatch for _, item := range mediaItems { @@ -579,6 +600,7 @@ for _, item := range mediaItems { ``` **New code**: + ```go var matches []BookMatch for _, item := range mediaItems { @@ -609,6 +631,7 @@ for _, item := range mediaItems { **Location 1 - PreviewCollection function** (lines 910-919): **Current code**: + ```go bookCards := make([]BookInfo, len(matchedItems)) for i, item := range matchedItems { @@ -623,6 +646,7 @@ for i, item := range matchedItems { ``` **New code**: + ```go bookCards := make([]BookInfo, len(matchedItems)) for i, item := range matchedItems { @@ -639,6 +663,7 @@ for i, item := range matchedItems { **Location 2 - mediaItemsToListMediaItemsRow helper** (line 935): **Current code**: + ```go func mediaItemsToListMediaItemsRow(item database.MediaItems) database.ListMediaItemsRow { return database.ListMediaItemsRow{ @@ -650,6 +675,7 @@ func mediaItemsToListMediaItemsRow(item database.MediaItems) database.ListMediaI ``` **New code**: + ```go // NOTE: This helper function doesn't have access to libraryID // Consider refactoring to pass libraryID or handle URL resolution at call site @@ -663,15 +689,15 @@ func (h *CollectionHandler) resolveFileURL(libraryID pgtype.UUID, filePath pgtyp if !filePath.Valid || filePath.String == "" { return "" } - + if strings.HasPrefix(filePath.String, "/uploads/") { return filePath.String } - + if filepath.IsAbs(filePath.String) { return filePath.String } - + libraryIDStr := libraryID.Bytes.String() return fmt.Sprintf("/uploads/library-%s/%s", libraryIDStr, filePath.String) } @@ -691,15 +717,15 @@ func (h *Handler) resolveCoverURL(libraryID pgtype.UUID, coverPath pgtype.Text) if !coverPath.Valid || coverPath.String == "" { return "" } - + if strings.HasPrefix(coverPath.String, "/uploads/") { return coverPath.String } - + if filepath.IsAbs(coverPath.String) { return coverPath.String } - + libraryIDStr := libraryID.Bytes.String() return fmt.Sprintf("/uploads/library-%s/%s", libraryIDStr, coverPath.String) } @@ -708,6 +734,7 @@ func (h *Handler) resolveCoverURL(libraryID pgtype.UUID, coverPath pgtype.Text) **Location 1 - GetAllProgress function** (lines 286-289): **Current code**: + ```go coverPath := "" if mediaItem.CoverImagePath.Valid { @@ -716,6 +743,7 @@ if mediaItem.CoverImagePath.Valid { ``` **New code** (remove the manual resolution, use helper): + ```go coverPath := h.resolveCoverURL(mediaItem.LibraryID, mediaItem.CoverImagePath) ``` @@ -723,6 +751,7 @@ coverPath := h.resolveCoverURL(mediaItem.LibraryID, mediaItem.CoverImagePath) **Location 2 - GetAllProgressData function** (lines 357-360): **Current code**: + ```go coverPath := "" if mediaItem.CoverImagePath.Valid { @@ -731,6 +760,7 @@ if mediaItem.CoverImagePath.Valid { ``` **New code**: + ```go coverPath := h.resolveCoverURL(mediaItem.LibraryID, mediaItem.CoverImagePath) ``` @@ -742,6 +772,7 @@ coverPath := h.resolveCoverURL(mediaItem.LibraryID, mediaItem.CoverImagePath) **File**: `internal/handlers/media.go` Add to imports: + ```go "bookhoard/internal/utils" ``` @@ -749,11 +780,13 @@ Add to imports: **GetMediaItem** - Find where it returns the response (around line 770): **Current code**: + ```go return c.JSON(http.StatusOK, item) ``` **New code**: + ```go return c.JSON(http.StatusOK, map[string]interface{}{ "id": uuid.UUID(item.ID.Bytes).String(), @@ -781,12 +814,14 @@ Wrap each item in the response with resolved URLs. The exact implementation depe **File**: `web/src/bookshelf.ts` **Current code** (line 49-50): + ```typescript ${book.cover_image_path ? `${book.title}` : ``` **New code**: + ```typescript ${book.cover_image_path ? `${book.title}` : @@ -798,32 +833,34 @@ The backend now returns full URLs like `/uploads/library-{id}/path/to/cover.jpg` ### Summary of Changes for Phase 7 -| File | Changes | -|------|---------| -| `internal/utils/mediaurl.go` | Create with `ResolveMediaURL()` function for URL resolution (one source of truth) | -| `internal/handlers/media.go` | Update GetMediaItem and ListMediaItems to use `utils.ResolveMediaURL()` for resolved URLs in responses | +| File | Changes | +| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `internal/utils/mediaurl.go` | Create with `ResolveMediaURL()` function for URL resolution (one source of truth) | +| `internal/handlers/media.go` | Update GetMediaItem and ListMediaItems to use `utils.ResolveMediaURL()` for resolved URLs in responses | | `internal/handlers/collections.go` | Use `utils.ResolveMediaURL()` in GetCollection, TestRules, PreviewCollection; update lines 193-201, 620-641, 910-919 | -| `internal/handlers/progress.go` | Use `utils.ResolveMediaURL()` in GetAllProgress; update lines 286-289, 357-360 | -| `web/src/bookshelf.ts` | Remove `/covers/` prefix from cover image URL | +| `internal/handlers/progress.go` | Use `utils.ResolveMediaURL()` in GetAllProgress; update lines 286-289, 357-360 | +| `web/src/bookshelf.ts` | Remove `/covers/` prefix from cover image URL | --- ### Additional Plan Updates Needed -| Item | Status | -|------|--------| -| Add `mi.library_id` to GetCollectionItems SQL query | Needs to be done before implementing Step 2 in collections.go | -| Create `internal/utils/mediaurl.go` | Needs to be created before implementing URL resolution | -| Update callers to use utils package | Replace h.resolveCoverURL/resolveFileURL with utils.ResolveMediaURL | +| Item | Status | +| --------------------------------------------------- | ------------------------------------------------------------------- | +| Add `mi.library_id` to GetCollectionItems SQL query | Needs to be done before implementing Step 2 in collections.go | +| Create `internal/utils/mediaurl.go` | Needs to be created before implementing URL resolution | +| Update callers to use utils package | Replace h.resolveCoverURL/resolveFileURL with utils.ResolveMediaURL | ## Phase 8: Backward Compatibility Handle existing absolute paths in database: ### Option A: Migration (One-time) + Create a script to convert existing absolute paths to relative paths using known library folder paths. ### Option B: Runtime Resolution (No migration) + Add backward compatibility in handlers: ```go @@ -832,7 +869,7 @@ func (mh *MediaHandler) getFullFilePath(ctx context.Context, libraryID pgtype.UU if filepath.IsAbs(relativePath) { return relativePath, nil } - + // Otherwise resolve as relative path return mh.libraryService.ResolveMediaPath(ctx, libraryID, relativePath) } @@ -894,7 +931,7 @@ func TestGetRelativePath(t *testing.T) { scanner := &MediaScanner{ folders: []string{"/app/uploads", "/var/books"}, } - + tests := []struct { absolute string expected string @@ -903,7 +940,7 @@ func TestGetRelativePath(t *testing.T) { {"/var/books/manga/Naruto/vol1", "manga/Naruto/vol1"}, {"/other/path/file.pdf", "/other/path/file.pdf"}, // fallback } - + for _, tt := range tests { result := scanner.getRelativePath(tt.absolute) assert.Equal(t, tt.expected, result) @@ -949,28 +986,28 @@ info: seq: 1 http: method: GET - url: '{{base_url}}/uploads/library-{{library_id}}/path/to/cover.jpg' + url: "{{base_url}}/uploads/library-{{library_id}}/path/to/cover.jpg" auth: none docs: |- ## Get Cover Image - + Retrieve the cover image for a media item via authenticated static-style URL. Uses JWT authentication (same as API endpoints). - + **Method:** GET - + **Endpoint:** /uploads/library-{id}/{path} - + **Authentication:** Bearer token required - + **Response:** Binary image data (JPEG, PNG, etc.) - + **Status Codes:** - 200: Success - returns image - 401: Unauthorized (missing/invalid JWT) - 404: File not found - + **Note:** The actual path would come from the API response which provides the resolved URL. This test is a template showing the URL format. @@ -989,34 +1026,34 @@ info: seq: 1 http: method: GET - url: '{{base_url}}/uploads/library-{{library_id}}/path/to/book.epub' + url: "{{base_url}}/uploads/library-{{library_id}}/path/to/book.epub" auth: none docs: |- ## Download Media Item - + Download a media item file (EPUB, PDF, CBZ, etc.) via authenticated static-style URL. Uses JWT authentication (same as API endpoints). - + **Method:** GET - + **Endpoint:** /uploads/library-{id}/{path} - + **Authentication:** Bearer token required - + **Path Resolution:** The handler resolves the relative file path stored in the database against the library's configured folder(s) to locate the actual file. - + **Backward Compatibility:** Supports both relative paths (new) and absolute paths (legacy data). - + **Response:** Binary file data with appropriate Content-Type header - + **Status Codes:** - 200: Success - returns file - 401: Unauthorized (missing/invalid JWT) - 404: File not found - + **Note:** The actual path would come from the API response which provides the resolved URL. This test shows the URL format. @@ -1030,7 +1067,7 @@ vars: ### File: `docs/developer/api/media-items/get_cover_image.md` -```markdown +````markdown --- title: Get Cover Image description: Retrieve the cover image for a media item @@ -1046,15 +1083,15 @@ Retrieve the cover image for a media item. ## Path Parameters -| Parameter | Type | Description | -|-----------|------|-------------| -| id | string | The media item ID (UUID) | +| Parameter | Type | Description | +| --------- | ------ | ------------------------ | +| id | string | The media item ID (UUID) | ## Headers -| Header | Required | Description | -|--------|----------|-------------| -| Authorization | Yes | Bearer token | +| Header | Required | Description | +| ------------- | -------- | ------------ | +| Authorization | Yes | Bearer token | ## Response @@ -1064,7 +1101,7 @@ Retrieve the cover image for a media item. - **400 Bad Request**: Invalid media item ID -- **404 Not Found**: +- **404 Not Found**: - Media item not found - No cover image configured - Cover image file not found on disk @@ -1076,6 +1113,7 @@ curl -H "Authorization: Bearer YOUR_TOKEN" \ http://localhost:8765/api/covers/550e8400-e29b-41d4-a716-446655440000 \ --output cover.jpg ``` +```` ## Notes @@ -1088,6 +1126,7 @@ curl -H "Authorization: Bearer YOUR_TOKEN" \ ### File: `docs/developer/api/media-items/download_book.md` Update existing documentation to note: + - File paths are stored relative to library folders - Handler resolves path at request time - Backward compatible with existing absolute paths @@ -1096,29 +1135,29 @@ Update existing documentation to note: ## Summary of Changes -| Phase | File | Change | -|-------|------|--------| -| Refactor | `internal/handlers/commonhandlers.go` | Move Handler struct from scanner.go (kitchen sink handler for progress/book matching) | -| Refactor | `internal/routers/*.go` | Update NewHandler instantiation if needed | -| 1 | `internal/services/media_scanner.go` | Add `getRelativePath()` function; use for both file_path and cover_path | -| 2 | `internal/services/library_service.go` | Add `ResolveMediaPath()` function (one source of truth) | -| 3 | `internal/handlers/media.go` | Add `getFullFilePath()` helper that calls service | -| 4 | `internal/handlers/media.go` | Modify `DownloadBook` to use `getFullFilePath()` | -| 5 | `internal/handlers/media.go` | Add `ServeFile()` handler for authenticated static-style routes | -| 5 | `internal/router/media.go` | Add route `GET /uploads/library-:id/*` on Echo (not protected group) | -| 6 | `internal/handlers/opds.go` | Add `libraryService` to struct; update `GetCoverImage` to use service | -| 7 | `internal/handlers/collections.go` | Add `libraryService` to struct/constructor; resolve cover paths to URLs in API responses | -| 7 | `internal/router/*.go` | Update CollectionHandler instantiation to pass LibraryService | -| 7 | `internal/handlers/commonhandlers.go` (Handler struct, used by progress.go) | Resolve cover paths to URLs in API responses | -| 7 | `internal/handlers/media.go` | Resolve file paths to URLs in API responses | -| 7 | `web/src/bookshelf.ts` | Remove `/covers/` prefix (use resolved URL directly) | -| 8 | Runtime resolution | Handles both absolute (old) and relative (new) paths | -| 9 | `internal/handlers/media_test.go` | Add unit tests for path resolution | -| 9 | `internal/services/media_scanner_test.go` | Add unit tests for `getRelativePath()` | -| 9 | `cmd/server/tests/cover_file_serving_test.go` | Add integration tests using test_helpers | -| 10 | `bruno/media-items/Get Cover Image.yml` | Add Bruno API test | -| 10 | `bruno/media-items/EPUB Download.yml` | Update to document relative path handling | -| 10 | `docs/developer/api/media-items/` | Update API documentation | +| Phase | File | Change | +| -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| Refactor | `internal/handlers/commonhandlers.go` | Move Handler struct from scanner.go (kitchen sink handler for progress/book matching) | +| Refactor | `internal/routers/*.go` | Update NewHandler instantiation if needed | +| 1 | `internal/services/media_scanner.go` | Add `getRelativePath()` function; use for both file_path and cover_path | +| 2 | `internal/services/library_service.go` | Add `ResolveMediaPath()` function (one source of truth) | +| 3 | `internal/handlers/media.go` | Add `getFullFilePath()` helper that calls service | +| 4 | `internal/handlers/media.go` | Modify `DownloadBook` to use `getFullFilePath()` | +| 5 | `internal/handlers/media.go` | Add `ServeFile()` handler for authenticated static-style routes | +| 5 | `internal/router/media.go` | Add route `GET /uploads/library-:id/*` on Echo (not protected group) | +| 6 | `internal/handlers/opds.go` | Add `libraryService` to struct; update `GetCoverImage` to use service | +| 7 | `internal/handlers/collections.go` | Add `libraryService` to struct/constructor; resolve cover paths to URLs in API responses | +| 7 | `internal/router/*.go` | Update CollectionHandler instantiation to pass LibraryService | +| 7 | `internal/handlers/commonhandlers.go` (Handler struct, used by progress.go) | Resolve cover paths to URLs in API responses | +| 7 | `internal/handlers/media.go` | Resolve file paths to URLs in API responses | +| 7 | `web/src/bookshelf.ts` | Remove `/covers/` prefix (use resolved URL directly) | +| 8 | Runtime resolution | Handles both absolute (old) and relative (new) paths | +| 9 | `internal/handlers/media_test.go` | Add unit tests for path resolution | +| 9 | `internal/services/media_scanner_test.go` | Add unit tests for `getRelativePath()` | +| 9 | `cmd/server/tests/cover_file_serving_test.go` | Add integration tests using test_helpers | +| 10 | `bruno/media-items/Get Cover Image.yml` | Add Bruno API test | +| 10 | `bruno/media-items/EPUB Download.yml` | Update to document relative path handling | +| 10 | `docs/developer/api/media-items/` | Update API documentation | --- @@ -1165,9 +1204,9 @@ Users can configure any mount point in docker-compose: services: bookhoard: volumes: - - ./epubs:/app/epubs # ebooks - - ./manga:/var/manga # manga - - ./comics:/media/comics # comics + - ./epubs:/app/epubs # ebooks + - ./manga:/var/manga # manga + - ./comics:/media/comics # comics ``` The system stores relative paths, so it works with any configuration. diff --git a/docs/FRONTEND_INTEGRATION.md b/docs/FRONTEND_INTEGRATION.md index ac0e16b..35e74d9 100644 --- a/docs/FRONTEND_INTEGRATION.md +++ b/docs/FRONTEND_INTEGRATION.md @@ -6,14 +6,15 @@ The backend implements dual-field normalization for searchability: ### Architecture -| Field Type | Purpose | Behavior | Example | -|-----------|---------|-----------|----------| -| **Display Field** (`tags`, `contributors`) | Show to users | Preserves exact variant, punctuation, proper casing | `"ACME CORP."` | -| **Search Field** (`tags_search`, `contributors_search`) | Search against | Lowercase, no punctuation, deduplicated | `["acme corp"]` | +| Field Type | Purpose | Behavior | Example | +| ------------------------------------------------------- | -------------- | --------------------------------------------------- | --------------- | +| **Display Field** (`tags`, `contributors`) | Show to users | Preserves exact variant, punctuation, proper casing | `"ACME CORP."` | +| **Search Field** (`tags_search`, `contributors_search`) | Search against | Lowercase, no punctuation, deduplicated | `["acme corp"]` | ### Normalization Rules #### Tags + 1. Trim whitespace from each tag 2. Titlecase each tag (preserves hyphenation: "non-fiction" → "Non-Fiction") 3. Case-insensitive deduplication @@ -21,6 +22,7 @@ The backend implements dual-field normalization for searchability: 5. Store both display and search versions #### Contributors + 1. Trim whitespace from each contributor 2. Preserve original casing (including CAPSLOCK companies) 3. Preserve original punctuation for display @@ -31,6 +33,7 @@ The backend implements dual-field normalization for searchability: ### API Request/Response **Request:** + ```json { "tags": ["science-fiction", "ACME CORP.", " O'Reilly Media"], @@ -39,6 +42,7 @@ The backend implements dual-field normalization for searchability: ``` **Response (after normalization):** + ```json { "tags": ["Science-Fiction", "O'Reilly Media"], @@ -51,11 +55,13 @@ The backend implements dual-field normalization for searchability: ### Frontend Implementation Guidelines #### Display + - Use `tags` and `contributors` fields - These preserve exact user input (casing, punctuation) - No transformation needed #### Search + - Use search inputs against `tags_search` and `contributors_search` - Normalize user search input: - Convert to lowercase @@ -63,6 +69,7 @@ The backend implements dual-field normalization for searchability: - Search using `= ANY()` operator #### User Typing "Science-Fiction" + ```typescript // User types exact value const searchValue = "Science-Fiction"; @@ -73,6 +80,7 @@ const searchValue = "Science-Fiction"; ``` #### Search Query Behavior + ```typescript // User searches: "ACME CORP." // Backend normalizes search to: "acme corp" @@ -85,6 +93,7 @@ const searchValue = "Science-Fiction"; When building frontend checkbox filters for contributors/tags: #### Get Unique Values for Dropdown + ```typescript // Fetch distinct normalized values for filters GET /api/contributors?distinct=true @@ -94,6 +103,7 @@ Response: ["acme corp", "oreilly media", "penguin"] ``` #### Filter Query + ```typescript // User selects checkbox const filterValue = "acme corp"; @@ -117,24 +127,28 @@ const filterValue = "acme corp"; ### Common Mistakes to Avoid ❌ **Searching display field directly** + ```typescript // WRONG - Will miss different casing/punctuation WHERE 'ACME CORP.' = ANY(contributors) ``` ✅ **Search search field** + ```typescript // CORRECT - Case-insensitive, punctuation-free WHERE 'acme corp' = ANY(contributors_search) ``` ❌ **Don't normalize user search input** + ```typescript // WRONG - If user types "ACME CORP" explicitly to find exact match const search = "acme corp"; // Changes user's intent ``` ✅ **Use exact user input for search** + ```typescript // CORRECT - Backend handles normalization const search = "ACME CORP"; // Backend will match "acme corp" in search field @@ -143,14 +157,17 @@ const search = "ACME CORP"; // Backend will match "acme corp" in search field ### Schema Reference **Display Fields:** + - `tags TEXT[]` - Titlecase, original punctuation - `contributors TEXT[]` - Original casing, original punctuation **Search Fields:** + - `tags_search TEXT[]` - Lowercase, no punctuation, deduplicated - `contributors_search TEXT[]` - Lowercase, no punctuation, deduplicated **GIN Indexes:** + - `idx_media_items_tags_search` - Fast search on tags_search - `idx_media_items_contributors_search` - Fast search on contributors_search - `idx_media_items_tags_gin` - Display field (if needed) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index b2db201..b0117cb 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -23,6 +23,7 @@ Welcome to the Bookhoard contributing documentation. This section contains guide ## 🤝 How to Contribute We welcome contributions! Please see our [Development Guide](Development.md) for information on: + - Setting up your development environment - Understanding the codebase - Making pull requests diff --git a/docs/contributing/development.md b/docs/contributing/development.md index 33113aa..06b269f 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -30,6 +30,7 @@ bookhoard/ ### Backend Components **Handlers** (`internal/handlers/`): + - `auth.go` - Authentication & user management - `library.go` - Library CRUD operations - `scanner.go` - Media scanning operations @@ -51,6 +52,7 @@ bookhoard/ - `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 @@ -62,6 +64,7 @@ bookhoard/ - `transaction.go` - Database transaction middleware **Services** (`internal/services/`): + - `library_service.go` - Library operations - `media_scanner.go` - File scanning & metadata extraction - `worker.go` - Job queue worker pool @@ -71,6 +74,7 @@ bookhoard/ - `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 @@ -80,6 +84,7 @@ bookhoard/ ### Database Schema **Core Tables**: + - `users` - User accounts with authentication and settings - `libraries` - Library definitions - `library_types` - Media type definitions (ebooks, comics, manga) @@ -105,6 +110,7 @@ bookhoard/ - `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 @@ -114,6 +120,7 @@ bookhoard/ ### Technology Stack **Backend**: + - Go 1.25+ - Echo v4 - HTTP framework - pgx v5 - PostgreSQL driver @@ -122,18 +129,21 @@ bookhoard/ - 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 @@ -201,6 +211,7 @@ go run cmd/server/main.go ### Development Workflow **Backend Development**: + ```bash # Watch mode for Go (requires air or similar) air @@ -211,18 +222,21 @@ go build -o bookhoard cmd/server/main.go ``` **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) @@ -273,6 +287,7 @@ 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 @@ -284,6 +299,7 @@ Environment variables for testing: Integration tests automatically clean up libraries with "test" in the name (case-insensitive). **⚠️ IMPORTANT**: Do not use "test" in library names if you want to keep them! + - Libraries containing "test" (e.g., "My Test Library", "Test Library 1") will be deleted by test cleanup - Use names like "Development Library", "Staging Books", or "Personal" for libraries you want to keep - This ensures your manual test data persists between test runs @@ -357,11 +373,13 @@ podman-compose build --no-cache ### Environment Variables Required for production: + - `JWT_SECRET` - 64-byte random string (generate: `openssl rand -hex 32`) - `DBPASS` - Strong database password (generate: `openssl rand -hex 16`) - `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. @@ -369,6 +387,7 @@ Optional: ### Performance Tuning **PostgreSQL Settings**: + ```sql -- In postgresql.conf shared_buffers = 256MB @@ -385,6 +404,7 @@ max_wal_size = 4GB ``` **Go Settings**: + - GOMAXPROCS = number of CPU cores - Worker pool concurrency: 3 (configurable in services/worker.go) @@ -403,19 +423,23 @@ 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` diff --git a/docs/developer/api-reference.md b/docs/developer/api-reference.md index f726c23..02dad29 100644 --- a/docs/developer/api-reference.md +++ b/docs/developer/api-reference.md @@ -4,6 +4,7 @@ > For updated, split endpoint documentation with interactive API explorer, see [API Documentation Portal](api/api-reference.md). > > **Use the split docs for:** +> > - Easier navigation by category > - Interactive API explorer > - Endpoint-specific examples @@ -65,6 +66,7 @@ Content-Type: application/json ``` **Response** (201): + ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", @@ -93,6 +95,7 @@ Content-Type: application/json ``` **Response** (200): + ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", @@ -118,6 +121,7 @@ Content-Type: application/json ``` **Response** (200): + ```json { "token": "new-jwt-token", @@ -144,6 +148,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "id": "uuid", @@ -219,6 +224,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "libraries": [ @@ -290,11 +296,13 @@ Authorization: Bearer ``` **Query Parameters**: + - `library_id` (required): UUID of library - `limit`: Number of items to return (max 100, default 20) - `offset`: Number of items to skip **Response** (200): + ```json { "media_items": [ @@ -337,11 +345,13 @@ Authorization: Bearer ``` **Query Parameters**: + - `q` (required): Search query (minimum 2 characters) - `limit`: Number of results (default 20) - `offset`: Number to skip **Response** (200): + ```json { "results": [ @@ -409,6 +419,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "media_item_id": "uuid", @@ -452,6 +463,7 @@ Content-Type: application/json ``` **Response** (200): + ```json { "sync_status": "success", @@ -478,6 +490,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "notes": [ @@ -541,6 +554,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "highlights": [ @@ -611,6 +625,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "rating": 8, @@ -668,6 +683,7 @@ Content-Type: application/json ``` **Response** (201): + ```json { "device_id": "uuid", @@ -690,6 +706,7 @@ Content-Type: application/json ``` **Response** (200): + ```json { "status": "pending|approved|expired", @@ -711,6 +728,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "devices": [ @@ -760,10 +778,12 @@ Authorization: Bearer ``` **Query Parameters**: + - `start_date` (optional): Start date (ISO 8601 format) - `end_date` (optional): End date (ISO 8601 format) **Response** (200): + ```json { "pages_read": 1250, @@ -782,6 +802,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "devices": [ @@ -805,9 +826,11 @@ Authorization: Bearer ``` **Query Parameters**: + - `limit` (optional): Number of results (default: 10) **Response** (200): + ```json { "books": [ @@ -842,6 +865,7 @@ Content-Type: application/json ``` **Response** (200): + ```json { "matches": [ @@ -875,6 +899,7 @@ Content-Type: application/json ``` **Response** (200): + ```json { "results": [ @@ -904,6 +929,7 @@ Content-Type: application/json ``` **Response** (200): + ```json { "auto_linked": 15, @@ -927,6 +953,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "unlinked_book_id": "uuid-1", @@ -950,6 +977,7 @@ Authorization: Bearer For complete collection management documentation, see **[COLLECTIONS_API.md](COLLECTIONS_API.md)**. **Quick Reference**: + - `GET /api/collections` - List all collections - `POST /api/collections` - Create new collection - `GET /api/collections/{id}` - Get collection details @@ -960,6 +988,7 @@ For complete collection management documentation, see **[COLLECTIONS_API.md](COL - `GET /api/collections/{id}/books` - Get books in collection **Features**: + - Auto-assign rules based on genre, author, series, tags, language, publisher, year - Device shelf mappings (Kobo shelves, KOReader categories) - Test rules before applying @@ -974,37 +1003,39 @@ GET /opds/devices/{deviceId}/catalog?page={page}&per_page={per_page} ``` **Query Parameters**: + - `page` (optional): Page number (default: 1) - `per_page` (optional): Items per page (default: 50, max: 200) **Response** (200 - OPDS 1.2 XML): + ```xml - urn:uuid:device-id Bookhoard Library 2026-02-01T12:00:00Z - + - + urn:uuid:bookhoard-uuid-123 The Hobbit J.R.R. Tolkien 2026-02-01T10:00:00Z - - - - - + uuid-123 abc123... @@ -1018,9 +1049,11 @@ GET /opds/devices/{deviceId}/download/{bookId}?format={format} ``` **Query Parameters**: + - `format` (optional): Book format - `epub` (default), `kepub` **Response** (200): + - Headers: - `Content-Type`: `application/epub+zip` or `application/vnd.kobo+xml+zip` - `Content-Disposition`: attachment; filename="The Hobbit.epub" @@ -1043,6 +1076,7 @@ GET /opds/devices/{deviceId}/formats/{bookId} ``` **Response** (200): + ```json { "media_item_id": "uuid-123", @@ -1109,6 +1143,7 @@ Content-Type: application/json ``` **Response** (202): + ```json { "sync_status": "accepted", @@ -1133,6 +1168,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "uuid": "book-uuid", @@ -1186,6 +1222,7 @@ Content-Type: application/json ``` **Response** (200): + ```json { "Status": "Success", @@ -1202,6 +1239,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "library_sync": [ @@ -1229,6 +1267,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "book_id": "book-uuid", @@ -1307,10 +1346,12 @@ Authorization: Bearer ``` **Query Parameters**: + - `status`: "unresolved|all" (default: "unresolved") - `type`: "progress|note|highlight|all" (default: "all") **Response** (200): + ```json { "conflicts": [ @@ -1373,6 +1414,7 @@ Content-Type: application/json ``` **Response** (200): + ```json { "conflict_resolved": true, @@ -1408,6 +1450,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "items": [ @@ -1473,6 +1516,7 @@ Authorization: Bearer ``` **Response** (200): + ```json { "pending": 15, @@ -1494,6 +1538,7 @@ WS /ws/sync?token= ### Message Format **Client → Server (Heartbeat)**: + ```json { "type": "ping" @@ -1501,6 +1546,7 @@ WS /ws/sync?token= ``` **Server → Client (Progress Update)**: + ```json { "type": "progress_update", @@ -1523,6 +1569,7 @@ WS /ws/sync?token= ``` **Server → Client (Conflict Detected)**: + ```json { "type": "conflict", @@ -1536,6 +1583,7 @@ WS /ws/sync?token= ``` **Server → Client (Pong)**: + ```json { "type": "pong" @@ -1571,16 +1619,19 @@ All endpoints return standardized error responses: ### Rate Limiting **Per-Device Limits**: + - Sync requests: 60/minute - Progress updates: 120/minute - Metadata requests: 30/minute **Per-User Limits**: + - All requests: 300/minute - Conflict resolutions: 10/minute - Device registrations: 5/hour **Rate Limit Headers**: + ``` X-RateLimit-Limit: 60 X-RateLimit-Remaining: 45 @@ -1612,16 +1663,19 @@ bruno/ ## Testing with Bruno OpenCollection YAML Install Bruno CLI: + ```bash npm install -g @usebruno/cli ``` Run all tests: + ```bash bruno run ``` Run specific collection: + ```bash bruno run bruno/devices/ ``` diff --git a/docs/developer/api/admin/list_users.md b/docs/developer/api/admin/list_users.md index 5517cb6..8c73329 100644 --- a/docs/developer/api/admin/list_users.md +++ b/docs/developer/api/admin/list_users.md @@ -7,17 +7,17 @@ List all users in the system (admin only). ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| limit | integer | No | Maximum number of users to return (default: 50) | -| offset | integer | No | Number of users to skip (default: 0) | -| search | string | No | Search by email or username | +| Parameter | Type | Required | Description | +| --------- | ------- | -------- | ----------------------------------------------- | +| limit | integer | No | Maximum number of users to return (default: 50) | +| offset | integer | No | Number of users to skip (default: 0) | +| search | string | No | Search by email or username | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -53,26 +53,26 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ### Response Fields -| Field | Type | Description | -|-------|------|-------------| -| `id` | string | User ID (UUID) | -| `email` | string | Email address | -| `username` | string | Username | -| `first_name` | string | First name (optional) | -| `last_name` | string | Last name (optional) | -| `role` | string | User role (`"user"` or `"admin"`) | -| `theme` | string | Theme preference (optional) | -| `max_devices` | integer | Maximum number of devices allowed | -| `device_count` | integer | Current number of registered devices | -| `created_at` | string | Account creation timestamp (ISO 8601) | -| `updated_at` | string | Last update timestamp (ISO 8601) | -| `total` | integer | Total number of users matching query | -| `limit` | integer | Limit applied to this request | -| `offset` | integer | Offset applied to this request | +| Field | Type | Description | +| -------------- | ------- | ------------------------------------- | +| `id` | string | User ID (UUID) | +| `email` | string | Email address | +| `username` | string | Username | +| `first_name` | string | First name (optional) | +| `last_name` | string | Last name (optional) | +| `role` | string | User role (`"user"` or `"admin"`) | +| `theme` | string | Theme preference (optional) | +| `max_devices` | integer | Maximum number of devices allowed | +| `device_count` | integer | Current number of registered devices | +| `created_at` | string | Account creation timestamp (ISO 8601) | +| `updated_at` | string | Last update timestamp (ISO 8601) | +| `total` | integer | Total number of users matching query | +| `limit` | integer | Limit applied to this request | +| `offset` | integer | Offset applied to this request | ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | +| Code | Description | +| ---- | ----------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | diff --git a/docs/developer/api/admin/update_user_max_devices.md b/docs/developer/api/admin/update_user_max_devices.md index bdaecf5..3178a38 100644 --- a/docs/developer/api/admin/update_user_max_devices.md +++ b/docs/developer/api/admin/update_user_max_devices.md @@ -8,15 +8,15 @@ Update the maximum number of devices a user can register (admin only). ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | User UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | User UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| max_devices | integer | Yes | Maximum number of devices (1-100) | +| Field | Type | Required | Description | +| ----------- | ------- | -------- | --------------------------------- | +| max_devices | integer | Yes | Maximum number of devices (1-100) | ### Example Request @@ -41,9 +41,9 @@ Update the maximum number of devices a user can register (admin only). ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid max_devices value (must be 1-100) | -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | User not found | +| Code | Description | +| ---- | ----------------------------------------- | +| 400 | Invalid max_devices value (must be 1-100) | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | User not found | diff --git a/docs/developer/api/analytics/get_analytics.md b/docs/developer/api/analytics/get_analytics.md index ee2cf62..70d4a77 100644 --- a/docs/developer/api/analytics/get_analytics.md +++ b/docs/developer/api/analytics/get_analytics.md @@ -7,16 +7,16 @@ Retrieve reading statistics for a date range. ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| start_date | string | No | Start date (ISO 8601 format) | -| end_date | string | No | End date (ISO 8601 format) | +| Parameter | Type | Required | Description | +| ---------- | ------ | -------- | ---------------------------- | +| start_date | string | No | Start date (ISO 8601 format) | +| end_date | string | No | End date (ISO 8601 format) | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -39,7 +39,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid date format | -| 401 | Invalid or expired token | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid date format | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/api-reference.md b/docs/developer/api/api-reference.md index 8df2a1a..c241737 100644 --- a/docs/developer/api/api-reference.md +++ b/docs/developer/api/api-reference.md @@ -255,7 +255,7 @@ See [WebSocket API](websocket/) ## Documentation - GET /docs - Documentation home -- GET /docs/* - Show documentation pages +- GET /docs/\* - Show documentation pages - GET /docs/api/search - Search API documentation - GET /docs/search-index.json - Search index for documentation search diff --git a/docs/developer/api/authentication/login.md b/docs/developer/api/authentication/login.md index 6674585..e81866b 100644 --- a/docs/developer/api/authentication/login.md +++ b/docs/developer/api/authentication/login.md @@ -8,10 +8,10 @@ Authenticate with email and password. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| login | string | Yes | User's email address or username | -| password | string | Yes | User's password | +| Field | Type | Required | Description | +| -------- | ------ | -------- | -------------------------------- | +| login | string | Yes | User's email address or username | +| password | string | Yes | User's password | ### Example Request @@ -42,6 +42,7 @@ Authenticate with email and password. ``` **Set-Cookie Header**: + ``` Set-Cookie: token=eyJhbG...; Max-Age=604800; Path=/; HttpOnly ``` @@ -50,8 +51,8 @@ Set-Cookie: token=eyJhbG...; Max-Age=604800; Path=/; HttpOnly ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid email or password | -| 400 | Missing required fields | -| 429 | Too many login attempts | +| Code | Description | +| ---- | ------------------------- | +| 401 | Invalid email or password | +| 400 | Missing required fields | +| 429 | Too many login attempts | diff --git a/docs/developer/api/authentication/logout.md b/docs/developer/api/authentication/logout.md index 87a91d0..9ff506e 100644 --- a/docs/developer/api/authentication/logout.md +++ b/docs/developer/api/authentication/logout.md @@ -8,9 +8,9 @@ Invalidate the current JWT token. ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (e.g., `Bearer eyJhbG...`) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | --------------------------------------- | +| Authorization | string | Yes | Bearer token (e.g., `Bearer eyJhbG...`) | ### Example Request @@ -25,7 +25,7 @@ No response body. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | Token already invalidated | +| Code | Description | +| ---- | ------------------------- | +| 401 | Invalid or expired token | +| 403 | Token already invalidated | diff --git a/docs/developer/api/authentication/overview.md b/docs/developer/api/authentication/overview.md index a2e90c5..056f5bb 100644 --- a/docs/developer/api/authentication/overview.md +++ b/docs/developer/api/authentication/overview.md @@ -42,6 +42,7 @@ When a user registers or logs in: 4. Server returns JSON response with both tokens and user profile **Request**: + ```json POST /api/auth/login { @@ -51,6 +52,7 @@ POST /api/auth/login ``` **Response**: + ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", @@ -67,6 +69,7 @@ POST /api/auth/login ``` **Set-Cookie Header**: + ``` Set-Cookie: token=eyJhbG...; Max-Age=604800; Path=/; HttpOnly ``` @@ -94,6 +97,7 @@ POST /api/auth/refresh ``` **Response**: + ```json { "access_token": "new-jwt-token", @@ -135,6 +139,7 @@ When an API call receives a 401 Unauthorized response: ``` The frontend toast.js interceptor: + 1. Clears invalid tokens from localStorage 2. Shows an error toast notification 3. Allows user to re-authenticate @@ -149,10 +154,12 @@ The frontend toast.js interceptor: ## Token Storage Recommendations ### Browser Applications + - **Backend**: Automatically manages HTTP-only cookie - **Frontend**: Store tokens in localStorage for API calls ### Mobile Applications + - Store access token in secure storage (Keychain/Keystore) - Store refresh token in secure storage - Handle 401 responses by prompting user to re-authenticate @@ -160,6 +167,7 @@ The frontend toast.js interceptor: ## Constants Reference All session durations use constants defined in: + - `internal/handlers/auth.go` - SessionDuration, SessionDurationSec - `internal/handlers/refresh_token.go` - SessionDurationSec (mirrored) diff --git a/docs/developer/api/authentication/refresh_token.md b/docs/developer/api/authentication/refresh_token.md index 59a97a3..0482cc1 100644 --- a/docs/developer/api/authentication/refresh_token.md +++ b/docs/developer/api/authentication/refresh_token.md @@ -8,9 +8,9 @@ Obtain a new JWT access token using a refresh token. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| refresh_token | string | Yes | Valid refresh token (UUID) | +| Field | Type | Required | Description | +| ------------- | ------ | -------- | -------------------------- | +| refresh_token | string | Yes | Valid refresh token (UUID) | ### Example Request @@ -36,7 +36,7 @@ The new access token is valid for 7 days from the time of refresh. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired refresh token | -| 400 | Missing refresh token or invalid format | +| Code | Description | +| ---- | --------------------------------------- | +| 401 | Invalid or expired refresh token | +| 400 | Missing refresh token or invalid format | diff --git a/docs/developer/api/authentication/register.md b/docs/developer/api/authentication/register.md index d85557c..cc40b0b 100644 --- a/docs/developer/api/authentication/register.md +++ b/docs/developer/api/authentication/register.md @@ -8,13 +8,13 @@ Create a new user account. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| email | string | Yes | User's email address | -| username | string | Yes | Desired username (3-50 chars) | -| password | string | Yes | Password (min 8 chars, must meet complexity requirements) | -| first_name | string | No | User's first name | -| last_name | string | No | User's last name | +| Field | Type | Required | Description | +| ---------- | ------ | -------- | --------------------------------------------------------- | +| email | string | Yes | User's email address | +| username | string | Yes | Desired username (3-50 chars) | +| password | string | Yes | Password (min 8 chars, must meet complexity requirements) | +| first_name | string | No | User's first name | +| last_name | string | No | User's last name | ### Example Request @@ -50,6 +50,7 @@ Create a new user account. ``` **Set-Cookie Header**: + ``` Set-Cookie: token=eyJhbG...; Max-Age=604800; Path=/; HttpOnly ``` @@ -60,7 +61,7 @@ Set-Cookie: token=eyJhbG...; Max-Age=604800; Path=/; HttpOnly ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid email format, weak password, or missing fields | -| 409 | Email or username already exists | +| Code | Description | +| ---- | ------------------------------------------------------ | +| 400 | Invalid email format, weak password, or missing fields | +| 409 | Email or username already exists | diff --git a/docs/developer/api/book-matching/auto_link_books.md b/docs/developer/api/book-matching/auto_link_books.md index c256bf3..be13706 100644 --- a/docs/developer/api/book-matching/auto_link_books.md +++ b/docs/developer/api/book-matching/auto_link_books.md @@ -8,10 +8,10 @@ Automatically link books to media items based on matching metadata. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| device_id | string (UUID) | Yes | Device UUID | -| threshold | float | No | Match confidence threshold (0.0-1.0, default: 0.7) | +| Field | Type | Required | Description | +| --------- | ------------- | -------- | -------------------------------------------------- | +| device_id | string (UUID) | Yes | Device UUID | +| threshold | float | No | Match confidence threshold (0.0-1.0, default: 0.7) | ### Example Request @@ -41,8 +41,8 @@ Automatically link books to media items based on matching metadata. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 404 | Device not found | diff --git a/docs/developer/api/book-matching/bulk_link_books.md b/docs/developer/api/book-matching/bulk_link_books.md index e7aedcf..b95f510 100644 --- a/docs/developer/api/book-matching/bulk_link_books.md +++ b/docs/developer/api/book-matching/bulk_link_books.md @@ -8,9 +8,9 @@ Link multiple books to media items at once. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| links | array | Yes | Array of book-media link objects | +| Field | Type | Required | Description | +| ----- | ----- | -------- | -------------------------------- | +| links | array | Yes | Array of book-media link objects | Each link object contains: | Field | Type | Required | Description | @@ -50,8 +50,8 @@ Each link object contains: ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 404 | Device, book, or media item not found | +| Code | Description | +| ---- | ------------------------------------- | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 404 | Device, book, or media item not found | diff --git a/docs/developer/api/book-matching/create_device_file_alias.md b/docs/developer/api/book-matching/create_device_file_alias.md index f931b49..ce0b252 100644 --- a/docs/developer/api/book-matching/create_device_file_alias.md +++ b/docs/developer/api/book-matching/create_device_file_alias.md @@ -8,17 +8,17 @@ Create a new file alias for a device. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | Device UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| media_item_id | string (UUID) | Yes | Media item UUID | -| file_name | string | Yes | Name of the file | -| file_hash | string | No | SHA256 hash of the file (optional) | +| Field | Type | Required | Description | +| ------------- | ------------- | -------- | ---------------------------------- | +| media_item_id | string (UUID) | Yes | Media item UUID | +| file_name | string | Yes | Name of the file | +| file_hash | string | No | SHA256 hash of the file (optional) | ### Example Request @@ -45,9 +45,9 @@ Create a new file alias for a device. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 404 | Device or media item not found | -| 409 | File alias already exists | +| Code | Description | +| ---- | ------------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 404 | Device or media item not found | +| 409 | File alias already exists | diff --git a/docs/developer/api/book-matching/delete_device_file_alias.md b/docs/developer/api/book-matching/delete_device_file_alias.md index c67bf5d..5151473 100644 --- a/docs/developer/api/book-matching/delete_device_file_alias.md +++ b/docs/developer/api/book-matching/delete_device_file_alias.md @@ -7,16 +7,16 @@ Delete a device file alias. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | -| aliasId | string (UUID) | Yes | File alias UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Device UUID | +| aliasId | string (UUID) | Yes | File alias UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -31,7 +31,7 @@ File alias deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Device or file alias not found | +| Code | Description | +| ---- | ------------------------------ | +| 401 | Invalid or expired token | +| 404 | Device or file alias not found | diff --git a/docs/developer/api/book-matching/get_book_matches.md b/docs/developer/api/book-matching/get_book_matches.md index 1613ab3..b86d047 100644 --- a/docs/developer/api/book-matching/get_book_matches.md +++ b/docs/developer/api/book-matching/get_book_matches.md @@ -7,16 +7,16 @@ Get potential book matches for a given query. ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| q | string | Yes | Search query (title, author, etc.) | -| limit | integer | No | Maximum number of matches to return (default: 10) | +| Parameter | Type | Required | Description | +| --------- | ------- | -------- | ------------------------------------------------- | +| q | string | Yes | Search query (title, author, etc.) | +| limit | integer | No | Maximum number of matches to return (default: 10) | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -46,7 +46,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Missing required query parameter | -| 401 | Invalid or expired token | +| Code | Description | +| ---- | -------------------------------- | +| 400 | Missing required query parameter | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/book-matching/get_device_file_aliases.md b/docs/developer/api/book-matching/get_device_file_aliases.md index 44e2ab1..0eb3b2c 100644 --- a/docs/developer/api/book-matching/get_device_file_aliases.md +++ b/docs/developer/api/book-matching/get_device_file_aliases.md @@ -7,15 +7,15 @@ Get all file aliases for a specific device. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | Device UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -44,7 +44,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Device not found | diff --git a/docs/developer/api/book-matching/get_unlinked_book_suggestions.md b/docs/developer/api/book-matching/get_unlinked_book_suggestions.md index 3b2bfc6..f8f6488 100644 --- a/docs/developer/api/book-matching/get_unlinked_book_suggestions.md +++ b/docs/developer/api/book-matching/get_unlinked_book_suggestions.md @@ -7,22 +7,22 @@ Get suggested matches for unlinked books on a device. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | Device UUID | ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| limit | integer | No | Maximum number of suggestions per book (default: 5) | -| threshold | float | No | Minimum confidence threshold (default: 0.5) | +| Parameter | Type | Required | Description | +| --------- | ------- | -------- | --------------------------------------------------- | +| limit | integer | No | Maximum number of suggestions per book (default: 5) | +| threshold | float | No | Minimum confidence threshold (default: 0.5) | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -61,7 +61,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Device not found | diff --git a/docs/developer/api/book-matching/get_unlinked_books.md b/docs/developer/api/book-matching/get_unlinked_books.md index c6807a4..a0571be 100644 --- a/docs/developer/api/book-matching/get_unlinked_books.md +++ b/docs/developer/api/book-matching/get_unlinked_books.md @@ -7,22 +7,22 @@ Get all books that haven't been linked to media items yet for a specific device. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| deviceId | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| deviceId | string (UUID) | Yes | Device UUID | ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| limit | integer | No | Maximum number of items to return (default: 50) | -| offset | integer | No | Number of items to skip (default: 0) | +| Parameter | Type | Required | Description | +| --------- | ------- | -------- | ----------------------------------------------- | +| limit | integer | No | Maximum number of items to return (default: 50) | +| offset | integer | No | Number of items to skip (default: 0) | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -52,7 +52,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Device not found | diff --git a/docs/developer/api/book-matching/link_book.md b/docs/developer/api/book-matching/link_book.md index 828c5e0..8a90448 100644 --- a/docs/developer/api/book-matching/link_book.md +++ b/docs/developer/api/book-matching/link_book.md @@ -8,12 +8,12 @@ Link a device book to a Bookhoard media item. Supports bulk linking. ## Manual Link Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| links | array | Yes | List of book links | -| links[].unlinked_book_id | string | Yes | Device book UUID | -| links[].media_item_id | string | Yes | Bookhoard media item UUID | -| links[].confidence_score | float | No | Match confidence (0-1) | +| Field | Type | Required | Description | +| ------------------------ | ------ | -------- | ------------------------- | +| links | array | Yes | List of book links | +| links[].unlinked_book_id | string | Yes | Device book UUID | +| links[].media_item_id | string | Yes | Bookhoard media item UUID | +| links[].confidence_score | float | No | Match confidence (0-1) | ### Example Manual Link Request @@ -31,10 +31,10 @@ Link a device book to a Bookhoard media item. Supports bulk linking. ## Auto-Link Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| confidence_threshold | float | No | Minimum confidence for auto-link (default: 0.8) | -| limit | integer | No | Maximum books to auto-link (default: 50) | +| Field | Type | Required | Description | +| -------------------- | ------- | -------- | ----------------------------------------------- | +| confidence_threshold | float | No | Minimum confidence for auto-link (default: 0.8) | +| limit | integer | No | Maximum books to auto-link (default: 50) | ### Example Auto-Link Request @@ -81,8 +81,8 @@ Link a device book to a Bookhoard media item. Supports bulk linking. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid link data | -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid link data | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/book-matching/search_books.md b/docs/developer/api/book-matching/search_books.md index 136b41a..2b813ed 100644 --- a/docs/developer/api/book-matching/search_books.md +++ b/docs/developer/api/book-matching/search_books.md @@ -8,13 +8,13 @@ Query books to find potential matches for linking. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| identifiers | array | No | List of identifiers (ISBN, UUID) | -| sha256 | string | No | SHA256 hash of book file | -| title | string | No | Book title | -| author | string | No | Book author | -| file_size | integer | No | File size in bytes | +| Field | Type | Required | Description | +| ----------- | ------- | -------- | -------------------------------- | +| identifiers | array | No | List of identifiers (ISBN, UUID) | +| sha256 | string | No | SHA256 hash of book file | +| title | string | No | Book title | +| author | string | No | Book author | +| file_size | integer | No | File size in bytes | ### Example Request @@ -46,7 +46,7 @@ Query books to find potential matches for linking. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid query parameters | -| 401 | Invalid or expired token | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid query parameters | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/book-matching/update_device_file_alias.md b/docs/developer/api/book-matching/update_device_file_alias.md index a99c6c7..b9cf6f4 100644 --- a/docs/developer/api/book-matching/update_device_file_alias.md +++ b/docs/developer/api/book-matching/update_device_file_alias.md @@ -8,17 +8,17 @@ Update an existing device file alias. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | -| aliasId | string (UUID) | Yes | File alias UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Device UUID | +| aliasId | string (UUID) | Yes | File alias UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| file_name | string | No | New file name | -| file_hash | string | No | New file hash | +| Field | Type | Required | Description | +| --------- | ------ | -------- | ------------- | +| file_name | string | No | New file name | +| file_hash | string | No | New file hash | ### Example Request @@ -44,8 +44,8 @@ Update an existing device file alias. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 404 | Device or file alias not found | +| Code | Description | +| ---- | ------------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 404 | Device or file alias not found | diff --git a/docs/developer/api/collections/add_auto_assign_rule.md b/docs/developer/api/collections/add_auto_assign_rule.md index 641019d..280abc4 100644 --- a/docs/developer/api/collections/add_auto_assign_rule.md +++ b/docs/developer/api/collections/add_auto_assign_rule.md @@ -8,44 +8,44 @@ Add an automatic book assignment rule to a collection. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Collection UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Collection UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) | -| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) | -| value | string/number | Yes | Value to compare against | -| priority | integer | No | Rule priority (1 = highest, default: 1) | -| enabled | boolean | No | Whether rule is active (default: true) | +| Field | Type | Required | Description | +| -------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | +| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) | +| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) | +| value | string/number | Yes | Value to compare against | +| priority | integer | No | Rule priority (1 = highest, default: 1) | +| enabled | boolean | No | Whether rule is active (default: true) | ### Supported Fields -| Field | Type | Description | -|-------|------|-------------| -| genre | string | Book genre | -| author | string | Book author | -| series | string | Book series name | -| language | string | Book language | -| publisher | string | Publisher name | +| Field | Type | Description | +| -------------- | ------ | ------------------------------------- | +| genre | string | Book genre | +| author | string | Book author | +| series | string | Book series name | +| language | string | Book language | +| publisher | string | Publisher name | | copyright_year | number | Publication year (numeric comparison) | -| tags | string | Book tags | +| tags | string | Book tags | ### Supported Operators -| Operator | Type | Description | -|----------|------|-------------| -| equals | all | Exact match | -| not_equals | all | Not equal | -| contains | string | Contains substring (case-insensitive) | -| not_contains | string | Does not contain | -| starts_with | string | Starts with (case-insensitive) | -| ends_with | string | Ends with (case-insensitive) | -| greater_than | number | Greater than | -| less_than | number | Less than | +| Operator | Type | Description | +| ------------ | ------ | ------------------------------------- | +| equals | all | Exact match | +| not_equals | all | Not equal | +| contains | string | Contains substring (case-insensitive) | +| not_contains | string | Does not contain | +| starts_with | string | Starts with (case-insensitive) | +| ends_with | string | Ends with (case-insensitive) | +| greater_than | number | Greater than | +| less_than | number | Less than | ### Example Request @@ -75,11 +75,11 @@ Add an automatic book assignment rule to a collection. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request (validation failed) | -| 401 | Authentication required | -| 404 | Collection not found | -| 500 | Internal server error | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Invalid request (validation failed) | +| 401 | Authentication required | +| 404 | Collection not found | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/bulk_assign.md b/docs/developer/api/collections/bulk_assign.md index cb9477b..1c14f7c 100644 --- a/docs/developer/api/collections/bulk_assign.md +++ b/docs/developer/api/collections/bulk_assign.md @@ -8,15 +8,15 @@ Add multiple books to a collection at once. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Collection UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Collection UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| book_ids | array of UUID | Yes | Array of book IDs to add | +| Field | Type | Required | Description | +| -------- | ------------- | -------- | ------------------------ | +| book_ids | array of UUID | Yes | Array of book IDs to add | ### Example Request @@ -42,11 +42,11 @@ Books added to collection successfully. No response body. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request (validation failed) | -| 401 | Authentication required | -| 404 | Collection or book(s) not found | -| 500 | Internal server error | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Invalid request (validation failed) | +| 401 | Authentication required | +| 404 | Collection or book(s) not found | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/create_collection.md b/docs/developer/api/collections/create_collection.md index e0177b8..ef3803c 100644 --- a/docs/developer/api/collections/create_collection.md +++ b/docs/developer/api/collections/create_collection.md @@ -8,24 +8,24 @@ Create a new collection. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| name | string | Yes | Collection name (max 255 chars) | -| description | string | No | Collection description | -| color | string | No | Hex color code (e.g., "#FF5733") | -| icon | string | No | Emoji icon (e.g., "🚀", "📖") | -| auto_assign_rules | array | No | Array of rule objects | -| view_settings | object | No | Per-device display preferences | +| Field | Type | Required | Description | +| ----------------- | ------ | -------- | -------------------------------- | +| name | string | Yes | Collection name (max 255 chars) | +| description | string | No | Collection description | +| color | string | No | Hex color code (e.g., "#FF5733") | +| icon | string | No | Emoji icon (e.g., "🚀", "📖") | +| auto_assign_rules | array | No | Array of rule objects | +| view_settings | object | No | Per-device display preferences | ### Auto-Assign Rule Object -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) | -| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) | -| value | string/number | Yes | Value to compare against | -| priority | integer | No | Rule priority (1 = highest, default: 1) | -| enabled | boolean | No | Whether rule is active (default: true) | +| Field | Type | Required | Description | +| -------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | +| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) | +| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) | +| value | string/number | Yes | Value to compare against | +| priority | integer | No | Rule priority (1 = highest, default: 1) | +| enabled | boolean | No | Whether rule is active (default: true) | ### Example Request @@ -87,10 +87,10 @@ Create a new collection. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request (validation failed) | -| 401 | Authentication required | -| 500 | Internal server error | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Invalid request (validation failed) | +| 401 | Authentication required | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/create_shelf_mapping.md b/docs/developer/api/collections/create_shelf_mapping.md index 9ba0781..3209547 100644 --- a/docs/developer/api/collections/create_shelf_mapping.md +++ b/docs/developer/api/collections/create_shelf_mapping.md @@ -8,26 +8,26 @@ Map a collection to a device shelf for syncing. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| deviceId | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| deviceId | string (UUID) | Yes | Device UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| collection_id | string (UUID) | Yes | Collection UUID to map | -| device_shelf_name | string | Yes | Name of the shelf on the device | -| sync_direction | string | No | Sync direction (default: "bidirectional") | +| Field | Type | Required | Description | +| ----------------- | ------------- | -------- | ----------------------------------------- | +| collection_id | string (UUID) | Yes | Collection UUID to map | +| device_shelf_name | string | Yes | Name of the shelf on the device | +| sync_direction | string | No | Sync direction (default: "bidirectional") | ### Sync Directions -| Direction | Description | -|-----------|-------------| -| bidirectional | Sync both ways between Bookhoard and device | -| book_to_hoard | Bookhoard → Device only | -| device_to_hoard | Device → Bookhoard only | -| none | No sync (mapping only for reference) | +| Direction | Description | +| --------------- | ------------------------------------------- | +| bidirectional | Sync both ways between Bookhoard and device | +| book_to_hoard | Bookhoard → Device only | +| device_to_hoard | Device → Bookhoard only | +| none | No sync (mapping only for reference) | ### Example Request @@ -58,12 +58,12 @@ Collections can be synced to device-specific shelves (Kobo, KOReader). This allo ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request (validation failed) | -| 401 | Authentication required | -| 404 | Device or collection not found | -| 409 | Mapping already exists | -| 500 | Internal server error | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Invalid request (validation failed) | +| 401 | Authentication required | +| 404 | Device or collection not found | +| 409 | Mapping already exists | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/delete_collection.md b/docs/developer/api/collections/delete_collection.md index 56ec1c6..c73dfd7 100644 --- a/docs/developer/api/collections/delete_collection.md +++ b/docs/developer/api/collections/delete_collection.md @@ -8,9 +8,9 @@ Delete a collection. Books are NOT deleted. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Collection UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Collection UUID | ## Response (204 No Content) @@ -18,10 +18,10 @@ Collection deleted successfully. No response body. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Authentication required | -| 404 | Collection not found | -| 500 | Internal server error | +| Code | Description | +| ---- | ----------------------- | +| 401 | Authentication required | +| 404 | Collection not found | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/delete_shelf_mapping.md b/docs/developer/api/collections/delete_shelf_mapping.md index 26ef452..8c85071 100644 --- a/docs/developer/api/collections/delete_shelf_mapping.md +++ b/docs/developer/api/collections/delete_shelf_mapping.md @@ -8,10 +8,10 @@ Remove a collection-to-shelf mapping for a device. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| deviceId | string (UUID) | Yes | Device UUID | -| collectionId | string (UUID) | Yes | Collection UUID | +| Parameter | Type | Required | Description | +| ------------ | ------------- | -------- | --------------- | +| deviceId | string (UUID) | Yes | Device UUID | +| collectionId | string (UUID) | Yes | Collection UUID | ## Response (204 No Content) @@ -25,10 +25,10 @@ Shelf mapping deleted successfully. No response body. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Authentication required | -| 404 | Device or collection not found | -| 500 | Internal server error | +| Code | Description | +| ---- | ------------------------------ | +| 401 | Authentication required | +| 404 | Device or collection not found | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/get_collection.md b/docs/developer/api/collections/get_collection.md index a9b6305..5a5f483 100644 --- a/docs/developer/api/collections/get_collection.md +++ b/docs/developer/api/collections/get_collection.md @@ -8,17 +8,17 @@ Get single collection with all books. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Collection UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Collection UUID | ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| include_books | boolean | No | Include books in response (default: true) | -| limit | integer | No | Number of books to return (default: 50) | -| offset | integer | No | Number of books to skip (default: 0) | +| Parameter | Type | Required | Description | +| ------------- | ------- | -------- | ----------------------------------------- | +| include_books | boolean | No | Include books in response (default: true) | +| limit | integer | No | Number of books to return (default: 50) | +| offset | integer | No | Number of books to skip (default: 0) | ## Response (200 OK) @@ -45,10 +45,10 @@ Get single collection with all books. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Authentication required | -| 404 | Collection not found | -| 500 | Internal server error | +| Code | Description | +| ---- | ----------------------- | +| 401 | Authentication required | +| 404 | Collection not found | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/list_collections.md b/docs/developer/api/collections/list_collections.md index 76786a2..17ee02f 100644 --- a/docs/developer/api/collections/list_collections.md +++ b/docs/developer/api/collections/list_collections.md @@ -8,10 +8,10 @@ Get all collections for the authenticated user. ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| limit | integer | No | Number of collections to return (default: 50) | -| offset | integer | No | Number of collections to skip (default: 0) | +| Parameter | Type | Required | Description | +| --------- | ------- | -------- | --------------------------------------------- | +| limit | integer | No | Number of collections to return (default: 50) | +| offset | integer | No | Number of collections to skip (default: 0) | ## Response (200 OK) @@ -49,9 +49,9 @@ Get all collections for the authenticated user. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Authentication required | -| 500 | Internal server error | +| Code | Description | +| ---- | ----------------------- | +| 401 | Authentication required | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/remove_auto_assign_rule.md b/docs/developer/api/collections/remove_auto_assign_rule.md index d52743d..fc4e5a9 100644 --- a/docs/developer/api/collections/remove_auto_assign_rule.md +++ b/docs/developer/api/collections/remove_auto_assign_rule.md @@ -8,10 +8,10 @@ Remove an automatic book assignment rule from a collection. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| collectionId | string (UUID) | Yes | Collection UUID | -| ruleId | string (UUID) | Yes | Rule UUID | +| Parameter | Type | Required | Description | +| ------------ | ------------- | -------- | --------------- | +| collectionId | string (UUID) | Yes | Collection UUID | +| ruleId | string (UUID) | Yes | Rule UUID | ## Response (204 No Content) @@ -19,10 +19,10 @@ Rule deleted successfully. No response body. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Authentication required | -| 404 | Collection or rule not found | -| 500 | Internal server error | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Authentication required | +| 404 | Collection or rule not found | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/test_rule.md b/docs/developer/api/collections/test_rule.md index b9126b9..24a9699 100644 --- a/docs/developer/api/collections/test_rule.md +++ b/docs/developer/api/collections/test_rule.md @@ -8,17 +8,17 @@ Test which books would match given rules without saving. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| rules | array | Yes | Array of rule objects to test | +| Field | Type | Required | Description | +| ----- | ----- | -------- | ----------------------------- | +| rules | array | Yes | Array of rule objects to test | ### Rule Object -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) | -| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) | -| value | string/number | Yes | Value to compare against | +| Field | Type | Required | Description | +| -------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | +| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) | +| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) | +| value | string/number | Yes | Value to compare against | ### Example Request @@ -62,10 +62,10 @@ Test rules before creating a collection to verify correct book matching. This en ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request (validation failed) | -| 401 | Authentication required | -| 500 | Internal server error | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Invalid request (validation failed) | +| 401 | Authentication required | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/collections/update_collection.md b/docs/developer/api/collections/update_collection.md index 2d751c7..e527dce 100644 --- a/docs/developer/api/collections/update_collection.md +++ b/docs/developer/api/collections/update_collection.md @@ -8,22 +8,22 @@ Update collection details. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Collection UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Collection UUID | ## Request Body All fields are optional. Include only fields you want to update. -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| name | string | No | Collection name (max 255 chars) | -| description | string | No | Collection description | -| color | string | No | Hex color code (e.g., "#FF5733") | -| icon | string | No | Emoji icon (e.g., "🚀", "📖") | -| auto_assign_rules | array | No | Array of rule objects (replaces existing rules) | -| view_settings | object | No | Per-device display preferences | +| Field | Type | Required | Description | +| ----------------- | ------ | -------- | ----------------------------------------------- | +| name | string | No | Collection name (max 255 chars) | +| description | string | No | Collection description | +| color | string | No | Hex color code (e.g., "#FF5733") | +| icon | string | No | Emoji icon (e.g., "🚀", "📖") | +| auto_assign_rules | array | No | Array of rule objects (replaces existing rules) | +| view_settings | object | No | Per-device display preferences | ### Example Request @@ -53,11 +53,11 @@ All fields are optional. Include only fields you want to update. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request (validation failed) | -| 401 | Authentication required | -| 404 | Collection not found | -| 500 | Internal server error | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Invalid request (validation failed) | +| 401 | Authentication required | +| 404 | Collection not found | +| 500 | Internal server error | ## Try It Out diff --git a/docs/developer/api/conflicts/bulk_dismiss_conflicts.md b/docs/developer/api/conflicts/bulk_dismiss_conflicts.md index d182157..7bd6d26 100644 --- a/docs/developer/api/conflicts/bulk_dismiss_conflicts.md +++ b/docs/developer/api/conflicts/bulk_dismiss_conflicts.md @@ -8,9 +8,9 @@ Dismiss multiple conflicts at once. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| conflict_ids | array of UUID | Yes | Array of conflict UUIDs to dismiss | +| Field | Type | Required | Description | +| ------------ | ------------- | -------- | ---------------------------------- | +| conflict_ids | array of UUID | Yes | Array of conflict UUIDs to dismiss | ### Example Request @@ -34,7 +34,7 @@ Dismiss multiple conflicts at once. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request body | -| 401 | Invalid or expired token | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid request body | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/conflicts/bulk_resolve_conflicts.md b/docs/developer/api/conflicts/bulk_resolve_conflicts.md index 9cbcdf2..6b094d6 100644 --- a/docs/developer/api/conflicts/bulk_resolve_conflicts.md +++ b/docs/developer/api/conflicts/bulk_resolve_conflicts.md @@ -8,10 +8,10 @@ Resolve multiple conflicts at once using a specified strategy. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| conflict_ids | array of UUID | Yes | Array of conflict UUIDs to resolve | -| resolution | string | Yes | Resolution strategy: "device", "server", or "highest_progress" | +| Field | Type | Required | Description | +| ------------ | ------------- | -------- | -------------------------------------------------------------- | +| conflict_ids | array of UUID | Yes | Array of conflict UUIDs to resolve | +| resolution | string | Yes | Resolution strategy: "device", "server", or "highest_progress" | ### Example Request @@ -37,8 +37,8 @@ Resolve multiple conflicts at once using a specified strategy. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request body | -| 401 | Invalid or expired token | -| 400 | Invalid resolution strategy | +| Code | Description | +| ---- | --------------------------- | +| 400 | Invalid request body | +| 401 | Invalid or expired token | +| 400 | Invalid resolution strategy | diff --git a/docs/developer/api/conflicts/delete_conflict.md b/docs/developer/api/conflicts/delete_conflict.md index b297f07..5ddb4de 100644 --- a/docs/developer/api/conflicts/delete_conflict.md +++ b/docs/developer/api/conflicts/delete_conflict.md @@ -7,15 +7,15 @@ Delete a specific conflict record. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Conflict UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------- | +| id | string (UUID) | Yes | Conflict UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,7 +30,7 @@ Conflict deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Conflict not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Conflict not found | diff --git a/docs/developer/api/conflicts/dismiss_all_resolved.md b/docs/developer/api/conflicts/dismiss_all_resolved.md index 5dc7f16..e2cba5d 100644 --- a/docs/developer/api/conflicts/dismiss_all_resolved.md +++ b/docs/developer/api/conflicts/dismiss_all_resolved.md @@ -7,9 +7,9 @@ Dismiss all resolved conflicts. ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -29,6 +29,6 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/conflicts/get_conflict.md b/docs/developer/api/conflicts/get_conflict.md index 44c17c6..58914fb 100644 --- a/docs/developer/api/conflicts/get_conflict.md +++ b/docs/developer/api/conflicts/get_conflict.md @@ -7,15 +7,15 @@ Get detailed information about a specific conflict. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Conflict UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------- | +| id | string (UUID) | Yes | Conflict UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -69,7 +69,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Conflict not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Conflict not found | diff --git a/docs/developer/api/conflicts/list_conflicts.md b/docs/developer/api/conflicts/list_conflicts.md index 3574a61..2f5aa28 100644 --- a/docs/developer/api/conflicts/list_conflicts.md +++ b/docs/developer/api/conflicts/list_conflicts.md @@ -7,18 +7,18 @@ List all sync conflicts for the current user. ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| status | string | No | Filter by status (active, resolved, dismissed) | -| media_item_id | string (UUID) | No | Filter by media item | -| limit | integer | No | Maximum number of conflicts to return (default: 50) | -| offset | integer | No | Number of conflicts to skip (default: 0) | +| Parameter | Type | Required | Description | +| ------------- | ------------- | -------- | --------------------------------------------------- | +| status | string | No | Filter by status (active, resolved, dismissed) | +| media_item_id | string (UUID) | No | Filter by media item | +| limit | integer | No | Maximum number of conflicts to return (default: 50) | +| offset | integer | No | Number of conflicts to skip (default: 0) | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -60,6 +60,6 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/conflicts/resolve_conflict.md b/docs/developer/api/conflicts/resolve_conflict.md index 7ddb902..d8078ad 100644 --- a/docs/developer/api/conflicts/resolve_conflict.md +++ b/docs/developer/api/conflicts/resolve_conflict.md @@ -8,15 +8,15 @@ Resolve a specific conflict by choosing which version to keep. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Conflict UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------- | +| id | string (UUID) | Yes | Conflict UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| resolution | string | Yes | Resolution strategy: "device", "server", or "highest_progress" | +| Field | Type | Required | Description | +| ---------- | ------ | -------- | -------------------------------------------------------------- | +| resolution | string | Yes | Resolution strategy: "device", "server", or "highest_progress" | ### Example Request @@ -38,9 +38,9 @@ Resolve a specific conflict by choosing which version to keep. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid resolution strategy | -| 401 | Invalid or expired token | -| 404 | Conflict not found | -| 400 | Conflict already resolved | +| Code | Description | +| ---- | --------------------------- | +| 400 | Invalid resolution strategy | +| 401 | Invalid or expired token | +| 404 | Conflict not found | +| 400 | Conflict already resolved | diff --git a/docs/developer/api/custom-section-builder.md b/docs/developer/api/custom-section-builder.md index f317b5a..0dfb6ea 100644 --- a/docs/developer/api/custom-section-builder.md +++ b/docs/developer/api/custom-section-builder.md @@ -9,6 +9,7 @@ Evaluates filter rules and returns matching items without saving the collection. **Endpoint:** `POST /api/collections/preview` **Request Body:** + ```json { "library_id": "uuid", @@ -28,23 +29,24 @@ Evaluates filter rules and returns matching items without saving the collection. **Available Filter Fields:** -| Field | Type | Operators | -|-------|------|-----------| -| `title` | text | contains, equals, starts_with, ends_with, regex | -| `author` | text | contains, equals | -| `genre` | select | equals, not_equals, in, not_in | -| `series` | text | is_set, is_not_set, equals, contains | -| `progress` | number | equals, not_equals, greater_than, less_than, between, is_set, is_not_set | -| `rating` | number | equals, not_equals, greater_than, less_than, is_set, is_not_set | -| `date_added` | date | equals, not_equals, before, after, between, last_x_days | -| `last_read` | date | equals, before, after, between, last_x_days, is_set, is_not_set | -| `publisher` | text | contains, equals | -| `language` | select | equals, not_equals, in | -| `format` | select | equals, in | -| `tags` | text | contains, not_contains, equals | -| `narrators` | text | contains, equals, is_set, is_not_set | +| Field | Type | Operators | +| ------------ | ------ | ------------------------------------------------------------------------ | +| `title` | text | contains, equals, starts_with, ends_with, regex | +| `author` | text | contains, equals | +| `genre` | select | equals, not_equals, in, not_in | +| `series` | text | is_set, is_not_set, equals, contains | +| `progress` | number | equals, not_equals, greater_than, less_than, between, is_set, is_not_set | +| `rating` | number | equals, not_equals, greater_than, less_than, is_set, is_not_set | +| `date_added` | date | equals, not_equals, before, after, between, last_x_days | +| `last_read` | date | equals, before, after, between, last_x_days, is_set, is_not_set | +| `publisher` | text | contains, equals | +| `language` | select | equals, not_equals, in | +| `format` | select | equals, in | +| `tags` | text | contains, not_contains, equals | +| `narrators` | text | contains, equals, is_set, is_not_set | **Response:** + ```json { "items": [ @@ -65,6 +67,7 @@ Creates a new custom collection with filter rules and/or manual book selection. **Endpoint:** `POST /api/collections` **Request Body:** + ```json { "name": "My Custom Section", @@ -89,6 +92,7 @@ Creates a new custom collection with filter rules and/or manual book selection. **TypeScript:** `web/src/custom-section-builder.ts` Key features: + - 14 filter fields with various operators - Live preview functionality - Search + multi-select for manual book addition @@ -97,6 +101,7 @@ Key features: ## Example Use Cases ### Sci-Fi Favorites + ```json { "rules": [ @@ -110,6 +115,7 @@ Key features: ``` ### High Rated Books + ```json { "rules": [ @@ -123,6 +129,7 @@ Key features: ``` ### Long Books (Manual Selection) + ```json { "manual_book_ids": ["uuid1", "uuid2", "uuid3"] @@ -130,6 +137,7 @@ Key features: ``` ### Recently Finished Audiobooks + ```json { "rules": [ diff --git a/docs/developer/api/dashboard.md b/docs/developer/api/dashboard.md index 7b19264..0c0cfdc 100644 --- a/docs/developer/api/dashboard.md +++ b/docs/developer/api/dashboard.md @@ -10,26 +10,27 @@ Retrieve all dashboard sections for a specific library, including system collect ### Query Parameters -| Parameter | Type | Required | Description | -|-----------|--------|----------|-----------------------------------------------| -| library_id| string | Yes | Library UUID to fetch sections for | -| limit | number | No | Items per section (default: 20, max: 100) | +| Parameter | Type | Required | Description | +| ---------- | ------ | -------- | ----------------------------------------- | +| library_id | string | Yes | Library UUID to fetch sections for | +| limit | number | No | Items per section (default: 20, max: 100) | ### Response Returns array of sections in user's customized order (respects `collection_order` and `hidden_collections` preferences). **Section Types**: + - `is_system: true`: System collections (4 pre-seeded defaults) - `is_system: false`: User-created collections with `show_on_dashboard: true` **System Collections**: -| ID | Title | Icon | Description | +| ID | Title | Icon | Description | |-----------------|------------------|------|--------------------------------------------------| -| continue-reading| Continue Reading | 📖 | Books with progress > 0% and < 100% | -| recently-added | Recently Added | 🆕 | Newest items in library | -| recently-read | Recently Read | ✅ | Books with progress = 100% | -| not-started | Not Started | 📕 | Books with no reading progress | +| continue-reading| Continue Reading | 📖 | Books with progress > 0% and < 100% | +| recently-added | Recently Added | 🆕 | Newest items in library | +| recently-read | Recently Read | ✅ | Books with progress = 100% | +| not-started | Not Started | 📕 | Books with no reading progress | ### Example Response @@ -115,6 +116,7 @@ Reset a system collection to its default state (removes user customizations). ``` Valid `collection_name` values: + - `continue-reading` - `recently-added` - `recently-read` @@ -130,12 +132,12 @@ Valid `collection_name` values: ### Error Responses -| Status | Description | -|--------|--------------------------------| -| 400 | Missing library_id | -| 400 | Invalid library_id | -| 400 | Invalid collection_name | -| 401 | Unauthorized | -| 500 | Failed to load sections | -| 500 | Failed to save preferences | -| 500 | Failed to restore collection | +| Status | Description | +| ------ | ---------------------------- | +| 400 | Missing library_id | +| 400 | Invalid library_id | +| 400 | Invalid collection_name | +| 401 | Unauthorized | +| 500 | Failed to load sections | +| 500 | Failed to save preferences | +| 500 | Failed to restore collection | diff --git a/docs/developer/api/devices/add_to_shelf.md b/docs/developer/api/devices/add_to_shelf.md index 4692d3b..057a681 100644 --- a/docs/developer/api/devices/add_to_shelf.md +++ b/docs/developer/api/devices/add_to_shelf.md @@ -8,15 +8,15 @@ Add a media item to a device's shelf (Kobo reading shelf). ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | Device UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| media_item_id | string (UUID) | Yes | Media item UUID to add to shelf | +| Field | Type | Required | Description | +| ------------- | ------------- | -------- | ------------------------------- | +| media_item_id | string (UUID) | Yes | Media item UUID to add to shelf | ### Example Request @@ -38,9 +38,9 @@ Add a media item to a device's shelf (Kobo reading shelf). ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 404 | Device or media item not found | -| 409 | Item already on shelf | +| Code | Description | +| ---- | ------------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 404 | Device or media item not found | +| 409 | Item already on shelf | diff --git a/docs/developer/api/devices/approve_device_registration.md b/docs/developer/api/devices/approve_device_registration.md index a9c6a5c..77fb659 100644 --- a/docs/developer/api/devices/approve_device_registration.md +++ b/docs/developer/api/devices/approve_device_registration.md @@ -7,15 +7,15 @@ Approve a pending device registration request. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| registration_id | string (UUID) | Yes | Registration request UUID | +| Parameter | Type | Required | Description | +| --------------- | ------------- | -------- | ------------------------- | +| registration_id | string (UUID) | Yes | Registration request UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -30,7 +30,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... { "message": "device approved successfully", "device_name": "string", - "device_type": "string", + "device_type": "string", "registration_id": "uuid", "approved": true } @@ -38,9 +38,9 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Registration not found | -| 400 | Registration already processed | +| Code | Description | +| ---- | ----------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Registration not found | +| 400 | Registration already processed | diff --git a/docs/developer/api/devices/clear_shelf.md b/docs/developer/api/devices/clear_shelf.md index 8f73db7..2199cfd 100644 --- a/docs/developer/api/devices/clear_shelf.md +++ b/docs/developer/api/devices/clear_shelf.md @@ -7,15 +7,15 @@ Remove all items from a device's shelf. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | Device UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,7 +30,7 @@ Shelf cleared successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Device not found | diff --git a/docs/developer/api/devices/delete_device.md b/docs/developer/api/devices/delete_device.md index 4896dfd..35080ca 100644 --- a/docs/developer/api/devices/delete_device.md +++ b/docs/developer/api/devices/delete_device.md @@ -7,15 +7,15 @@ Delete a device and revoke its access. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | Device UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,8 +30,8 @@ Device deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | Device does not belong to user | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------------ | +| 401 | Invalid or expired token | +| 403 | Device does not belong to user | +| 404 | Device not found | diff --git a/docs/developer/api/devices/get_devices.md b/docs/developer/api/devices/get_devices.md index 8ee9d0d..a7fef51 100644 --- a/docs/developer/api/devices/get_devices.md +++ b/docs/developer/api/devices/get_devices.md @@ -8,9 +8,9 @@ Check device registration status or get device details. ## Request Body (Status Check) -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| registration_id | string | Yes | Registration UUID | +| Field | Type | Required | Description | +| --------------- | ------ | -------- | ----------------- | +| registration_id | string | Yes | Registration UUID | ### Example Request (Status Check) @@ -52,7 +52,7 @@ Check device registration status or get device details. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token (for device details) | -| 404 | Device or registration not found | +| Code | Description | +| ---- | --------------------------------------------- | +| 401 | Invalid or expired token (for device details) | +| 404 | Device or registration not found | diff --git a/docs/developer/api/devices/get_shelf.md b/docs/developer/api/devices/get_shelf.md index cfa2191..ed9cc83 100644 --- a/docs/developer/api/devices/get_shelf.md +++ b/docs/developer/api/devices/get_shelf.md @@ -7,15 +7,15 @@ Get all items on a device's shelf. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | Device UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -44,7 +44,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Device not found | diff --git a/docs/developer/api/devices/list_devices.md b/docs/developer/api/devices/list_devices.md index 722dfa9..404d0cf 100644 --- a/docs/developer/api/devices/list_devices.md +++ b/docs/developer/api/devices/list_devices.md @@ -7,9 +7,9 @@ Retrieve all devices registered to the current user. ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -39,6 +39,6 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/devices/list_pending_registrations.md b/docs/developer/api/devices/list_pending_registrations.md index a0a373e..99e6f29 100644 --- a/docs/developer/api/devices/list_pending_registrations.md +++ b/docs/developer/api/devices/list_pending_registrations.md @@ -7,9 +7,9 @@ List all pending device registration requests. ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -37,7 +37,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | +| Code | Description | +| ---- | ----------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | diff --git a/docs/developer/api/devices/register_device.md b/docs/developer/api/devices/register_device.md index 0dfb7da..644fad9 100644 --- a/docs/developer/api/devices/register_device.md +++ b/docs/developer/api/devices/register_device.md @@ -8,11 +8,11 @@ Register a new device for sync. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| device_name | string | Yes | Device name | -| device_type | string | Yes | Device type: kobo, koreader, web, mobile | -| device_identifier | string | Yes | Hardware-specific ID | +| Field | Type | Required | Description | +| ----------------- | ------ | -------- | ---------------------------------------- | +| device_name | string | Yes | Device name | +| device_type | string | Yes | Device type: kobo, koreader, web, mobile | +| device_identifier | string | Yes | Hardware-specific ID | ### Example Request @@ -38,7 +38,7 @@ Register a new device for sync. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid device data | -| 409 | Device already registered | +| Code | Description | +| ---- | ------------------------- | +| 400 | Invalid device data | +| 409 | Device already registered | diff --git a/docs/developer/api/devices/reject_device_registration.md b/docs/developer/api/devices/reject_device_registration.md index a8d2249..bac1f40 100644 --- a/docs/developer/api/devices/reject_device_registration.md +++ b/docs/developer/api/devices/reject_device_registration.md @@ -7,15 +7,15 @@ Reject a pending device registration request. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| registration_id | string (UUID) | Yes | Registration request UUID | +| Parameter | Type | Required | Description | +| --------------- | ------------- | -------- | ------------------------- | +| registration_id | string (UUID) | Yes | Registration request UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -34,9 +34,9 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Registration not found | -| 400 | Registration already processed | +| Code | Description | +| ---- | ----------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Registration not found | +| 400 | Registration already processed | diff --git a/docs/developer/api/devices/remove_from_shelf.md b/docs/developer/api/devices/remove_from_shelf.md index bb6d129..b75ea6a 100644 --- a/docs/developer/api/devices/remove_from_shelf.md +++ b/docs/developer/api/devices/remove_from_shelf.md @@ -8,15 +8,15 @@ Remove a media item from a device's shelf. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | Device UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| media_item_id | string (UUID) | Yes | Media item UUID to remove from shelf | +| Field | Type | Required | Description | +| ------------- | ------------- | -------- | ------------------------------------ | +| media_item_id | string (UUID) | Yes | Media item UUID to remove from shelf | ### Example Request @@ -32,8 +32,8 @@ Item removed from shelf successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 404 | Device or shelf item not found | +| Code | Description | +| ---- | ------------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 404 | Device or shelf item not found | diff --git a/docs/developer/api/devices/revoke_device.md b/docs/developer/api/devices/revoke_device.md index ee81531..ba9765b 100644 --- a/docs/developer/api/devices/revoke_device.md +++ b/docs/developer/api/devices/revoke_device.md @@ -7,15 +7,15 @@ Revoke access to a device. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| device_id | string | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ----------- | +| device_id | string | Yes | Device UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,8 +30,8 @@ Device revoked successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not own this device | -| 404 | Device not found | +| Code | Description | +| ---- | ----------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not own this device | +| 404 | Device not found | diff --git a/docs/developer/api/devices/update_device.md b/docs/developer/api/devices/update_device.md index 711d87f..6f72300 100644 --- a/docs/developer/api/devices/update_device.md +++ b/docs/developer/api/devices/update_device.md @@ -8,16 +8,16 @@ Update a device's information. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| id | string (UUID) | Yes | Device UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| name | string | No | Device display name | -| device_type | string | No | Device type (kobo, koreader, etc.) | +| Field | Type | Required | Description | +| ----------- | ------ | -------- | ---------------------------------- | +| name | string | No | Device display name | +| device_type | string | No | Device type (kobo, koreader, etc.) | ### Example Request @@ -44,9 +44,9 @@ Update a device's information. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 403 | Device does not belong to user | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 403 | Device does not belong to user | +| 404 | Device not found | diff --git a/docs/developer/api/highlights/create_highlight.md b/docs/developer/api/highlights/create_highlight.md index 839c275..bdee97d 100644 --- a/docs/developer/api/highlights/create_highlight.md +++ b/docs/developer/api/highlights/create_highlight.md @@ -8,20 +8,20 @@ Create a new highlight for a media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| selection_text | string | Yes | Highlighted text | -| start_position | string | No | Start position (e.g., epubcfi) | -| end_position | string | No | End position (e.g., epubcfi) | -| color | string | No | Highlight color (hex, default: "#ffff00") | -| percentage_start | float | No | Start percentage (0-1) | -| percentage_end | float | No | End percentage (0-1) | +| Field | Type | Required | Description | +| ---------------- | ------ | -------- | ----------------------------------------- | +| selection_text | string | Yes | Highlighted text | +| start_position | string | No | Start position (e.g., epubcfi) | +| end_position | string | No | End position (e.g., epubcfi) | +| color | string | No | Highlight color (hex, default: "#ffff00") | +| percentage_start | float | No | Start percentage (0-1) | +| percentage_end | float | No | End percentage (0-1) | ### Example Request @@ -55,8 +55,8 @@ Create a new highlight for a media item. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid highlight data | -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid highlight data | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/highlights/delete_highlight.md b/docs/developer/api/highlights/delete_highlight.md index 36c566f..f844d0c 100644 --- a/docs/developer/api/highlights/delete_highlight.md +++ b/docs/developer/api/highlights/delete_highlight.md @@ -7,15 +7,15 @@ Delete a highlight. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| highlight_id | string | Yes | Highlight UUID | +| Parameter | Type | Required | Description | +| ------------ | ------ | -------- | -------------- | +| highlight_id | string | Yes | Highlight UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,8 +30,8 @@ Highlight deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not own this highlight | -| 404 | Highlight not found | +| Code | Description | +| ---- | -------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not own this highlight | +| 404 | Highlight not found | diff --git a/docs/developer/api/highlights/get_highlights.md b/docs/developer/api/highlights/get_highlights.md index bb7716f..e0b00fd 100644 --- a/docs/developer/api/highlights/get_highlights.md +++ b/docs/developer/api/highlights/get_highlights.md @@ -7,15 +7,15 @@ Retrieve all highlights for a specific media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -51,7 +51,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/highlights/update_highlight.md b/docs/developer/api/highlights/update_highlight.md index a160cb2..bb12fdc 100644 --- a/docs/developer/api/highlights/update_highlight.md +++ b/docs/developer/api/highlights/update_highlight.md @@ -8,16 +8,16 @@ Update an existing highlight. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| highlight_id | string | Yes | Highlight UUID | +| Parameter | Type | Required | Description | +| ------------ | ------ | -------- | -------------- | +| highlight_id | string | Yes | Highlight UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| selection_text | string | No | Updated highlighted text | -| color | string | No | Updated highlight color (hex) | +| Field | Type | Required | Description | +| -------------- | ------ | -------- | ----------------------------- | +| selection_text | string | No | Updated highlighted text | +| color | string | No | Updated highlight color (hex) | ### Example Request @@ -43,9 +43,9 @@ Update an existing highlight. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid highlight data | -| 401 | Invalid or expired token | -| 403 | User does not own this highlight | -| 404 | Highlight not found | +| Code | Description | +| ---- | -------------------------------- | +| 400 | Invalid highlight data | +| 401 | Invalid or expired token | +| 403 | User does not own this highlight | +| 404 | Highlight not found | diff --git a/docs/developer/api/kobo/analytics_gettests.md b/docs/developer/api/kobo/analytics_gettests.md index 7b7298b..03187e3 100644 --- a/docs/developer/api/kobo/analytics_gettests.md +++ b/docs/developer/api/kobo/analytics_gettests.md @@ -11,9 +11,9 @@ This endpoint requires device authentication (not user JWT). This is a Kobo comp ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| (varies) | object | No | Kobo analytics data (format varies) | +| Field | Type | Required | Description | +| -------- | ------ | -------- | ----------------------------------- | +| (varies) | object | No | Kobo analytics data (format varies) | ### Example Request @@ -33,9 +33,9 @@ This endpoint requires device authentication (not user JWT). This is a Kobo comp ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Device authentication failed | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Device authentication failed | ## Notes diff --git a/docs/developer/api/kobo/bookmark_sync.md b/docs/developer/api/kobo/bookmark_sync.md index e05f0e6..b5c2020 100644 --- a/docs/developer/api/kobo/bookmark_sync.md +++ b/docs/developer/api/kobo/bookmark_sync.md @@ -11,19 +11,19 @@ This endpoint requires device authentication (not user JWT). Kobo devices authen ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| bookmarks | array | Yes | Array of bookmark objects | +| Field | Type | Required | Description | +| --------- | ----- | -------- | ------------------------- | +| bookmarks | array | Yes | Array of bookmark objects | ### Bookmark Object -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| BookmarkID | string | Yes | Unique bookmark ID | -| ContentID | string | Yes | Book content ID | -| StartPosition | integer | Yes | Bookmark position | -| Text | string | No | Bookmark text | -| DateCreated | string | Yes | ISO 8601 timestamp | +| Field | Type | Required | Description | +| ------------- | ------- | -------- | ------------------ | +| BookmarkID | string | Yes | Unique bookmark ID | +| ContentID | string | Yes | Book content ID | +| StartPosition | integer | Yes | Bookmark position | +| Text | string | No | Bookmark text | +| DateCreated | string | Yes | ISO 8601 timestamp | ### Example Request @@ -52,8 +52,8 @@ This endpoint requires device authentication (not user JWT). Kobo devices authen ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Device authentication failed | -| 400 | Invalid request data | -| 404 | Device or book not found | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Device authentication failed | +| 400 | Invalid request data | +| 404 | Device or book not found | diff --git a/docs/developer/api/kobo/initialization.md b/docs/developer/api/kobo/initialization.md index f017341..9c024a7 100644 --- a/docs/developer/api/kobo/initialization.md +++ b/docs/developer/api/kobo/initialization.md @@ -11,11 +11,11 @@ This endpoint requires device authentication (not user JWT). Kobo devices authen ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| X-Device-ID | string | Yes | Device UUID | -| X-Device-Key | string | Yes | Device authentication key | -| X-Kobo-UserKey | string | No | Kobo user key (if available) | +| Header | Type | Required | Description | +| -------------- | ------ | -------- | ---------------------------- | +| X-Device-ID | string | Yes | Device UUID | +| X-Device-Key | string | Yes | Device authentication key | +| X-Kobo-UserKey | string | No | Kobo user key (if available) | ### Example Request @@ -39,10 +39,10 @@ X-Device-Key: device-auth-key ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Device authentication failed | -| 404 | Device not found | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Device authentication failed | +| 404 | Device not found | ## Notes diff --git a/docs/developer/api/kobo/markup_sync.md b/docs/developer/api/kobo/markup_sync.md index 162f993..1ec621b 100644 --- a/docs/developer/api/kobo/markup_sync.md +++ b/docs/developer/api/kobo/markup_sync.md @@ -11,23 +11,23 @@ This endpoint requires device authentication (not user JWT). Kobo devices authen ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| bookmarks | array | Yes | Array of bookmark/markup objects | +| Field | Type | Required | Description | +| --------- | ----- | -------- | -------------------------------- | +| bookmarks | array | Yes | Array of bookmark/markup objects | ### Bookmark Object -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| BookmarkID | string | Yes | Unique bookmark ID | -| ContentID | string | Yes | Book content ID | -| StartPosition | integer | Yes | Highlight start position | -| EndPosition | integer | No | Highlight end position | -| Text | string | No | Highlighted text | -| Annotation | string | No | User annotation | -| DateCreated | string | Yes | ISO 8601 timestamp | -| Chapter | string | No | Chapter title | -| Hidden | boolean | No | Whether bookmark is hidden | +| Field | Type | Required | Description | +| ------------- | ------- | -------- | -------------------------- | +| BookmarkID | string | Yes | Unique bookmark ID | +| ContentID | string | Yes | Book content ID | +| StartPosition | integer | Yes | Highlight start position | +| EndPosition | integer | No | Highlight end position | +| Text | string | No | Highlighted text | +| Annotation | string | No | User annotation | +| DateCreated | string | Yes | ISO 8601 timestamp | +| Chapter | string | No | Chapter title | +| Hidden | boolean | No | Whether bookmark is hidden | ### Example Request @@ -60,8 +60,8 @@ This endpoint requires device authentication (not user JWT). Kobo devices authen ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Device authentication failed | -| 400 | Invalid request data | -| 404 | Device or book not found | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Device authentication failed | +| 400 | Invalid request data | +| 404 | Device or book not found | diff --git a/docs/developer/api/kobo/sync_from_server.md b/docs/developer/api/kobo/sync_from_server.md index 22a9064..4de73f3 100644 --- a/docs/developer/api/kobo/sync_from_server.md +++ b/docs/developer/api/kobo/sync_from_server.md @@ -11,19 +11,16 @@ This endpoint requires device authentication (not user JWT). Kobo devices authen ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| book_ids | array of string | No | Array of ContentIDs to sync | -| full_sync | boolean | No | Whether to perform full sync (default: false) | +| Field | Type | Required | Description | +| --------- | --------------- | -------- | --------------------------------------------- | +| book_ids | array of string | No | Array of ContentIDs to sync | +| full_sync | boolean | No | Whether to perform full sync (default: false) | ### Example Request ```json { - "book_ids": [ - "content-id-1", - "content-id-2" - ], + "book_ids": ["content-id-1", "content-id-2"], "full_sync": false } ``` @@ -53,10 +50,10 @@ This endpoint requires device authentication (not user JWT). Kobo devices authen ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Device authentication failed | -| 404 | Device not found | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Device authentication failed | +| 404 | Device not found | ## Notes diff --git a/docs/developer/api/koreader/get_library.md b/docs/developer/api/koreader/get_library.md index 9ea010d..3aa9c81 100644 --- a/docs/developer/api/koreader/get_library.md +++ b/docs/developer/api/koreader/get_library.md @@ -11,10 +11,10 @@ This endpoint requires device authentication (not user JWT). Devices authenticat ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| X-Device-ID | string | Yes | Device UUID | -| X-Device-Key | string | Yes | Device authentication key | +| Header | Type | Required | Description | +| ------------ | ------ | -------- | ------------------------- | +| X-Device-ID | string | Yes | Device UUID | +| X-Device-Key | string | Yes | Device authentication key | ### Example Request @@ -43,7 +43,7 @@ X-Device-Key: device-auth-key ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Device authentication failed | -| 404 | Device not found | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Device authentication failed | +| 404 | Device not found | diff --git a/docs/developer/api/koreader/get_metadata.md b/docs/developer/api/koreader/get_metadata.md index 01be4a6..c09faba 100644 --- a/docs/developer/api/koreader/get_metadata.md +++ b/docs/developer/api/koreader/get_metadata.md @@ -7,9 +7,9 @@ Get metadata for a book from KOReader device. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| uuid | string (UUID) | Yes | Book UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| uuid | string (UUID) | Yes | Book UUID | ## Device Authentication @@ -17,10 +17,10 @@ This endpoint requires device authentication (not user JWT). Devices authenticat ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| X-Device-ID | string | Yes | Device UUID | -| X-Device-Key | string | Yes | Device authentication key | +| Header | Type | Required | Description | +| ------------ | ------ | -------- | ------------------------- | +| X-Device-ID | string | Yes | Device UUID | +| X-Device-Key | string | Yes | Device authentication key | ### Example Request @@ -45,7 +45,7 @@ X-Device-Key: device-auth-key ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Device authentication failed | -| 404 | Book or device not found | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Device authentication failed | +| 404 | Book or device not found | diff --git a/docs/developer/api/koreader/sync_bookmarks.md b/docs/developer/api/koreader/sync_bookmarks.md index 489a5ca..c7e4eb4 100644 --- a/docs/developer/api/koreader/sync_bookmarks.md +++ b/docs/developer/api/koreader/sync_bookmarks.md @@ -11,23 +11,23 @@ This endpoint requires device authentication (not user JWT). Devices authenticat ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| device_id | string (UUID) | Yes | Device UUID | -| bookmarks | array | Yes | Array of bookmark objects | +| Field | Type | Required | Description | +| --------- | ------------- | -------- | ------------------------- | +| device_id | string (UUID) | Yes | Device UUID | +| bookmarks | array | Yes | Array of bookmark objects | ### Bookmark Object -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| book | string | Yes | Book identifier | -| chapter | string | No | Chapter title | -| page | integer | No | Page number | -| position | float | Yes | Position in document (0-1) | -| notes | string | No | Bookmark notes | -| highlighted_text | string | No | Highlighted text | -| time | string | Yes | ISO 8601 timestamp | -| created_at | string | Yes | ISO 8601 timestamp | +| Field | Type | Required | Description | +| ---------------- | ------- | -------- | -------------------------- | +| book | string | Yes | Book identifier | +| chapter | string | No | Chapter title | +| page | integer | No | Page number | +| position | float | Yes | Position in document (0-1) | +| notes | string | No | Bookmark notes | +| highlighted_text | string | No | Highlighted text | +| time | string | Yes | ISO 8601 timestamp | +| created_at | string | Yes | ISO 8601 timestamp | ### Example Request @@ -60,8 +60,8 @@ This endpoint requires device authentication (not user JWT). Devices authenticat ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Device authentication failed | -| 400 | Invalid request data | -| 404 | Device not found | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Device authentication failed | +| 400 | Invalid request data | +| 404 | Device not found | diff --git a/docs/developer/api/koreader/sync_progress.md b/docs/developer/api/koreader/sync_progress.md index 3bbc742..dd45f51 100644 --- a/docs/developer/api/koreader/sync_progress.md +++ b/docs/developer/api/koreader/sync_progress.md @@ -11,21 +11,21 @@ This endpoint requires device authentication (not user JWT). Devices authenticat ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| device_id | string (UUID) | Yes | Device UUID | -| progress | array | Yes | Array of progress objects | +| Field | Type | Required | Description | +| --------- | ------------- | -------- | ------------------------- | +| device_id | string (UUID) | Yes | Device UUID | +| progress | array | Yes | Array of progress objects | ### Progress Object -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| book | string | Yes | Book identifier (filename or UUID) | -| percent | float | Yes | Progress percentage (0-100) | -| page | integer | No | Current page number | -| total_pages | integer | No | Total pages in document | -| date_read | string | No | ISO 8601 timestamp of last read | -| updated_at | string | Yes | ISO 8601 timestamp | +| Field | Type | Required | Description | +| ----------- | ------- | -------- | ---------------------------------- | +| book | string | Yes | Book identifier (filename or UUID) | +| percent | float | Yes | Progress percentage (0-100) | +| page | integer | No | Current page number | +| total_pages | integer | No | Total pages in document | +| date_read | string | No | ISO 8601 timestamp of last read | +| updated_at | string | Yes | ISO 8601 timestamp | ### Example Request @@ -56,8 +56,8 @@ This endpoint requires device authentication (not user JWT). Devices authenticat ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Device authentication failed | -| 400 | Invalid request data | -| 404 | Device not found | +| Code | Description | +| ---- | ---------------------------- | +| 401 | Device authentication failed | +| 400 | Invalid request data | +| 404 | Device not found | diff --git a/docs/developer/api/libraries/add_library_folder.md b/docs/developer/api/libraries/add_library_folder.md index 808de00..90f7969 100644 --- a/docs/developer/api/libraries/add_library_folder.md +++ b/docs/developer/api/libraries/add_library_folder.md @@ -8,15 +8,15 @@ Add a folder to an existing library (Admin only). ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| library_id | string | Yes | Library UUID | +| Parameter | Type | Required | Description | +| ---------- | ------ | -------- | ------------ | +| library_id | string | Yes | Library UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| folder_path | string | Yes | Absolute path to folder | +| Field | Type | Required | Description | +| ----------- | ------ | -------- | ----------------------- | +| folder_path | string | Yes | Absolute path to folder | ### Example Request @@ -39,9 +39,9 @@ Add a folder to an existing library (Admin only). ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid folder path | -| 401 | Invalid or expired token | -| 403 | User is not an admin | -| 404 | Library not found | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid folder path | +| 401 | Invalid or expired token | +| 403 | User is not an admin | +| 404 | Library not found | diff --git a/docs/developer/api/libraries/browse-folders.md b/docs/developer/api/libraries/browse-folders.md index bd139b5..4bb9921 100644 --- a/docs/developer/api/libraries/browse-folders.md +++ b/docs/developer/api/libraries/browse-folders.md @@ -7,15 +7,15 @@ Browse server directories for folder selection in library management. ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| path | string | No | Directory path to browse (default: "/") | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------------------------------- | +| path | string | No | Directory path to browse (default: "/") | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (admin only) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------------------- | +| Authorization | string | Yes | Bearer token (admin only) | ### Example Request @@ -36,13 +36,13 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Path traversal attempt or invalid path | -| 401 | Invalid or expired token | -| 403 | User is not an admin | -| 400 | Path does not exist | -| 400 | Path is not a directory | +| Code | Description | +| ---- | -------------------------------------- | +| 400 | Path traversal attempt or invalid path | +| 401 | Invalid or expired token | +| 403 | User is not an admin | +| 400 | Path does not exist | +| 400 | Path is not a directory | ## Security diff --git a/docs/developer/api/libraries/create_library.md b/docs/developer/api/libraries/create_library.md index d4e3d40..373574e 100644 --- a/docs/developer/api/libraries/create_library.md +++ b/docs/developer/api/libraries/create_library.md @@ -8,11 +8,11 @@ Create a new library (Admin only). ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| name | string | Yes | Library name | -| description | string | No | Library description | -| type | string | Yes | Library type (e.g., "ebooks", "comics", "audiobooks") | +| Field | Type | Required | Description | +| ----------- | ------ | -------- | ----------------------------------------------------- | +| name | string | Yes | Library name | +| description | string | No | Library description | +| type | string | Yes | Library type (e.g., "ebooks", "comics", "audiobooks") | ### Example Request @@ -38,8 +38,8 @@ Create a new library (Admin only). ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid input data | -| 401 | Invalid or expired token | -| 403 | User is not an admin | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid input data | +| 401 | Invalid or expired token | +| 403 | User is not an admin | diff --git a/docs/developer/api/libraries/delete_library.md b/docs/developer/api/libraries/delete_library.md index 3820882..ed477c8 100644 --- a/docs/developer/api/libraries/delete_library.md +++ b/docs/developer/api/libraries/delete_library.md @@ -7,15 +7,15 @@ Delete a library and all associated data. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Library UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------ | +| id | string (UUID) | Yes | Library UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -30,8 +30,8 @@ Library deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | +| Code | Description | +| ---- | ----------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | diff --git a/docs/developer/api/libraries/delete_library_folder.md b/docs/developer/api/libraries/delete_library_folder.md index 33223f4..18f6cb8 100644 --- a/docs/developer/api/libraries/delete_library_folder.md +++ b/docs/developer/api/libraries/delete_library_folder.md @@ -8,15 +8,15 @@ Delete a folder from a library. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Library UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------ | +| id | string (UUID) | Yes | Library UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| folder_path | string | Yes | Absolute path to the folder to delete | +| Field | Type | Required | Description | +| ----------- | ------ | -------- | ------------------------------------- | +| folder_path | string | Yes | Absolute path to the folder to delete | ### Example Request @@ -32,9 +32,9 @@ Library folder deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library or folder not found | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library or folder not found | diff --git a/docs/developer/api/libraries/get_library.md b/docs/developer/api/libraries/get_library.md index 920de0d..3f2866d 100644 --- a/docs/developer/api/libraries/get_library.md +++ b/docs/developer/api/libraries/get_library.md @@ -7,15 +7,15 @@ Retrieve details of a specific library. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| library_id | string | Yes | Library UUID | +| Parameter | Type | Required | Description | +| ---------- | ------ | -------- | ------------ | +| library_id | string | Yes | Library UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -44,8 +44,8 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have access to this library | -| 404 | Library not found | +| Code | Description | +| ---- | ----------------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have access to this library | +| 404 | Library not found | diff --git a/docs/developer/api/libraries/get_library_media_items.md b/docs/developer/api/libraries/get_library_media_items.md index 406aece..a4ef04e 100644 --- a/docs/developer/api/libraries/get_library_media_items.md +++ b/docs/developer/api/libraries/get_library_media_items.md @@ -7,24 +7,24 @@ Get all media items in a specific library. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Library UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------ | +| id | string (UUID) | Yes | Library UUID | ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| limit | integer | No | Maximum number of items to return (default: 50) | -| offset | integer | No | Number of items to skip (default: 0) | -| sort_by | string | No | Sort field (title, created_at, etc.) | -| sort_order | string | No | Sort order (asc, desc) | +| Parameter | Type | Required | Description | +| ---------- | ------- | -------- | ----------------------------------------------- | +| limit | integer | No | Maximum number of items to return (default: 50) | +| offset | integer | No | Number of items to skip (default: 0) | +| sort_by | string | No | Sort field (title, created_at, etc.) | +| sort_order | string | No | Sort order (asc, desc) | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -54,8 +54,8 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | +| Code | Description | +| ---- | ----------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | diff --git a/docs/developer/api/libraries/get_library_stats.md b/docs/developer/api/libraries/get_library_stats.md index f89b096..1effb78 100644 --- a/docs/developer/api/libraries/get_library_stats.md +++ b/docs/developer/api/libraries/get_library_stats.md @@ -7,15 +7,15 @@ Get statistics for a specific library. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Library UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------ | +| id | string (UUID) | Yes | Library UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -43,8 +43,8 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | +| Code | Description | +| ---- | ----------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | diff --git a/docs/developer/api/libraries/get_visible_libraries.md b/docs/developer/api/libraries/get_visible_libraries.md index 493869a..bd85f02 100644 --- a/docs/developer/api/libraries/get_visible_libraries.md +++ b/docs/developer/api/libraries/get_visible_libraries.md @@ -7,9 +7,9 @@ Retrieve all libraries visible to the current user. ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -36,6 +36,6 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/libraries/set_library_visibility.md b/docs/developer/api/libraries/set_library_visibility.md index 2225494..3897f7c 100644 --- a/docs/developer/api/libraries/set_library_visibility.md +++ b/docs/developer/api/libraries/set_library_visibility.md @@ -8,11 +8,11 @@ Set library visibility for a specific user (Admin only). ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| user_id | string | Yes | User UUID | -| library_id | string | Yes | Library UUID | -| is_visible | boolean | Yes | Whether library is visible to user | +| Field | Type | Required | Description | +| ---------- | ------- | -------- | ---------------------------------- | +| user_id | string | Yes | User UUID | +| library_id | string | Yes | Library UUID | +| is_visible | boolean | Yes | Whether library is visible to user | ### Example Request @@ -36,9 +36,9 @@ Set library visibility for a specific user (Admin only). ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid input data | -| 401 | Invalid or expired token | -| 403 | User is not an admin | -| 404 | User or library not found | +| Code | Description | +| ---- | ------------------------- | +| 400 | Invalid input data | +| 401 | Invalid or expired token | +| 403 | User is not an admin | +| 404 | User or library not found | diff --git a/docs/developer/api/libraries/update_library.md b/docs/developer/api/libraries/update_library.md index d216858..2dd8da5 100644 --- a/docs/developer/api/libraries/update_library.md +++ b/docs/developer/api/libraries/update_library.md @@ -8,16 +8,16 @@ Update a library's information. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Library UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------ | +| id | string (UUID) | Yes | Library UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| name | string | No | Library name | -| library_type_id | integer | No | Library type ID | +| Field | Type | Required | Description | +| --------------- | ------- | -------- | --------------- | +| name | string | No | Library name | +| library_type_id | integer | No | Library type ID | ### Example Request @@ -42,9 +42,9 @@ Update a library's information. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | diff --git a/docs/developer/api/media-items/bulk_delete_media_items.md b/docs/developer/api/media-items/bulk_delete_media_items.md index 2a409f2..1f89955 100644 --- a/docs/developer/api/media-items/bulk_delete_media_items.md +++ b/docs/developer/api/media-items/bulk_delete_media_items.md @@ -8,9 +8,9 @@ Delete multiple media items at once (supports ebooks, comics, manga). ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| media_item_ids | array of UUID | Yes | Array of media item UUIDs to delete | +| Field | Type | Required | Description | +| -------------- | ------------- | -------- | ----------------------------------- | +| media_item_ids | array of UUID | Yes | Array of media item UUIDs to delete | ### Example Request @@ -51,24 +51,24 @@ Delete multiple media items at once (supports ebooks, comics, manga). ## Response Fields -| Field | Type | Description | -|-------|------|-------------| -| results | array | Individual result for each media item | -| results[].media_item_id | string | UUID of the media item | -| results[].status | string | "success" or "error" | -| results[].error | string | Error message (only present if status is "error") | -| total | number | Total number of media items processed | -| deleted | number | Number of media items successfully deleted | -| failed | number | Number of media items that failed to delete | +| Field | Type | Description | +| ----------------------- | ------ | ------------------------------------------------- | +| results | array | Individual result for each media item | +| results[].media_item_id | string | UUID of the media item | +| results[].status | string | "success" or "error" | +| results[].error | string | Error message (only present if status is "error") | +| total | number | Total number of media items processed | +| deleted | number | Number of media items successfully deleted | +| failed | number | Number of media items that failed to delete | ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data or empty media_item_ids array | -| 401 | Invalid or expired token | -| 403 | User does not have permission | -| 500 | Server error during deletion | +| Code | Description | +| ---- | -------------------------------------------------- | +| 400 | Invalid request data or empty media_item_ids array | +| 401 | Invalid or expired token | +| 403 | User does not have permission | +| 500 | Server error during deletion | ## Notes diff --git a/docs/developer/api/media-items/bulk_update_media_items.md b/docs/developer/api/media-items/bulk_update_media_items.md index 7980621..58a23b0 100644 --- a/docs/developer/api/media-items/bulk_update_media_items.md +++ b/docs/developer/api/media-items/bulk_update_media_items.md @@ -8,21 +8,21 @@ Update multiple media items at once (supports ebooks, comics, manga). ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| media_item_updates | array of objects | Yes | Array of update operations | -| media_item_updates[].media_item_id | string (UUID) | Yes | Media item UUID to update | -| media_item_updates[].updates | object | Yes | Fields to update | +| Field | Type | Required | Description | +| ---------------------------------- | ---------------- | -------- | -------------------------- | +| media_item_updates | array of objects | Yes | Array of update operations | +| media_item_updates[].media_item_id | string (UUID) | Yes | Media item UUID to update | +| media_item_updates[].updates | object | Yes | Fields to update | ### Update Fields -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| title | string | No | Updated title | -| author | string | No | Updated author | -| genre | string | No | Updated genre | -| language | string | No | Updated language (ISO 639-1 code) | -| tags | array of strings | No | Updated tags (auto-normalized) | +| Field | Type | Required | Description | +| -------- | ---------------- | -------- | --------------------------------- | +| title | string | No | Updated title | +| author | string | No | Updated author | +| genre | string | No | Updated genre | +| language | string | No | Updated language (ISO 639-1 code) | +| tags | array of strings | No | Updated tags (auto-normalized) | ### Example Request @@ -71,15 +71,15 @@ Update multiple media items at once (supports ebooks, comics, manga). ## Response Fields -| Field | Type | Description | -|-------|------|-------------| -| results | array | Individual result for each media item | -| results[].media_item_id | string | UUID of the media item | -| results[].status | string | "success" or "error" | -| results[].error | string | Error message (only present if status is "error") | -| total | number | Total number of media items processed | -| updated | number | Number of media items successfully updated | -| failed | number | Number of media items that failed to update | +| Field | Type | Description | +| ----------------------- | ------ | ------------------------------------------------- | +| results | array | Individual result for each media item | +| results[].media_item_id | string | UUID of the media item | +| results[].status | string | "success" or "error" | +| results[].error | string | Error message (only present if status is "error") | +| total | number | Total number of media items processed | +| updated | number | Number of media items successfully updated | +| failed | number | Number of media items that failed to update | ## Tag and Contributor Normalization @@ -90,13 +90,13 @@ The backend automatically normalizes tags: ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data or empty media_item_updates array | -| 401 | Invalid or expired token | -| 403 | User does not have permission | -| 404 | One or more media items not found | -| 500 | Server error during update | +| Code | Description | +| ---- | ------------------------------------------------------ | +| 400 | Invalid request data or empty media_item_updates array | +| 401 | Invalid or expired token | +| 403 | User does not have permission | +| 404 | One or more media items not found | +| 500 | Server error during update | ## Notes diff --git a/docs/developer/api/media-items/create_media_item.md b/docs/developer/api/media-items/create_media_item.md index d106eeb..46c67e7 100644 --- a/docs/developer/api/media-items/create_media_item.md +++ b/docs/developer/api/media-items/create_media_item.md @@ -18,31 +18,31 @@ See [Library API documentation](../libraries/) for more details. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| library_id | string (UUID) | Yes | Library UUID to add the media item to | -| title | string | Yes | Media item title (1-500 characters) | -| author | string | No | Author name | -| isbn | string | No | ISBN number | -| description | string | No | Description or summary | -| file_path | string | Yes | Path to the media file | -| file_size | integer | Yes | Size of the file in bytes | -| mime_type | string | Yes | MIME type of the file | -| cover_image_path | string | No | Path to the cover image | -| series | string | No | Series name | -| series_number | integer | No | Number in the series | -| tags | array of strings | No | Tags (auto-normalized) | -| asin | string | No | Amazon ASIN | -| date_published | string | No | Publication date | -| publisher | string | No | Publisher name | -| contributors | array of strings | No | Contributors (auto-normalized) | +| Field | Type | Required | Description | +| ---------------- | ---------------- | -------- | ------------------------------------- | +| library_id | string (UUID) | Yes | Library UUID to add the media item to | +| title | string | Yes | Media item title (1-500 characters) | +| author | string | No | Author name | +| isbn | string | No | ISBN number | +| description | string | No | Description or summary | +| file_path | string | Yes | Path to the media file | +| file_size | integer | Yes | Size of the file in bytes | +| mime_type | string | Yes | MIME type of the file | +| cover_image_path | string | No | Path to the cover image | +| series | string | No | Series name | +| series_number | integer | No | Number in the series | +| tags | array of strings | No | Tags (auto-normalized) | +| asin | string | No | Amazon ASIN | +| date_published | string | No | Publication date | +| publisher | string | No | Publisher name | +| contributors | array of strings | No | Contributors (auto-normalized) | ## Tag/Contributor Normalization Tags and contributors are automatically normalized: - **Tags**: Titlecased, punctuation preserved, case-insensitive deduplication -- **Contributors**: Original casing and punctuation preserved, case-insensitive deduplication +- **Contributors**: Original casing and punctuation preserved, case-insensitive deduplication - **Search fields**: Auto-generated for case-insensitive search ### Example Request @@ -92,12 +92,12 @@ Tags and contributors are automatically normalized: ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data OR library has no folders | -| 401 | Invalid or expired token | -| 403 | User is not an admin | -| 404 | Library not found | +| Code | Description | +| ---- | ---------------------------------------------- | +| 400 | Invalid request data OR library has no folders | +| 401 | Invalid or expired token | +| 403 | User is not an admin | +| 404 | Library not found | ### 400 - Library Has No Folders diff --git a/docs/developer/api/media-items/delete_media_item.md b/docs/developer/api/media-items/delete_media_item.md index a57788b..01de155 100644 --- a/docs/developer/api/media-items/delete_media_item.md +++ b/docs/developer/api/media-items/delete_media_item.md @@ -7,15 +7,15 @@ Delete a media item from the library (Admin only). ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,8 +30,8 @@ Media item deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User is not an admin | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 403 | User is not an admin | +| 404 | Media item not found | diff --git a/docs/developer/api/media-items/download_media_item.md b/docs/developer/api/media-items/download_media_item.md index 58f55aa..f3b7347 100644 --- a/docs/developer/api/media-items/download_media_item.md +++ b/docs/developer/api/media-items/download_media_item.md @@ -8,24 +8,25 @@ Download a media item file (EPUB, PDF, etc.) from the Bookhoard server. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| uuid | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| uuid | string | Yes | Media item UUID | ## Response **Success (200 OK)**: Binary file data **Response Headers**: + - `Content-Type`: `application/epub+zip`, `application/pdf`, or appropriate MIME type - `Content-Disposition`: `attachment; filename="filename.epub"` ## Error Responses -| Code | Description | -|------|-------------| -| 404 | Media item not found | -| 500 | Server error during file download | +| Code | Description | +| ---- | --------------------------------- | +| 404 | Media item not found | +| 500 | Server error during file download | ## Example diff --git a/docs/developer/api/media-items/filter_sort_media_items.md b/docs/developer/api/media-items/filter_sort_media_items.md index 27a90b7..0aa8433 100644 --- a/docs/developer/api/media-items/filter_sort_media_items.md +++ b/docs/developer/api/media-items/filter_sort_media_items.md @@ -8,18 +8,18 @@ Filter and sort media items with advanced criteria. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| library_id | string | Yes | Library UUID | -| author_filter | string | No | Filter by author name | -| series_filter | string | No | Filter by series name | -| genre_filter | string | No | Filter by genre | -| year_min | integer | No | Minimum copyright year | -| year_max | integer | No | Maximum copyright year | -| has_cover | boolean | No | Filter by cover image existence | -| sort | string | No | Sort field and order (e.g., "title ASC", "created_at DESC") | -| limit | integer | No | Number of results (default 20) | -| offset | integer | No | Number to skip | +| Field | Type | Required | Description | +| ------------- | ------- | -------- | ----------------------------------------------------------- | +| library_id | string | Yes | Library UUID | +| author_filter | string | No | Filter by author name | +| series_filter | string | No | Filter by series name | +| genre_filter | string | No | Filter by genre | +| year_min | integer | No | Minimum copyright year | +| year_max | integer | No | Maximum copyright year | +| has_cover | boolean | No | Filter by cover image existence | +| sort | string | No | Sort field and order (e.g., "title ASC", "created_at DESC") | +| limit | integer | No | Number of results (default 20) | +| offset | integer | No | Number to skip | ### Example Request @@ -56,8 +56,8 @@ Filter and sort media items with advanced criteria. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid filter parameters | -| 401 | Invalid or expired token | -| 403 | User does not have access to this library | +| Code | Description | +| ---- | ----------------------------------------- | +| 400 | Invalid filter parameters | +| 401 | Invalid or expired token | +| 403 | User does not have access to this library | diff --git a/docs/developer/api/media-items/get_media_item.md b/docs/developer/api/media-items/get_media_item.md index f546429..2afcd71 100644 --- a/docs/developer/api/media-items/get_media_item.md +++ b/docs/developer/api/media-items/get_media_item.md @@ -7,15 +7,15 @@ Retrieve details of a specific media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -37,13 +37,13 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... "file_size": 1024000, "mime_type": "application/epub+zip", "cover_image_path": "/path/to/cover.jpg", - "series": "Series Name", - "series_number": 1, - "tags": ["sci-fi", "space opera"], - "tags_search": ["sci fi", "space opera"], - "contributors": ["Author Name", "ACME CORP."], - "contributors_search": ["author name", "acme corp"], - "language": "en", + "series": "Series Name", + "series_number": 1, + "tags": ["sci-fi", "space opera"], + "tags_search": ["sci fi", "space opera"], + "contributors": ["Author Name", "ACME CORP."], + "contributors_search": ["author name", "acme corp"], + "language": "en", "page_count": 350, "genre": "Science Fiction", "copyright_year": 2023, @@ -53,8 +53,8 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have access to this media item | -| 404 | Media item not found | +| Code | Description | +| ---- | -------------------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have access to this media item | +| 404 | Media item not found | diff --git a/docs/developer/api/media-items/list_media_items.md b/docs/developer/api/media-items/list_media_items.md index 89da7d7..fbc5ff1 100644 --- a/docs/developer/api/media-items/list_media_items.md +++ b/docs/developer/api/media-items/list_media_items.md @@ -7,17 +7,17 @@ Retrieve a paginated list of media items from a library. ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| library_id | string | Yes | Library UUID | -| limit | integer | No | Number of items to return (max 100, default 20) | -| offset | integer | No | Number of items to skip | +| Parameter | Type | Required | Description | +| ---------- | ------- | -------- | ----------------------------------------------- | +| library_id | string | Yes | Library UUID | +| limit | integer | No | Number of items to return (max 100, default 20) | +| offset | integer | No | Number of items to skip | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -41,13 +41,13 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... "file_size": 1024000, "mime_type": "application/epub+zip", "cover_image_path": "/path/to/cover.jpg", - "series": "Series Name", - "series_number": 1, - "tags": ["sci-fi", "space opera"], - "tags_search": ["sci fi", "space opera"], - "contributors": ["Author Name", "ACME CORP."], - "contributors_search": ["author name", "acme corp"], - "language": "en", + "series": "Series Name", + "series_number": 1, + "tags": ["sci-fi", "space opera"], + "tags_search": ["sci fi", "space opera"], + "contributors": ["Author Name", "ACME CORP."], + "contributors_search": ["author name", "acme corp"], + "language": "en", "page_count": 350, "genre": "Science Fiction", "copyright_year": 2023, @@ -60,8 +60,8 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid query parameters | -| 401 | Invalid or expired token | -| 403 | User does not have access to this library | +| Code | Description | +| ---- | ----------------------------------------- | +| 400 | Invalid query parameters | +| 401 | Invalid or expired token | +| 403 | User does not have access to this library | diff --git a/docs/developer/api/media-items/search_media_items.md b/docs/developer/api/media-items/search_media_items.md index 0f8456b..b797ccc 100644 --- a/docs/developer/api/media-items/search_media_items.md +++ b/docs/developer/api/media-items/search_media_items.md @@ -5,6 +5,7 @@ Search for media items by title, author, series, tags, or contributors. **Note:** Search is case-insensitive and punctuation-agnostic. The search query is matched against normalized tags_search and contributors_search fields, allowing users to find matches regardless of casing or punctuation. Examples: + - Search "acme corp" finds items with "ACME CORP." or "Acme Corp" - Search "oreilly" finds items with "O'Reilly Media" or "OReilly Media" - Search "science fiction" finds items with "Science-Fiction" or "science-fiction" @@ -14,17 +15,17 @@ Examples: ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| q | string | Yes | Search query (minimum 2 characters) | -| limit | integer | No | Number of results (default 20) | -| offset | integer | No | Number to skip | +| Parameter | Type | Required | Description | +| --------- | ------- | -------- | ----------------------------------- | +| q | string | Yes | Search query (minimum 2 characters) | +| limit | integer | No | Number of results (default 20) | +| offset | integer | No | Number to skip | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -51,7 +52,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid search query (too short) | -| 401 | Invalid or expired token | +| Code | Description | +| ---- | -------------------------------- | +| 400 | Invalid search query (too short) | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/media-items/update_media_item.md b/docs/developer/api/media-items/update_media_item.md index b7438b4..2732088 100644 --- a/docs/developer/api/media-items/update_media_item.md +++ b/docs/developer/api/media-items/update_media_item.md @@ -8,27 +8,28 @@ Update media item metadata (Admin only). ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | - ## Request Body +## Request Body - | Field | Type | Required | Description | - |--------|------|-----------|-------------| - | title | string | No | Updated title | - | author | string | No | Updated author | - | description | string | No | Updated description | - | series | string | No | Series name | - | series_number | integer | No | Number in series | - | tags | array of string | No | Updated tags (auto-normalized) | - | contributors | array of string | No | Updated contributors (auto-normalized) | - - **Tag/Contributor Normalization:** - - Tags are titlecased and deduplicated (case-insensitive) - - Contributors preserve original casing and punctuation - - Punctuation-preferred deduplication (keeps "ACME CORP." over "acme corp") - - Search fields auto-generated for case-insensitive search +| Field | Type | Required | Description | +| ------------- | --------------- | -------- | -------------------------------------- | +| title | string | No | Updated title | +| author | string | No | Updated author | +| description | string | No | Updated description | +| series | string | No | Series name | +| series_number | integer | No | Number in series | +| tags | array of string | No | Updated tags (auto-normalized) | +| contributors | array of string | No | Updated contributors (auto-normalized) | + +**Tag/Contributor Normalization:** + +- Tags are titlecased and deduplicated (case-insensitive) +- Contributors preserve original casing and punctuation +- Punctuation-preferred deduplication (keeps "ACME CORP." over "acme corp") +- Search fields auto-generated for case-insensitive search ### Example Request @@ -58,9 +59,9 @@ Update media item metadata (Admin only). ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid input data | -| 401 | Invalid or expired token | -| 403 | User is not an admin | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid input data | +| 401 | Invalid or expired token | +| 403 | User is not an admin | +| 404 | Media item not found | diff --git a/docs/developer/api/notes/create_note.md b/docs/developer/api/notes/create_note.md index 8c80ac7..170a391 100644 --- a/docs/developer/api/notes/create_note.md +++ b/docs/developer/api/notes/create_note.md @@ -8,18 +8,18 @@ Create a new note for a media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| content | string | Yes | Note content | -| position | string | No | Location reference (e.g., epubcfi) | -| percentage_location | float | No | Location as percentage (0-1) | -| epubcfi_location | string | No | EPUB CFI location | +| Field | Type | Required | Description | +| ------------------- | ------ | -------- | ---------------------------------- | +| content | string | Yes | Note content | +| position | string | No | Location reference (e.g., epubcfi) | +| percentage_location | float | No | Location as percentage (0-1) | +| epubcfi_location | string | No | EPUB CFI location | ### Example Request @@ -49,8 +49,8 @@ Create a new note for a media item. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid note data | -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid note data | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/notes/delete_note.md b/docs/developer/api/notes/delete_note.md index a4f7334..d4f15cc 100644 --- a/docs/developer/api/notes/delete_note.md +++ b/docs/developer/api/notes/delete_note.md @@ -7,15 +7,15 @@ Delete a note. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| note_id | string | Yes | Note UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ----------- | +| note_id | string | Yes | Note UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,8 +30,8 @@ Note deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not own this note | -| 404 | Note not found | +| Code | Description | +| ---- | --------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not own this note | +| 404 | Note not found | diff --git a/docs/developer/api/notes/get_notes.md b/docs/developer/api/notes/get_notes.md index 29d819f..a7217d6 100644 --- a/docs/developer/api/notes/get_notes.md +++ b/docs/developer/api/notes/get_notes.md @@ -7,15 +7,15 @@ Retrieve all notes for a specific media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -48,7 +48,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/notes/update_note.md b/docs/developer/api/notes/update_note.md index 56cfeb3..a4a4925 100644 --- a/docs/developer/api/notes/update_note.md +++ b/docs/developer/api/notes/update_note.md @@ -8,16 +8,16 @@ Update an existing note. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| note_id | string | Yes | Note UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ----------- | +| note_id | string | Yes | Note UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| content | string | No | Updated note content | -| position | string | No | Updated location reference | +| Field | Type | Required | Description | +| -------- | ------ | -------- | -------------------------- | +| content | string | No | Updated note content | +| position | string | No | Updated location reference | ### Example Request @@ -43,9 +43,9 @@ Update an existing note. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid note data | -| 401 | Invalid or expired token | -| 403 | User does not own this note | -| 404 | Note not found | +| Code | Description | +| ---- | --------------------------- | +| 400 | Invalid note data | +| 401 | Invalid or expired token | +| 403 | User does not own this note | +| 404 | Note not found | diff --git a/docs/developer/api/opds/acquisition.md b/docs/developer/api/opds/acquisition.md index 7b572c3..3b8b758 100644 --- a/docs/developer/api/opds/acquisition.md +++ b/docs/developer/api/opds/acquisition.md @@ -9,9 +9,9 @@ Download books and list available formats. ### Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| format | string | No | Book format: `epub` (default), `kepub` | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | -------------------------------------- | +| format | string | No | Book format: `epub` (default), `kepub` | ### Example Request @@ -22,6 +22,7 @@ GET /opds/devices/kobo-id/download/uuid-123?format=kepub ### Response (200 OK) **Headers:** + - `Content-Type`: `application/epub+zip` or `application/vnd.kobo+xml+zip` - `Content-Disposition`: attachment; filename="The Hobbit.epub" - `X-Bookhoard-UUID`: uuid-123 diff --git a/docs/developer/api/opds/feeds.md b/docs/developer/api/opds/feeds.md index 3833b2c..bdb82eb 100644 --- a/docs/developer/api/opds/feeds.md +++ b/docs/developer/api/opds/feeds.md @@ -9,10 +9,10 @@ Bookhoard provides OPDS 1.2 feeds for device compatibility. ### Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| page | integer | No | Page number (default: 1) | -| per_page | integer | No | Items per page (default: 50, max: 200) | +| Parameter | Type | Required | Description | +| --------- | ------- | -------- | -------------------------------------- | +| page | integer | No | Page number (default: 1) | +| per_page | integer | No | Items per page (default: 50, max: 200) | ### Example Request @@ -24,31 +24,31 @@ GET /opds/devices/kobo-id/catalog?page=1&per_page=50 ```xml - urn:uuid:device-id Bookhoard Library 2026-02-01T12:00:00Z - + - + urn:uuid:bookhoard-uuid-123 The Hobbit J.R.R. Tolkien 2026-02-01T10:00:00Z - - - - - + uuid-123 abc123... @@ -62,9 +62,9 @@ GET /opds/devices/kobo-id/catalog?page=1&per_page=50 ### Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| q | string | Yes | Search query | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ------------ | +| q | string | Yes | Search query | ### Example Request diff --git a/docs/developer/api/opds/publication.md b/docs/developer/api/opds/publication.md index 1e5f9b2..9143899 100644 --- a/docs/developer/api/opds/publication.md +++ b/docs/developer/api/opds/publication.md @@ -16,6 +16,7 @@ GET /opds/devices/kobo-id/nav ### Response (200 OK - OPDS 1.2 Navigation XML) Returns OPDS navigation feed with links to: + - Root catalog - Search - Collections/shelves diff --git a/docs/developer/api/progress/delete_progress.md b/docs/developer/api/progress/delete_progress.md index 0b0f29e..83e6937 100644 --- a/docs/developer/api/progress/delete_progress.md +++ b/docs/developer/api/progress/delete_progress.md @@ -7,15 +7,15 @@ Delete reading progress for a media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,7 +30,7 @@ Progress deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/progress/get_progress.md b/docs/developer/api/progress/get_progress.md index ea5d550..01f2f6f 100644 --- a/docs/developer/api/progress/get_progress.md +++ b/docs/developer/api/progress/get_progress.md @@ -7,15 +7,15 @@ Retrieve reading progress for a specific media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -46,7 +46,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/progress/get_progress_history.md b/docs/developer/api/progress/get_progress_history.md index 5d9b63c..f143b4f 100644 --- a/docs/developer/api/progress/get_progress_history.md +++ b/docs/developer/api/progress/get_progress_history.md @@ -7,22 +7,22 @@ Get historical reading progress data for a media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Media item UUID | ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| limit | integer | No | Maximum number of history entries (default: 100) | -| offset | integer | No | Number of entries to skip (default: 0) | +| Parameter | Type | Required | Description | +| --------- | ------- | -------- | ------------------------------------------------ | +| limit | integer | No | Maximum number of history entries (default: 100) | +| offset | integer | No | Number of entries to skip (default: 0) | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -63,7 +63,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/progress/get_universal_progress.md b/docs/developer/api/progress/get_universal_progress.md index a8f9da7..fbd2f6c 100644 --- a/docs/developer/api/progress/get_universal_progress.md +++ b/docs/developer/api/progress/get_universal_progress.md @@ -7,15 +7,15 @@ Get universal (device-agnostic) reading progress for a media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Media item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -44,7 +44,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/progress/update_progress.md b/docs/developer/api/progress/update_progress.md index ab29077..73ec679 100644 --- a/docs/developer/api/progress/update_progress.md +++ b/docs/developer/api/progress/update_progress.md @@ -8,25 +8,25 @@ Update reading progress for a media item. This will sync across all devices via ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| source | string | Yes | Progress source (e.g., "web", "koreader", "kobo") | -| location | object | Yes | Location information | -| location.percentage | float | No | Progress percentage (0-1) | -| location.epubcfi | string | No | EPUB CFI location | -| location.character | integer | No | Character offset | -| location.chapter | integer | No | Chapter number | -| location.page | integer | No | Current page | -| location.total_pages | integer | No | Total pages | -| device_metadata | object | No | Device metadata | -| device_metadata.device_type | string | No | Device type | -| device_metadata.user_agent | string | No | User agent string | +| Field | Type | Required | Description | +| --------------------------- | ------- | -------- | ------------------------------------------------- | +| source | string | Yes | Progress source (e.g., "web", "koreader", "kobo") | +| location | object | Yes | Location information | +| location.percentage | float | No | Progress percentage (0-1) | +| location.epubcfi | string | No | EPUB CFI location | +| location.character | integer | No | Character offset | +| location.chapter | integer | No | Chapter number | +| location.page | integer | No | Current page | +| location.total_pages | integer | No | Total pages | +| device_metadata | object | No | Device metadata | +| device_metadata.device_type | string | No | Device type | +| device_metadata.user_agent | string | No | User agent string | ### Example Request @@ -61,8 +61,8 @@ Update reading progress for a media item. This will sync across all devices via ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid location data | -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid location data | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/progress/update_universal_progress.md b/docs/developer/api/progress/update_universal_progress.md index c6a8cdb..2e91424 100644 --- a/docs/developer/api/progress/update_universal_progress.md +++ b/docs/developer/api/progress/update_universal_progress.md @@ -8,19 +8,19 @@ Update universal reading progress for a media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| id | string (UUID) | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| id | string (UUID) | Yes | Media item UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| percentage | float | No | Progress percentage (0-100) | -| position | integer | No | Current position in bytes | -| page | integer | No | Current page number | -| finished | boolean | No | Whether the book is finished | -| device_id | string (UUID) | No | Device UUID (optional, for tracking source) | +| Field | Type | Required | Description | +| ---------- | ------------- | -------- | ------------------------------------------- | +| percentage | float | No | Progress percentage (0-100) | +| position | integer | No | Current position in bytes | +| page | integer | No | Current page number | +| finished | boolean | No | Whether the book is finished | +| device_id | string (UUID) | No | Device UUID (optional, for tracking source) | ### Example Request @@ -49,8 +49,8 @@ Update universal reading progress for a media item. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/queue/clear_device_queue.md b/docs/developer/api/queue/clear_device_queue.md index 84c17f7..dd88976 100644 --- a/docs/developer/api/queue/clear_device_queue.md +++ b/docs/developer/api/queue/clear_device_queue.md @@ -7,15 +7,15 @@ Clear all queue items for a specific device. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| device_id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| device_id | string (UUID) | Yes | Device UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,7 +30,7 @@ Device queue cleared successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Device not found | diff --git a/docs/developer/api/queue/delete_queue_item.md b/docs/developer/api/queue/delete_queue_item.md index 9154d80..ef721b0 100644 --- a/docs/developer/api/queue/delete_queue_item.md +++ b/docs/developer/api/queue/delete_queue_item.md @@ -7,15 +7,15 @@ Delete a specific queue item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| item_id | string (UUID) | Yes | Queue item UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| item_id | string (UUID) | Yes | Queue item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -30,7 +30,7 @@ Queue item deleted successfully. ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Queue item not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Queue item not found | diff --git a/docs/developer/api/queue/get_device_queue_stats.md b/docs/developer/api/queue/get_device_queue_stats.md index d131967..fc9ea21 100644 --- a/docs/developer/api/queue/get_device_queue_stats.md +++ b/docs/developer/api/queue/get_device_queue_stats.md @@ -7,15 +7,15 @@ Get statistics for a specific device's sync queue. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| device_id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| device_id | string (UUID) | Yes | Device UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -38,7 +38,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Device not found | diff --git a/docs/developer/api/queue/list_all_queue_items.md b/docs/developer/api/queue/list_all_queue_items.md index 128ede3..0cc2bb3 100644 --- a/docs/developer/api/queue/list_all_queue_items.md +++ b/docs/developer/api/queue/list_all_queue_items.md @@ -7,18 +7,18 @@ List all queue items across all devices (admin only). ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| device_id | string (UUID) | No | Filter by device | -| status | string | No | Filter by status (pending, processing, completed, failed) | -| limit | integer | No | Maximum number of items to return (default: 100) | -| offset | integer | No | Number of items to skip (default: 0) | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------------------------------------------------- | +| device_id | string (UUID) | No | Filter by device | +| status | string | No | Filter by status (pending, processing, completed, failed) | +| limit | integer | No | Maximum number of items to return (default: 100) | +| offset | integer | No | Number of items to skip (default: 0) | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -52,7 +52,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | +| Code | Description | +| ---- | ----------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | diff --git a/docs/developer/api/queue/list_device_queue_items.md b/docs/developer/api/queue/list_device_queue_items.md index f2c4464..1273e40 100644 --- a/docs/developer/api/queue/list_device_queue_items.md +++ b/docs/developer/api/queue/list_device_queue_items.md @@ -7,23 +7,23 @@ List all queue items for a specific device. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| device_id | string (UUID) | Yes | Device UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ----------- | +| device_id | string (UUID) | Yes | Device UUID | ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| status | string | No | Filter by status (pending, processing, completed, failed) | -| limit | integer | No | Maximum number of items to return (default: 50) | -| offset | integer | No | Number of items to skip (default: 0) | +| Parameter | Type | Required | Description | +| --------- | ------- | -------- | --------------------------------------------------------- | +| status | string | No | Filter by status (pending, processing, completed, failed) | +| limit | integer | No | Maximum number of items to return (default: 50) | +| offset | integer | No | Number of items to skip (default: 0) | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -55,7 +55,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Device not found | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | +| 404 | Device not found | diff --git a/docs/developer/api/queue/retry_queue_item.md b/docs/developer/api/queue/retry_queue_item.md index 2e9c3cb..49ccae8 100644 --- a/docs/developer/api/queue/retry_queue_item.md +++ b/docs/developer/api/queue/retry_queue_item.md @@ -7,15 +7,15 @@ Retry a failed queue item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| item_id | string (UUID) | Yes | Queue item UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| item_id | string (UUID) | Yes | Queue item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -35,8 +35,8 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Queue item not found | -| 400 | Item cannot be retried (not in failed state) | +| Code | Description | +| ---- | -------------------------------------------- | +| 401 | Invalid or expired token | +| 404 | Queue item not found | +| 400 | Item cannot be retried (not in failed state) | diff --git a/docs/developer/api/ratings/create_rating.md b/docs/developer/api/ratings/create_rating.md index fb6dae9..18a5400 100644 --- a/docs/developer/api/ratings/create_rating.md +++ b/docs/developer/api/ratings/create_rating.md @@ -8,15 +8,15 @@ Set a rating for a media item. Creates a new rating or updates an existing one. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| rating | integer | Yes | Rating from 1-10 | +| Field | Type | Required | Description | +| ------ | ------- | -------- | ---------------- | +| rating | integer | Yes | Rating from 1-10 | ### Example Request @@ -41,8 +41,8 @@ Set a rating for a media item. Creates a new rating or updates an existing one. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid rating (must be 1-10) | -| 401 | Invalid or expired token | -| 404 | Media item not found | +| Code | Description | +| ---- | ----------------------------- | +| 400 | Invalid rating (must be 1-10) | +| 401 | Invalid or expired token | +| 404 | Media item not found | diff --git a/docs/developer/api/ratings/delete_rating.md b/docs/developer/api/ratings/delete_rating.md index 9545a97..a10a30e 100644 --- a/docs/developer/api/ratings/delete_rating.md +++ b/docs/developer/api/ratings/delete_rating.md @@ -7,9 +7,9 @@ Delete the current user's rating for a media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ### Example Request @@ -28,7 +28,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Media item or rating not found | +| Code | Description | +| ---- | ------------------------------ | +| 401 | Invalid or expired token | +| 404 | Media item or rating not found | diff --git a/docs/developer/api/ratings/get_ratings.md b/docs/developer/api/ratings/get_ratings.md index c15aef0..c9fbe5a 100644 --- a/docs/developer/api/ratings/get_ratings.md +++ b/docs/developer/api/ratings/get_ratings.md @@ -7,15 +7,15 @@ Retrieve the current user's rating for a media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -38,7 +38,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 404 | Media item not found or no rating set | +| Code | Description | +| ---- | ------------------------------------- | +| 401 | Invalid or expired token | +| 404 | Media item not found or no rating set | diff --git a/docs/developer/api/ratings/update_rating.md b/docs/developer/api/ratings/update_rating.md index 20fee30..6da8f5d 100644 --- a/docs/developer/api/ratings/update_rating.md +++ b/docs/developer/api/ratings/update_rating.md @@ -8,16 +8,16 @@ Update an existing rating for a media item. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| media_id | string | Yes | Media item UUID | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------- | +| media_id | string | Yes | Media item UUID | ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| rating | integer | Yes | Rating value (1-10 scale) | -| review | string | No | Optional review text | +| Field | Type | Required | Description | +| ------ | ------- | -------- | ------------------------- | +| rating | integer | Yes | Rating value (1-10 scale) | +| review | string | No | Optional review text | **Rating Scale**: 1-10 (odd numbers = half-stars: 1=0.5★, 2=1★, 3=1.5★, ..., 9=4.5★, 10=5★) @@ -46,8 +46,8 @@ Update an existing rating for a media item. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 404 | Media item or rating not found | +| Code | Description | +| ---- | ------------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 404 | Media item or rating not found | diff --git a/docs/developer/api/scanner/get_scan_status.md b/docs/developer/api/scanner/get_scan_status.md index 7bdfcce..2f680b5 100644 --- a/docs/developer/api/scanner/get_scan_status.md +++ b/docs/developer/api/scanner/get_scan_status.md @@ -7,15 +7,15 @@ Get the status of a specific scan job. ## Path Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| jobId | string (UUID) | Yes | Scan job UUID | +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------- | +| jobId | string (UUID) | Yes | Scan job UUID | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -52,18 +52,18 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Status Values -| Status | Description | -|--------|-------------| -| pending | Job is queued | -| in_progress | Job is currently running | -| completed | Job completed successfully | -| failed | Job failed with errors | -| cancelled | Job was cancelled | +| Status | Description | +| ----------- | -------------------------- | +| pending | Job is queued | +| in_progress | Job is currently running | +| completed | Job completed successfully | +| failed | Job failed with errors | +| cancelled | Job was cancelled | ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Job not found | +| Code | Description | +| ---- | ----------------------------------- | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Job not found | diff --git a/docs/developer/api/scanner/get_watch_mode_status.md b/docs/developer/api/scanner/get_watch_mode_status.md index 902fdd1..a79e350 100644 --- a/docs/developer/api/scanner/get_watch_mode_status.md +++ b/docs/developer/api/scanner/get_watch_mode_status.md @@ -7,15 +7,15 @@ Get the watch mode status for a library. ## Query Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| library_id | string (UUID) | Yes | Library UUID to check | +| Parameter | Type | Required | Description | +| ---------- | ------------- | -------- | --------------------- | +| library_id | string (UUID) | Yes | Library UUID to check | ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token (must have admin role) | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ----------------------------------- | +| Authorization | string | Yes | Bearer token (must have admin role) | ### Example Request @@ -31,10 +31,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... "library_id": "uuid", "status": "watching", "started_at": "2026-02-08T10:00:00Z", - "watched_folders": [ - "/path/to/library/folder1", - "/path/to/library/folder2" - ], + "watched_folders": ["/path/to/library/folder1", "/path/to/library/folder2"], "stats": { "files_detected": 15, "files_processed": 12, @@ -46,17 +43,17 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Status Values -| Status | Description | -|--------|-------------| -| watching | Watch mode is active | -| stopped | Watch mode is not active | -| error | Watch mode encountered an error | +| Status | Description | +| -------- | ------------------------------- | +| watching | Watch mode is active | +| stopped | Watch mode is not active | +| error | Watch mode encountered an error | ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Missing library_id parameter | -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Missing library_id parameter | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | diff --git a/docs/developer/api/scanner/overview.md b/docs/developer/api/scanner/overview.md index 807756c..9b6350e 100644 --- a/docs/developer/api/scanner/overview.md +++ b/docs/developer/api/scanner/overview.md @@ -5,16 +5,19 @@ The Bookhoard scanner provides comprehensive library management for ebooks, comi ## Scanner Types ### Manual Scanning + - **One-time scan**: On-demand scanning of library folders - **Progress tracking**: Real-time status updates with file-by-file progress - **Error reporting**: Detailed logs of failed files with error messages ### Automated Scanner + - **Periodic scanning**: Configurable interval-based background scanning - **Automatic discovery**: Detects new files without manual intervention - **Resource-efficient**: Minimizes system impact with smart scheduling ### Watch Mode + - **Real-time monitoring**: Instant detection of file system changes - **Event-driven**: Processes files immediately upon addition/modification - **Platform support**: Works with inotify (Linux), FSEvents (macOS), and ReadDirectoryChangesW (Windows) @@ -22,36 +25,41 @@ The Bookhoard scanner provides comprehensive library management for ebooks, comi ## Supported Formats ### Ebooks -| Format | Extensions | -|--------|------------| -| EPUB | `.epub` | -| PDF | `.pdf` | -| Kindle | `.mobi`, `.azw`, `.azw3` | -| Text | `.txt`, `.rtf` | -| Document | `.doc`, `.docx` | -| Other | `.lit`, `.fb2`, `.pdb` | + +| Format | Extensions | +| -------- | ------------------------ | +| EPUB | `.epub` | +| PDF | `.pdf` | +| Kindle | `.mobi`, `.azw`, `.azw3` | +| Text | `.txt`, `.rtf` | +| Document | `.doc`, `.docx` | +| Other | `.lit`, `.fb2`, `.pdb` | ### Comics -| Format | Extensions | Archive Type | -|--------|------------|--------------| -| Comic Book ZIP | `.cbz` | ZIP | -| Comic Book RAR | `.cbr` | RAR | -| Comic Book 7z | `.cb7` | 7-Zip | -| Comic Book TAR | `.cbt` | TAR | -| PDF Comics | `.pdf` | PDF | + +| Format | Extensions | Archive Type | +| -------------- | ---------- | ------------ | +| Comic Book ZIP | `.cbz` | ZIP | +| Comic Book RAR | `.cbr` | RAR | +| Comic Book 7z | `.cb7` | 7-Zip | +| Comic Book TAR | `.cbt` | TAR | +| PDF Comics | `.pdf` | PDF | ### Manga -| Format | Extensions | Notes | -|--------|------------|-------| -| Comic Archives | `.cbz`, `.cbr` | Same as comics | -| Image Files | `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.webp` | Individual pages | + +| Format | Extensions | Notes | +| -------------- | ------------------------------------------------ | ---------------- | +| Comic Archives | `.cbz`, `.cbr` | Same as comics | +| Image Files | `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.webp` | Individual pages | ## Metadata Extraction ### Comic Archives (.cbz, .cbr, .cb7, .cbt) + The scanner automatically extracts metadata from comic archives: **ComicInfo.xml Support:** + - Series title - Issue number - Publisher @@ -61,11 +69,13 @@ The scanner automatically extracts metadata from comic archives: - Cover image extraction **Fallback Metadata:** + - Filename parsing - Archive structure analysis - Page count detection ### Manga Processing + - **Archive-based**: Processes .cbz/.cbr files like comics - **Image-based**: Handles directories of sequential images - **Chapter detection**: Identifies chapter/volume numbers from filenames @@ -74,21 +84,25 @@ The scanner automatically extracts metadata from comic archives: ## Scanner Features ### Smart Deduplication + - SHA256 hash calculation for all files - Automatic duplicate detection and skipping - Efficient incremental updates ### Library Type Awareness + - Format filtering based on library type - Type-specific metadata extraction - Appropriate thumbnail generation ### Error Handling + - Continues on individual file errors - Detailed error reporting in scan status - Failed file tracking for retry ### Progress Tracking + - Total files vs. processed files - Percentage completion - Added, updated, and failed file counts @@ -97,11 +111,13 @@ The scanner automatically extracts metadata from comic archives: ## Performance Considerations ### Large Libraries + - **Scanning speed**: Processes hundreds of files per second - **Memory usage**: Streaming metadata extraction - **Database efficiency**: Batch inserts and updates ### Resource Limits + - **Configurable intervals**: Prevent excessive scanning - **Rate limiting**: Watch mode debounce settings - **Admin controls**: Start/stop operations as needed @@ -109,6 +125,7 @@ The scanner automatically extracts metadata from comic archives: ## Usage Examples ### Create and Scan a Comic Library + ```json POST /api/libraries { @@ -125,6 +142,7 @@ POST /api/scanner/scan ``` ### Enable Watch Mode for Manga + ```json POST /api/scanner/watch/start { @@ -133,6 +151,7 @@ POST /api/scanner/watch/start ``` ### Check Scan Progress + ```http GET /api/scanner/status/550e8400-e29b-41d4-a716-446655440000 ``` diff --git a/docs/developer/api/scanner/scan_library.md b/docs/developer/api/scanner/scan_library.md index 21048ce..9b62df4 100644 --- a/docs/developer/api/scanner/scan_library.md +++ b/docs/developer/api/scanner/scan_library.md @@ -8,11 +8,11 @@ Initiate a one-time scan of a library for ebooks, manga, or comics. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| library_id | string (UUID) | Yes | Library UUID to scan (supports ebooks, manga, and comics) | -| recursive | boolean | No | Scan subdirectories recursively (default: true) | -| force | boolean | No | Force rescan of existing files (default: false) | +| Field | Type | Required | Description | +| ---------- | ------------- | -------- | --------------------------------------------------------- | +| library_id | string (UUID) | Yes | Library UUID to scan (supports ebooks, manga, and comics) | +| recursive | boolean | No | Scan subdirectories recursively (default: true) | +| force | boolean | No | Force rescan of existing files (default: false) | ## Supported Formats @@ -47,9 +47,9 @@ The scanner automatically detects and processes files based on the library type: ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | +| Code | Description | +| ---- | ----------------------------------- | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | diff --git a/docs/developer/api/scanner/start_scanner.md b/docs/developer/api/scanner/start_scanner.md index 19b1de0..26dce10 100644 --- a/docs/developer/api/scanner/start_scanner.md +++ b/docs/developer/api/scanner/start_scanner.md @@ -8,10 +8,10 @@ Start the automated background scanner for a library. Supports ebook, manga, and ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| library_id | string (UUID) | Yes | Library UUID to scan (supports ebooks, manga, and comics) | -| interval_seconds | integer | No | Scan interval in seconds (default: 3600, min: 300) | +| Field | Type | Required | Description | +| ---------------- | ------------- | -------- | --------------------------------------------------------- | +| library_id | string (UUID) | Yes | Library UUID to scan (supports ebooks, manga, and comics) | +| interval_seconds | integer | No | Scan interval in seconds (default: 3600, min: 300) | ## Supported Library Types @@ -41,10 +41,10 @@ Start the automated background scanner for a library. Supports ebook, manga, and ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data or interval too low | -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | -| 409 | Scanner already running for this library | +| Code | Description | +| ---- | ---------------------------------------- | +| 400 | Invalid request data or interval too low | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | +| 409 | Scanner already running for this library | diff --git a/docs/developer/api/scanner/start_watch_mode.md b/docs/developer/api/scanner/start_watch_mode.md index 01cb2dd..1e10cd4 100644 --- a/docs/developer/api/scanner/start_watch_mode.md +++ b/docs/developer/api/scanner/start_watch_mode.md @@ -8,13 +8,14 @@ Start watch mode for a library to automatically detect and process new/modified ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| library_id | string (UUID) | Yes | Library UUID to watch (supports ebooks, manga, and comics) | +| Field | Type | Required | Description | +| ---------- | ------------- | -------- | ---------------------------------------------------------- | +| library_id | string (UUID) | Yes | Library UUID to watch (supports ebooks, manga, and comics) | ## Watch Mode Features Watch mode automatically detects and processes: + - **New files** added to library folders - **Modified files** that have been updated - **Format-specific metadata extraction** for comics (.cbz, .cbr) and manga @@ -41,10 +42,10 @@ Watch mode automatically detects and processes: ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | -| 409 | Watch mode already active for this library | +| Code | Description | +| ---- | ------------------------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | +| 409 | Watch mode already active for this library | diff --git a/docs/developer/api/scanner/stop_scanner.md b/docs/developer/api/scanner/stop_scanner.md index 33f619c..f242998 100644 --- a/docs/developer/api/scanner/stop_scanner.md +++ b/docs/developer/api/scanner/stop_scanner.md @@ -8,9 +8,9 @@ Stop the automated background scanner for a library. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| library_id | string (UUID) | Yes | Library UUID to stop scanning | +| Field | Type | Required | Description | +| ---------- | ------------- | -------- | ----------------------------- | +| library_id | string (UUID) | Yes | Library UUID to stop scanning | ### Example Request @@ -32,10 +32,10 @@ Stop the automated background scanner for a library. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | -| 400 | Scanner not running for this library | +| Code | Description | +| ---- | ------------------------------------ | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | +| 400 | Scanner not running for this library | diff --git a/docs/developer/api/scanner/stop_watch_mode.md b/docs/developer/api/scanner/stop_watch_mode.md index eb4d2a9..8d50295 100644 --- a/docs/developer/api/scanner/stop_watch_mode.md +++ b/docs/developer/api/scanner/stop_watch_mode.md @@ -8,9 +8,9 @@ Stop watch mode for a library. ## Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| library_id | string (UUID) | Yes | Library UUID to stop watching | +| Field | Type | Required | Description | +| ---------- | ------------- | -------- | ----------------------------- | +| library_id | string (UUID) | Yes | Library UUID to stop watching | ### Example Request @@ -32,10 +32,10 @@ Stop watch mode for a library. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid request data | -| 401 | Invalid or expired token | -| 403 | User does not have admin privileges | -| 404 | Library not found | -| 400 | Watch mode not active for this library | +| Code | Description | +| ---- | -------------------------------------- | +| 400 | Invalid request data | +| 401 | Invalid or expired token | +| 403 | User does not have admin privileges | +| 404 | Library not found | +| 400 | Watch mode not active for this library | diff --git a/docs/developer/api/sync/kobo-protocol.md b/docs/developer/api/sync/kobo-protocol.md index 3fbe9e1..6952da4 100644 --- a/docs/developer/api/sync/kobo-protocol.md +++ b/docs/developer/api/sync/kobo-protocol.md @@ -10,23 +10,23 @@ Kobo uses a proprietary sync protocol with JSON payloads. ### Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer device token | -| x-kobo-device | string | Yes | JSON device info | -| Content-Type | string | Yes | application/json | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------------- | +| Authorization | string | Yes | Bearer device token | +| x-kobo-device | string | Yes | JSON device info | +| Content-Type | string | Yes | application/json | ### Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| ReadingSync | array | No | Array of reading progress data | -| ReadingSync[].ContentId | string | Yes | Book UUID | -| ReadingSync[].PercentRead | float | Yes | Progress percentage (0-100) | -| ReadingSync[].EntitlementId | string | Yes | Kobo entitlement ID | -| ReadingSync[].RemainingTimeMinutes | integer | No | Estimated remaining time | -| ReadingSync[].LastModified | string | Yes | ISO 8601 timestamp | -| BookmarkSync | array | No | Array of bookmarks/highlights | +| Field | Type | Required | Description | +| ---------------------------------- | ------- | -------- | ------------------------------ | +| ReadingSync | array | No | Array of reading progress data | +| ReadingSync[].ContentId | string | Yes | Book UUID | +| ReadingSync[].PercentRead | float | Yes | Progress percentage (0-100) | +| ReadingSync[].EntitlementId | string | Yes | Kobo entitlement ID | +| ReadingSync[].RemainingTimeMinutes | integer | No | Estimated remaining time | +| ReadingSync[].LastModified | string | Yes | ISO 8601 timestamp | +| BookmarkSync | array | No | Array of bookmarks/highlights | ### Example Request diff --git a/docs/developer/api/sync/koreader-protocol.md b/docs/developer/api/sync/koreader-protocol.md index bad7a88..64c6a10 100644 --- a/docs/developer/api/sync/koreader-protocol.md +++ b/docs/developer/api/sync/koreader-protocol.md @@ -10,27 +10,27 @@ KOReader uses a custom JSON-based sync protocol. ### Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer device token | -| Content-Type | string | Yes | application/json | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------------- | +| Authorization | string | Yes | Bearer device token | +| Content-Type | string | Yes | application/json | ### Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| library_id | string | No | Library UUID | -| books | array | Yes | Array of book sync data | -| books[].uuid | string | Yes | Book UUID | -| books[].title | string | Yes | Book title | -| books[].authors | array | Yes | Array of author names | -| books[].progress | float | Yes | Progress percentage (0-1) | -| books[].percentage | float | Yes | Progress percentage (0-1) | -| books[].last_read | string | Yes | ISO 8601 timestamp | -| books[].chapter | integer | No | Current chapter | -| books[].epubcfi | string | No | EPUB CFI location | -| books[].character | integer | No | Character offset | -| books[].bookmarks | array | No | Array of bookmarks/highlights | +| Field | Type | Required | Description | +| ------------------ | ------- | -------- | ----------------------------- | +| library_id | string | No | Library UUID | +| books | array | Yes | Array of book sync data | +| books[].uuid | string | Yes | Book UUID | +| books[].title | string | Yes | Book title | +| books[].authors | array | Yes | Array of author names | +| books[].progress | float | Yes | Progress percentage (0-1) | +| books[].percentage | float | Yes | Progress percentage (0-1) | +| books[].last_read | string | Yes | ISO 8601 timestamp | +| books[].chapter | integer | No | Current chapter | +| books[].epubcfi | string | No | EPUB CFI location | +| books[].character | integer | No | Character offset | +| books[].bookmarks | array | No | Array of bookmarks/highlights | ### Example Request diff --git a/docs/developer/api/system/settings.md b/docs/developer/api/system/settings.md index 254eb9b..699e058 100644 --- a/docs/developer/api/system/settings.md +++ b/docs/developer/api/system/settings.md @@ -21,12 +21,14 @@ Retrieve the current system-wide scan settings. **Authentication**: Admin role required **Response**: + - **200 OK**: Returns current scan settings - **401 Unauthorized**: Invalid or missing authentication - **403 Forbidden**: User does not have admin role - **500 Internal Server Error**: Server error **Response Body**: + ```json { "scan_frequency_minutes": 60, @@ -35,10 +37,12 @@ Retrieve the current system-wide scan settings. ``` **Fields**: + - `scan_frequency_minutes` (integer): How often to scan all libraries in minutes (15-1440) - `auto_scan_enabled` (boolean): Whether auto-scanning is enabled system-wide **Example**: + ```bash curl -X GET https://bookhoard.example.com/api/libraries/scan-settings \ -H "Authorization: Bearer " @@ -55,6 +59,7 @@ Update the system-wide scan settings. **Authentication**: Admin role required **Request Body**: + ```json { "scan_frequency_minutes": 30, @@ -63,6 +68,7 @@ Update the system-wide scan settings. ``` **Fields**: + - `scan_frequency_minutes` (integer, required): How often to scan all libraries in minutes - Minimum: 15 (15 minutes) - Maximum: 1440 (24 hours) @@ -71,6 +77,7 @@ Update the system-wide scan settings. - Default: true **Response**: + - **200 OK**: Settings updated successfully - **400 Bad Request**: Invalid request parameters - **401 Unauthorized**: Invalid or missing authentication @@ -78,6 +85,7 @@ Update the system-wide scan settings. - **500 Internal Server Error**: Server error **Success Response Body**: + ```json { "scan_frequency_minutes": 30, @@ -87,6 +95,7 @@ Update the system-wide scan settings. ``` **Error Response Body**: + ```json { "error": "error message" @@ -94,10 +103,12 @@ Update the system-wide scan settings. ``` **Validation Rules**: + - `scan_frequency_minutes` must be between 15 and 1440 minutes - Both fields are required **Example**: + ```bash curl -X PUT https://bookhoard.example.com/api/libraries/scan-settings \ -H "Authorization: Bearer " \ @@ -117,6 +128,7 @@ curl -X PUT https://bookhoard.example.com/api/libraries/scan-settings \ The `scan_frequency_minutes` setting determines how often the system will automatically scan all libraries for new media files. The scheduler will trigger scans for all libraries at the configured interval. **Constraints**: + - Minimum: 15 minutes (to prevent excessive scanning) - Maximum: 1440 minutes (24 hours) - Default: 60 minutes (1 hour) @@ -124,6 +136,7 @@ The `scan_frequency_minutes` setting determines how often the system will automa ### Auto-Scan Toggle The `auto_scan_enabled` setting acts as a master switch for automatic scanning: + - When `true`: All libraries will be scanned automatically at the configured interval - When `false`: No automatic scans will occur (manual scans still available) @@ -135,12 +148,12 @@ These settings apply to **all libraries** in the system. Individual users can no ## Error Codes -| Status Code | Error Description | -|-------------|-------------------| -| 400 | Invalid request parameters (e.g., frequency outside range) | -| 401 | Missing or invalid JWT token | -| 403 | User lacks admin role | -| 500 | Internal server error (e.g., database connection issue) | +| Status Code | Error Description | +| ----------- | ---------------------------------------------------------- | +| 400 | Invalid request parameters (e.g., frequency outside range) | +| 401 | Missing or invalid JWT token | +| 403 | User lacks admin role | +| 500 | Internal server error (e.g., database connection issue) | --- @@ -162,6 +175,7 @@ This API replaces the previous per-user scan settings system. The following chan - **Preserved**: Endpoint paths remain the same for backward compatibility The migration ensures that: + 1. All libraries scan at the same frequency 2. Only administrators can modify scan settings 3. The API endpoints remain unchanged for existing clients diff --git a/docs/developer/api/users/change_password.md b/docs/developer/api/users/change_password.md index 1c16ea1..cada988 100644 --- a/docs/developer/api/users/change_password.md +++ b/docs/developer/api/users/change_password.md @@ -3,6 +3,7 @@ Change user password. Supports both self-service and admin modes. **Endpoints**: + - Self-service: `PUT /api/auth/password` - Admin reset: `PUT /api/auth/password/:id` @@ -15,11 +16,11 @@ Users can change their own password by providing current password verification. ### Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| current_password | string | Yes | Current password for verification | -| new_password | string | Yes | New password (min 8 chars, complexity required) | -| confirm_password | string | Yes | Must match new_password | +| Field | Type | Required | Description | +| ---------------- | ------ | -------- | ----------------------------------------------- | +| current_password | string | Yes | Current password for verification | +| new_password | string | Yes | New password (min 8 chars, complexity required) | +| confirm_password | string | Yes | Must match new_password | ### Example Request @@ -39,10 +40,10 @@ Admins can reset any user's password without knowing the current password. ### Request Body (Admin Mode) -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| new_password | string | Yes | New password (min 8 chars, complexity required) | -| confirm_password | string | Yes | Must match new_password | +| Field | Type | Required | Description | +| ---------------- | ------ | -------- | ----------------------------------------------- | +| new_password | string | Yes | New password (min 8 chars, complexity required) | +| confirm_password | string | Yes | Must match new_password | ### Example Admin Request @@ -63,9 +64,9 @@ Admins can reset any user's password without knowing the current password. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid input, weak password, or passwords don't match | -| 401 | Current password is incorrect (self-service mode) | -| 403 | Admin access required (admin mode only) | -| 404 | User not found (admin mode only) | +| Code | Description | +| ---- | ------------------------------------------------------ | +| 400 | Invalid input, weak password, or passwords don't match | +| 401 | Current password is incorrect (self-service mode) | +| 403 | Admin access required (admin mode only) | +| 404 | User not found (admin mode only) | diff --git a/docs/developer/api/users/delete_user.md b/docs/developer/api/users/delete_user.md index 85523e6..640f9d1 100644 --- a/docs/developer/api/users/delete_user.md +++ b/docs/developer/api/users/delete_user.md @@ -3,6 +3,7 @@ Delete a user account. Supports both self-deletion and admin deletion. **Endpoints**: + - Self-deletion: `DELETE /api/auth/profile` - Admin deletion: `DELETE /api/auth/profile/:id` @@ -32,12 +33,12 @@ Admins can delete any user account by providing the user ID in the URL. ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Cannot delete the last admin | -| 401 | Invalid or expired token | -| 403 | Admin access required (trying to delete another user) | -| 404 | User not found (admin mode only) | +| Code | Description | +| ---- | ----------------------------------------------------- | +| 400 | Cannot delete the last admin | +| 401 | Invalid or expired token | +| 403 | Admin access required (trying to delete another user) | +| 404 | User not found (admin mode only) | ## Safety Rules diff --git a/docs/developer/api/users/get_profile.md b/docs/developer/api/users/get_profile.md index 2eec573..ccd94de 100644 --- a/docs/developer/api/users/get_profile.md +++ b/docs/developer/api/users/get_profile.md @@ -7,9 +7,9 @@ Retrieve the current authenticated user's profile. ## Request Headers -| Header | Type | Required | Description | -|--------|------|-----------|-------------| -| Authorization | string | Yes | Bearer token | +| Header | Type | Required | Description | +| ------------- | ------ | -------- | ------------ | +| Authorization | string | Yes | Bearer token | ### Example Request @@ -36,6 +36,6 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Error Responses -| Code | Description | -|------|-------------| -| 401 | Invalid or expired token | +| Code | Description | +| ---- | ------------------------ | +| 401 | Invalid or expired token | diff --git a/docs/developer/api/users/update_profile.md b/docs/developer/api/users/update_profile.md index 87bf3ea..cef488e 100644 --- a/docs/developer/api/users/update_profile.md +++ b/docs/developer/api/users/update_profile.md @@ -3,6 +3,7 @@ Update user profile information. Supports both self-edit and admin modes. **Endpoints**: + - Self-edit: `PUT /api/auth/profile` - Admin edit: `PUT /api/auth/profile/:id` @@ -15,13 +16,13 @@ Users can update their own profile. All fields are optional. ### Request Body -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| username | string | No | New username (must be unique, 3-50 chars) | -| email | string | No | New email (must be unique, valid format) | -| first_name | string | No | User's first name | -| last_name | string | No | User's last name | -| theme | string | No | Theme preference | +| Field | Type | Required | Description | +| ---------- | ------ | -------- | ----------------------------------------- | +| username | string | No | New username (must be unique, 3-50 chars) | +| email | string | No | New email (must be unique, valid format) | +| first_name | string | No | User's first name | +| last_name | string | No | User's last name | +| theme | string | No | Theme preference | ### Example Request @@ -43,9 +44,9 @@ Admins can update any user by providing the user ID in the URL. Additionally sup ### Additional Request Body Fields (Admin Only) -| Field | Type | Required | Description | -|--------|------|-----------|-------------| -| role | string | No | New role: "user" or "admin" | +| Field | Type | Required | Description | +| ----- | ------ | -------- | --------------------------- | +| role | string | No | New role: "user" or "admin" | ### Example Admin Request @@ -75,10 +76,10 @@ Admins can update any user by providing the user ID in the URL. Additionally sup ## Error Responses -| Code | Description | -|------|-------------| -| 400 | Invalid input data or invalid role | -| 401 | Invalid or expired token | -| 403 | Admin access required (admin mode only) | -| 404 | User not found (admin mode only) | -| 409 | Username or email already taken | +| Code | Description | +| ---- | --------------------------------------- | +| 400 | Invalid input data or invalid role | +| 401 | Invalid or expired token | +| 403 | Admin access required (admin mode only) | +| 404 | User not found (admin mode only) | +| 409 | Username or email already taken | diff --git a/docs/developer/api/websocket/protocol.md b/docs/developer/api/websocket/protocol.md index ec7f2de..d6b93f3 100644 --- a/docs/developer/api/websocket/protocol.md +++ b/docs/developer/api/websocket/protocol.md @@ -8,14 +8,14 @@ Real-time sync events broadcast to connected clients. ### Connection Parameters -| Parameter | Type | Required | Description | -|-----------|------|-----------|-------------| -| token | string | Yes | JWT authentication token | +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | ------------------------ | +| token | string | Yes | JWT authentication token | ### Example Connection ```javascript -const ws = new WebSocket('wss://bookhoard.com/ws/sync?token=eyJhbG...'); +const ws = new WebSocket("wss://bookhoard.com/ws/sync?token=eyJhbG..."); ``` ## Message Format diff --git a/docs/developer/api/websocket/sync_api.md b/docs/developer/api/websocket/sync_api.md index 9305b69..6d150d2 100644 --- a/docs/developer/api/websocket/sync_api.md +++ b/docs/developer/api/websocket/sync_api.md @@ -10,10 +10,12 @@ Real-time bidirectional sync API for live updates and notifications. Connect to the WebSocket endpoint with authentication: ```javascript -const ws = new WebSocket('wss://bookhoard.example/ws/sync?token=eyJhbGci...'); +const ws = new WebSocket("wss://bookhoard.example/ws/sync?token=eyJhbGci..."); // Or with device authentication -const ws = new WebSocket('wss://bookhoard.example/ws/sync?device_id=uuid&device_key=key'); +const ws = new WebSocket( + "wss://bookhoard.example/ws/sync?device_id=uuid&device_key=key", +); ``` ## Message Format @@ -214,23 +216,27 @@ wss://bookhoard.example/ws/sync?device_id=&device_key= ## Usage Example ```javascript -const ws = new WebSocket('wss://bookhoard.example/ws/sync?token=eyJhbGci...'); +const ws = new WebSocket("wss://bookhoard.example/ws/sync?token=eyJhbGci..."); ws.onopen = () => { - console.log('Connected to sync WebSocket'); + console.log("Connected to sync WebSocket"); // Subscribe to progress updates - ws.send(JSON.stringify({ - type: 'subscribe', - data: { topic: 'progress', device_id: 'device-uuid' } - })); + ws.send( + JSON.stringify({ + type: "subscribe", + data: { topic: "progress", device_id: "device-uuid" }, + }), + ); // Start heartbeat setInterval(() => { - ws.send(JSON.stringify({ - type: 'ping', - data: { timestamp: new Date().toISOString() } - })); + ws.send( + JSON.stringify({ + type: "ping", + data: { timestamp: new Date().toISOString() }, + }), + ); }, 30000); }; @@ -238,41 +244,41 @@ ws.onmessage = (event) => { const message = JSON.parse(event.data); switch (message.type) { - case 'progress_updated': - console.log('Progress updated:', message.data); + case "progress_updated": + console.log("Progress updated:", message.data); break; - case 'conflict_detected': - console.log('New conflict detected:', message.data); + case "conflict_detected": + console.log("New conflict detected:", message.data); break; - case 'scan_complete': - console.log('Scan complete:', message.data); + case "scan_complete": + console.log("Scan complete:", message.data); break; - case 'pong': - console.log('Pong received'); + case "pong": + console.log("Pong received"); break; default: - console.log('Unknown message type:', message.type); + console.log("Unknown message type:", message.type); } }; ws.onerror = (error) => { - console.error('WebSocket error:', error); + console.error("WebSocket error:", error); }; ws.onclose = () => { - console.log('WebSocket connection closed'); + console.log("WebSocket connection closed"); }; ``` ## Topics -| Topic | Description | Events | -|-------|-------------|--------| -| progress | Reading progress updates | progress_updated | -| conflicts | Sync conflict events | conflict_detected, conflict_resolved | -| scanner | Library scan events | scan_progress, scan_complete | -| devices | Device connection events | device_connected, device_disconnected | -| queue | Sync queue events | queue_item_added, queue_item_processed | +| Topic | Description | Events | +| --------- | ------------------------ | -------------------------------------- | +| progress | Reading progress updates | progress_updated | +| conflicts | Sync conflict events | conflict_detected, conflict_resolved | +| scanner | Library scan events | scan_progress, scan_complete | +| devices | Device connection events | device_connected, device_disconnected | +| queue | Sync queue events | queue_item_added, queue_item_processed | ## Best Practices diff --git a/docs/developer/collections-api.md b/docs/developer/collections-api.md index 7d0cefc..8402edd 100644 --- a/docs/developer/collections-api.md +++ b/docs/developer/collections-api.md @@ -9,6 +9,7 @@ Complete API reference for collection management endpoints. ## Overview Collections allow you to organize your books into custom categories with: + - **Auto-assignment rules**: Automatically add books matching criteria - **View settings**: Per-device display preferences - **Shelf mappings**: Sync to device-specific shelves (Kobo, KOReader) @@ -24,6 +25,7 @@ Collections allow you to organize your books into custom categories with: **Description**: Get all collections for the authenticated user **Response** (200 OK): + ```json { "collections": [ @@ -63,6 +65,7 @@ Collections allow you to organize your books into custom categories with: **Description**: Create a new collection **Request Body**: + ```json { "name": "To Read", @@ -90,6 +93,7 @@ Collections allow you to organize your books into custom categories with: ``` **Fields**: + - `name` (required): Collection name (max 255 chars) - `description` (optional): Collection description - `color` (optional): Hex color code (e.g., "#FF5733") @@ -98,6 +102,7 @@ Collections allow you to organize your books into custom categories with: - `view_settings` (optional): Per-device display preferences **Rule Object**: + - `field`: Field to match on (genre, author, series, language, publisher, copyright_year, tags) - `operator`: Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) - `value`: Value to compare against @@ -113,9 +118,11 @@ Collections allow you to organize your books into custom categories with: **Description**: Get single collection with all books **Path Parameters**: + - `id`: Collection UUID **Response** (200 OK): + ```json { "id": "550e8400-e29b-41d4-a716-446655440000", @@ -144,9 +151,11 @@ Collections allow you to organize your books into custom categories with: **Description**: Update collection details **Path Parameters**: + - `id`: Collection UUID **Request Body**: All fields are optional + ```json { "name": "Sci-Fi Favorites", @@ -165,6 +174,7 @@ Collections allow you to organize your books into custom categories with: **Description**: Delete a collection (books are NOT deleted) **Path Parameters**: + - `id`: Collection UUID **Response** (204 No Content) @@ -180,9 +190,11 @@ Collections allow you to organize your books into custom categories with: **Description**: Add one or more books to a collection **Path Parameters**: + - `id`: Collection UUID **Request Body**: + ```json { "book_ids": [ @@ -202,6 +214,7 @@ Collections allow you to organize your books into custom categories with: **Description**: Remove a single book from a collection **Path Parameters**: + - `id`: Collection UUID - `bookId`: Media Item UUID @@ -214,9 +227,11 @@ Collections allow you to organize your books into custom categories with: **Description**: Remove multiple books at once (efficient) **Path Parameters**: + - `id`: Collection UUID **Request Body**: + ```json { "book_ids": [ @@ -227,6 +242,7 @@ Collections allow you to organize your books into custom categories with: ``` **Response** (200 OK): + ```json { "removed": 2, @@ -243,13 +259,16 @@ Collections allow you to organize your books into custom categories with: **Description**: Get all books in a collection **Path Parameters**: + - `id`: Collection UUID **Query Parameters**: + - `limit` (optional): Number of books to return (default: 50) - `offset` (optional): Number of books to skip (default: 0) **Response** (200 OK): + ```json { "books": [ @@ -276,6 +295,7 @@ Collections allow you to organize your books into custom categories with: **Description**: Test which books would match given rules (without saving) **Request Body**: + ```json { "rules": [ @@ -294,6 +314,7 @@ Collections allow you to organize your books into custom categories with: ``` **Supported Fields**: + - `genre`: Book genre - `author`: Book author - `series`: Book series name @@ -303,6 +324,7 @@ Collections allow you to organize your books into custom categories with: - `tags`: Book tags **Supported Operators**: + - `equals`: Exact match - `not_equals`: Not equal - `contains`: Contains substring (case-insensitive) @@ -313,6 +335,7 @@ Collections allow you to organize your books into custom categories with: - `less_than`: Less than (numeric) **Response** (200 OK): + ```json { "matches": [ @@ -341,9 +364,11 @@ Collections allow you to organize your books into custom categories with: **Description**: Get all collection-to-shelf mappings for a device **Path Parameters**: + - `deviceId`: Device UUID **Response** (200 OK): + ```json { "mappings": [ @@ -366,9 +391,11 @@ Collections allow you to organize your books into custom categories with: **Description**: Map a collection to a device shelf **Path Parameters**: + - `deviceId`: Device UUID **Request Body**: + ```json { "collection_id": "550e8400-e29b-41d4-a716-446655440000", @@ -378,6 +405,7 @@ Collections allow you to organize your books into custom categories with: ``` **Sync Directions**: + - `bidirectional`: Sync both ways between Bookhoard and device - `book_to_hoard`: Bookhoard → Device only - `device_to_hoard`: Device → Bookhoard only @@ -392,10 +420,12 @@ Collections allow you to organize your books into custom categories with: **Description**: Update existing shelf mapping **Path Parameters**: + - `deviceId`: Device UUID - `collectionId`: Collection UUID **Request Body**: + ```json { "device_shelf_name": "Science Fiction", @@ -412,6 +442,7 @@ Collections allow you to organize your books into custom categories with: **Description**: Remove shelf mapping **Path Parameters**: + - `deviceId`: Device UUID - `collectionId`: Collection UUID @@ -424,6 +455,7 @@ Collections allow you to organize your books into custom categories with: All endpoints may return these errors: **400 Bad Request**: + ```json { "error": "invalid request: validation failed" @@ -431,6 +463,7 @@ All endpoints may return these errors: ``` **401 Unauthorized**: + ```json { "error": "authentication required" @@ -438,6 +471,7 @@ All endpoints may return these errors: ``` **404 Not Found**: + ```json { "error": "collection not found" @@ -445,6 +479,7 @@ All endpoints may return these errors: ``` **500 Internal Server Error**: + ```json { "error": "internal server error" @@ -459,6 +494,7 @@ All endpoints may return these errors: - **Unauthenticated**: 10 requests per minute Headers included: + ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 @@ -483,6 +519,7 @@ Complete API tests available in `bruno/collections/`: - `Bulk Remove Books.yml` Run tests: + ```bash bruno run bruno/collections/ ``` diff --git a/docs/developer/testing.md b/docs/developer/testing.md index 3f97794..f08d783 100644 --- a/docs/developer/testing.md +++ b/docs/developer/testing.md @@ -9,6 +9,7 @@ This document describes how to run and write tests for Bookhoard. All integration tests use the email domain **`@tests.bookhoard.internal`** for test users. This domain is reserved exclusively for testing and will never be used in production. **Why this domain?** + - Clear indication it's for testing only - Won't conflict with real user emails (which use real domains like `@gmail.com`, `@example.com`, etc.) - Safe cleanup: Tests can delete all users with this domain without risk to production data @@ -33,6 +34,7 @@ The test suite creates these standard users: ### Test Lifecycle and Cleanup Each test run: + 1. **Deletes** all existing users with `@tests.bookhoard.internal` email 2. **Creates** fresh standard test users 3. **Runs** the test with clean state @@ -45,12 +47,14 @@ This ensures complete test isolation - no state leaks between tests. When writing new tests: **✅ DO:** + - Use the standard test users from `setupTestServer(t)` - Use `createRegularUserOnce(t, setup.DB)` for additional test users - Let the test framework handle cleanup - Use the `@tests.bookhoard.internal` domain if creating custom test users **❌ DON'T:** + - Use real email domains like `@example.com` or `@gmail.com` - Manually manage test user deletion (unless absolutely necessary) - Assume test users persist between test runs @@ -59,21 +63,25 @@ When writing new tests: ## Running Tests ### Run All Tests + ```bash make test ``` ### Run Integration Tests Only + ```bash make test-integration ``` ### Run Specific Test + ```bash go test -v -run TestUpdateProfile ./cmd/server/tests/ ``` ### Run with Race Detection + ```bash go test -race ./cmd/server/tests/ ``` diff --git a/docs/developer/websocket-api.md b/docs/developer/websocket-api.md index 8f8f020..cf07a64 100644 --- a/docs/developer/websocket-api.md +++ b/docs/developer/websocket-api.md @@ -29,6 +29,7 @@ ws://localhost:8765/ws/sync?token= ``` **How to get JWT token**: + ```bash curl -X POST http://localhost:8765/api/auth/login \ -H "Content-Type: application/json" \ @@ -36,6 +37,7 @@ curl -X POST http://localhost:8765/api/auth/login \ ``` Response: + ```json { "token": "eyJhbGciOiJIUzI1NiIs...", @@ -65,6 +67,7 @@ Device tokens are generated when devices are registered via the API. ### Step 1: Connect to WebSocket **JavaScript Example**: + ```javascript const token = "your-jwt-token"; const ws = new WebSocket(`ws://localhost:8765/ws/sync?token=${token}`); @@ -83,6 +86,7 @@ ws.onclose = (event) => { ``` **Go Example**: + ```go import ( "github.com/gorilla/websocket" @@ -97,6 +101,7 @@ defer ws.Close() ``` **Python Example**: + ```python import websocket @@ -224,6 +229,7 @@ All messages follow this structure: ``` **Fields**: + - `type` (string, required): Message type identifier - `timestamp` (string, required): ISO 8601 timestamp - `data` (object, required): Message payload @@ -240,6 +246,7 @@ All messages follow this structure: **Purpose**: Send current progress for all user's books **Data Structure**: + ```json { "type": "initial_state", @@ -263,6 +270,7 @@ All messages follow this structure: ``` **Fields**: + - `progress` (object): Map of book UUID → progress data - `percentage` (number): 0.0 to 1.0 - `current_page` (number): Current page number @@ -277,6 +285,7 @@ All messages follow this structure: **Purpose**: Notify all connected clients of progress change **Data Structure**: + ```json { "type": "progress_update", @@ -294,11 +303,13 @@ All messages follow this structure: ``` **Fields**: + - `book_id` (string): UUID of book with updated progress - `percentage` (number): New progress value (0.0 to 1.0) - `source_device` (object): Device that sent the update **Use Cases**: + - Update progress bar in real-time - Sync reading position across devices - Update "currently reading" lists @@ -310,6 +321,7 @@ All messages follow this structure: **Purpose**: Share annotations across devices **Data Structure**: + ```json { "type": "annotation_update", @@ -333,6 +345,7 @@ All messages follow this structure: ``` **Fields**: + - `book_id` (string): UUID of book - `annotation_type` (string): Type of annotation (bookmark, highlight, note) - `data` (object): Annotation-specific data @@ -343,6 +356,7 @@ All messages follow this structure: - `source_device` (object): Device that created the annotation **Use Cases**: + - Show bookmarks on all devices - Share highlights between devices - Display reading notes @@ -354,6 +368,7 @@ All messages follow this structure: **Purpose**: Notify user of conflicting updates **Data Structure**: + ```json { "type": "conflict", @@ -367,11 +382,13 @@ All messages follow this structure: ``` **Fields**: + - `book_id` (string): UUID of book with conflict - `notification_type` (string): Type of conflict (progress_conflict, annotation_conflict) - `conflict_id` (string): UUID of conflict record **Use Cases**: + - Prompt user to resolve conflict - Show conflict resolution UI - Log conflict for manual review @@ -383,6 +400,7 @@ All messages follow this structure: **Purpose**: Keep connection alive **Data Structure**: + ```json { "type": "heartbeat", @@ -587,6 +605,7 @@ client.connect(); **Problem**: Can't connect to WebSocket **Solutions**: + 1. Check JWT token is valid 2. Verify server is running: `curl http://localhost:8765/health` 3. Check firewall settings @@ -597,6 +616,7 @@ client.connect(); **Problem**: WebSocket disconnects unexpectedly **Solutions**: + 1. Check network stability 2. Verify server keepalive settings 3. Implement reconnection logic (see above) @@ -607,6 +627,7 @@ client.connect(); **Problem**: Connected but no messages **Solutions**: + 1. Check onmessage handler is registered 2. Verify initial_state message received 3. Test with manual progress update via API @@ -617,6 +638,7 @@ client.connect(); **Problem**: 401 Unauthorized **Solutions**: + 1. Verify JWT token is not expired 2. Check token has correct claims (user_id) 3. For device tokens, verify Authorization header format @@ -627,6 +649,7 @@ client.connect(); **Problem**: Client memory increases over time **Solutions**: + 1. Clean up old messages 2. Don't store entire message history 3. Use weak references for large data @@ -637,6 +660,7 @@ client.connect(); ## Performance Tips 1. **Debounce UI Updates**: Don't update DOM on every message + ```javascript const debouncedUpdate = debounce(updateUI, 100); client.on("progress_update", (message) => { @@ -664,6 +688,7 @@ client.connect(); ## Support For issues or questions: + - GitHub Issues: [Bookhoard Repository] - Documentation: [Bookhoard Docs] - API Reference: [Bookhoard API Docs] diff --git a/docs/index.md b/docs/index.md index f41a4c0..c1d80bf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,19 +27,19 @@ Complete guide to Bookhoard documentation. Find what you need quickly. **[Developer Documentation Portal](developer/development.md)** - Technical documentation & API reference - - **API Documentation** - - [Complete API Reference](developer/api-reference.md) - Monolithic REST API reference (1,300+ lines) - - [Split Endpoint Docs](developer/api/api-reference.md) - Individual endpoints with interactive API Explorer - - [Collections API](developer/collections-api.md) - Collections management API - - [Admin API](developer/api/admin/) - User management (admin operations) - - [Media Items API](developer/api/media-items/) - Media item operations including bulk operations and downloads - - [Devices API](developer/api/devices/) - Device sync and shelf management - - [Conflicts API](developer/api/conflicts/) - Sync conflict resolution endpoints - - [Queue API](developer/api/queue/) - Sync queue management endpoints - - [Scanner API](developer/api/scanner/) - Library scanning and automated watch mode (admin) - - [KOReader API](developer/api/koreader/) - KOReader sync protocol endpoints - - [Kobo API](developer/api/kobo/) - Kobo sync protocol endpoints - - [WebSocket API](developer/api/websocket/) - Real-time sync events +- **API Documentation** + - [Complete API Reference](developer/api-reference.md) - Monolithic REST API reference (1,300+ lines) + - [Split Endpoint Docs](developer/api/api-reference.md) - Individual endpoints with interactive API Explorer + - [Collections API](developer/collections-api.md) - Collections management API + - [Admin API](developer/api/admin/) - User management (admin operations) + - [Media Items API](developer/api/media-items/) - Media item operations including bulk operations and downloads + - [Devices API](developer/api/devices/) - Device sync and shelf management + - [Conflicts API](developer/api/conflicts/) - Sync conflict resolution endpoints + - [Queue API](developer/api/queue/) - Sync queue management endpoints + - [Scanner API](developer/api/scanner/) - Library scanning and automated watch mode (admin) + - [KOReader API](developer/api/koreader/) - KOReader sync protocol endpoints + - [Kobo API](developer/api/kobo/) - Kobo sync protocol endpoints + - [WebSocket API](developer/api/websocket/) - Real-time sync events - **Protocol Specifications** - [Kobo Sync Protocol](developer/api/sync/kobo-protocol.md) - Kobo device sync @@ -62,21 +62,21 @@ Complete guide to Bookhoard documentation. Find what you need quickly. **[Contributing Portal](contributing/contributing.md)** - Development workflow - - [Development Guide](contributing/Development.md) - Architecture, setup, testing +- [Development Guide](contributing/Development.md) - Architecture, setup, testing - [PROJECT_GUIDELINES.md](PROJECT_GUIDELINES.md) - Development rules and standards --- ## 📚 Quick Links -| Want to... | Go to | -|------------|-------| -| **Get started** | [README.md](../README.md) - Project overview and quick start | -| **Set up a device** | [User Portal → Device Setup](user/user-guide.md) | -| **Use the API** | [Developer Portal → API Docs](developer/development.md) | -| **Deploy Bookhoard** | [Operations Portal → Troubleshooting](operations/troubleshooting.md) | - | **Contribute code** | [Contributing Portal → Development Guide](contributing/Development.md) | -| **Understand sync** | [User Portal → Sync Guide](user/sync-guide.md) | +| Want to... | Go to | +| -------------------- | ---------------------------------------------------------------------- | +| **Get started** | [README.md](../README.md) - Project overview and quick start | +| **Set up a device** | [User Portal → Device Setup](user/user-guide.md) | +| **Use the API** | [Developer Portal → API Docs](developer/development.md) | +| **Deploy Bookhoard** | [Operations Portal → Troubleshooting](operations/troubleshooting.md) | +| **Contribute code** | [Contributing Portal → Development Guide](contributing/Development.md) | +| **Understand sync** | [User Portal → Sync Guide](user/sync-guide.md) | --- @@ -84,44 +84,47 @@ Complete guide to Bookhoard documentation. Find what you need quickly. ### "How do I..." -| Question | Answer | -|----------|--------| -| ...install Bookhoard? | [README.md](../README.md) - Quick Start | -| ...set up my Kobo? | [Kobo Setup Guide](user/devices/kobo-setup.md) | -| ...set up KOReader? | [KOReader Setup Guide](user/devices/koreader-setup.md) | -| ...understand sync? | [Sync Guide](user/sync-guide.md) | -| ...resolve conflicts? | [Sync Guide](user/sync-guide.md) - Managing Conflicts | +| Question | Answer | +| --------------------------- | ------------------------------------------------------ | +| ...install Bookhoard? | [README.md](../README.md) - Quick Start | +| ...set up my Kobo? | [Kobo Setup Guide](user/devices/kobo-setup.md) | +| ...set up KOReader? | [KOReader Setup Guide](user/devices/koreader-setup.md) | +| ...understand sync? | [Sync Guide](user/sync-guide.md) | +| ...resolve conflicts? | [Sync Guide](user/sync-guide.md) - Managing Conflicts | | ...troubleshoot deployment? | [Troubleshooting Guide](operations/troubleshooting.md) | -| ...use the API? | [API Reference](developer/api-reference.md) | - | ...contribute code? | [Development Guide](contributing/Development.md) | +| ...use the API? | [API Reference](developer/api-reference.md) | +| ...contribute code? | [Development Guide](contributing/Development.md) | ### "Where is..." -| Information | Location | -|-------------|----------| -| ...features list? | [README.md](../README.md) | -| ...database schema? | [database/schema/schema.sql](../database/schema/schema.sql) | -| ...API endpoints? | [API Reference](developer/api-reference.md) | -| ...secrets config? | [.env.example](../.env.example) | -| ...operational config? | [docker-compose.yml](../docker-compose.yml) | -| ...deployment issues? | [Troubleshooting Guide](operations/troubleshooting.md) | +| Information | Location | +| ---------------------- | ----------------------------------------------------------- | +| ...features list? | [README.md](../README.md) | +| ...database schema? | [database/schema/schema.sql](../database/schema/schema.sql) | +| ...API endpoints? | [API Reference](developer/api-reference.md) | +| ...secrets config? | [.env.example](../.env.example) | +| ...operational config? | [docker-compose.yml](../docker-compose.yml) | +| ...deployment issues? | [Troubleshooting Guide](operations/troubleshooting.md) | --- ## 🎯 Common Workflows ### Set up a new device + 1. Choose your device: [Kobo](user/devices/kobo-setup.md) or [KOReader](user/devices/koreader-setup.md) 2. Understand sync: [Sync Guide](user/sync-guide.md) 3. Troubleshoot: Device-specific guides ### Integrate with Bookhoard API + 1. Start with [API Reference](developer/api-reference.md) 2. Explore [Collections API](developer/collections-api.md) 3. Review [WebSocket API](developer/websocket-api.md) 4. Test with [bruno/](../bruno/) collections ### Deploy to production + 1. Follow [README.md](../README.md) quick start 2. Configure environment: [.env.example](../.env.example) 3. Review [Troubleshooting Guide](operations/troubleshooting.md) diff --git a/docs/operations/operations.md b/docs/operations/operations.md index 3d6024a..614c016 100644 --- a/docs/operations/operations.md +++ b/docs/operations/operations.md @@ -16,17 +16,20 @@ Welcome to the Bookhoard operations documentation. This section contains guides ### Additional Deployment Guides **[Deployment Guide](deployment.md)** - Comprehensive deployment guide -- *Coming Soon* + +- _Coming Soon_ ## 🛠️ Maintenance **[Maintenance Guide](maintenance.md)** - Ongoing operations and maintenance -- *Coming Soon* + +- _Coming Soon_ ## 📊 Monitoring **[Monitoring Guide](monitoring.md)** - Monitoring and alerting -- *Coming Soon* + +- _Coming Soon_ --- diff --git a/docs/operations/troubleshooting.md b/docs/operations/troubleshooting.md index e2c8370..d77c677 100644 --- a/docs/operations/troubleshooting.md +++ b/docs/operations/troubleshooting.md @@ -9,6 +9,7 @@ This guide addresses common issues when deploying and running Bookhoard on diffe **Issue:** Missing or incorrect `.env` file **Solution:** + ```bash # 1. Copy the example file cp .env.example .env @@ -36,6 +37,7 @@ DBPASS="your-secure-database-password" # Strong password **Issue:** Port 8765 already in use **Solution:** + ```bash # Check what's using the port lsof -i :8765 @@ -52,6 +54,7 @@ SERVER_PORT=8766 **Issue:** Container runtime compatibility **Solution:** + ```bash # Podman is recommended (podman-compose works with docker-compose.yml) # Install podman-compose: @@ -72,6 +75,7 @@ docker compose up -d **Issue:** PostgreSQL fails on foreign key constraints **Solution:** + ```bash # Clean database volume and restart: # Podman: @@ -93,6 +97,7 @@ podman-compose logs db # or: docker compose logs db **Issue:** `sqlc` or `templ` not in PATH **Solution:** + ```bash # Ensure Go tools are installed correctly go version # Should be 1.25+ @@ -108,6 +113,7 @@ podman-compose build --no-cache # or: docker compose build --no-cache **Issue:** KEPUB conversion fails or cache problems **Solution:** + ```bash # Check cache directory exists and is writable ls -la /var/bookhoard/cache/kepub @@ -133,6 +139,7 @@ grep BOOKHOARD_CONVERSION .env **Issue:** Different OS architectures (ARM vs x86) **Solution:** + ```bash # Check current architecture uname -m @@ -152,6 +159,7 @@ FROM golang:1.25-alpine AS builder **Issue:** Can't connect to localhost **Solution:** + ```bash # Check if containers are running: podman-compose ps # or: docker compose ps @@ -169,6 +177,7 @@ curl -s http://SERVER_IP:8765/api/libraries/visible ## 🚀 Quick Verification ### Basic Health Checks: + ```bash # Check container status podman-compose ps # or: docker compose ps @@ -184,6 +193,7 @@ docker compose logs app ``` ### First-Time Setup: + ```bash # 1. Clone repository git clone @@ -205,6 +215,7 @@ curl -s http://localhost:8765/api/libraries/visible ## 🌐 Production Deployment ### Environment Variables: + ```bash # Required production variables export JWT_SECRET="your-production-jwt-secret" @@ -220,6 +231,7 @@ docker compose -f docker-compose.yml -f docker-compose.prod.yml build ## 📋 Debugging Steps ### Check Application Logs: + ```bash # Application logs: docker compose logs app @@ -232,6 +244,7 @@ docker compose logs -f app ``` ### Check Database Schema: + ```bash # Connect to database: docker compose exec db psql -U postgres -d bookhoard @@ -258,6 +271,7 @@ WHERE tc.table_schema = 'public'; ``` ### Test New Features: + ```bash # Test notes API: curl -s -H "Authorization: Bearer YOUR_TOKEN" \ @@ -273,24 +287,28 @@ curl -s -H "Authorization: Bearer YOUR_TOKEN" \ The current setup follows best practices: ### ✅ **Containerization** + - Multi-stage Docker builds for smaller images - Separate database and application containers - Proper volume mounting for data persistence - Health checks for service dependencies ### ✅ **Database Design** + - PostgreSQL with proper foreign key constraints - Cascade deletes for data integrity - Indexed for performance - pgx v5 compatibility ### ✅ **API Design** + - RESTful endpoints following standards - JWT-based authentication - Proper HTTP status codes - Comprehensive error handling ### ✅ **Build Process** + - Go modules with vendoring support - SQL code generation with sqlc - Template generation with templ @@ -323,6 +341,7 @@ The highlights and notes functionality provides: ## 🆘 When All Else Fails ### Last Resort Steps: + 1. **Check this guide** for common solutions 2. **Verify environment variables** are set correctly 3. **Ensure no port conflicts** on the target machine @@ -331,9 +350,10 @@ The highlights and notes functionality provides: 6. **Test incrementally** - Start with basic setup, then add complexity ### Get Help: + - **Check GitHub Issues** for known problems - **Verify Docker version** compatibility - **Test with minimal setup** before adding customizations - **Check system resources** (memory, disk space) -The system is designed to be robust and should work across different platforms with minimal configuration. \ No newline at end of file +The system is designed to be robust and should work across different platforms with minimal configuration. diff --git a/docs/user/admin-guide.md b/docs/user/admin-guide.md index 155153f..5b5ca20 100644 --- a/docs/user/admin-guide.md +++ b/docs/user/admin-guide.md @@ -3,6 +3,7 @@ **Coming Soon** This guide will cover: + - User management - Library management - System monitoring @@ -12,4 +13,4 @@ This guide will cover: --- -*In the meantime, check out our [Operations Portal](../operations/)* +_In the meantime, check out our [Operations Portal](../operations/)_ diff --git a/docs/user/dashboard.md b/docs/user/dashboard.md index 9a1264c..6a28ec0 100644 --- a/docs/user/dashboard.md +++ b/docs/user/dashboard.md @@ -19,6 +19,7 @@ Smart sections are automatically generated based on your reading activity: Any collection marked with "Show on Dashboard" will appear as a section on your dashboard. To enable a collection: + 1. Go to Collections 2. Edit a collection 3. Toggle "Show on Dashboard" diff --git a/docs/user/devices/kobo-setup.md b/docs/user/devices/kobo-setup.md index 0bbb630..003a97a 100644 --- a/docs/user/devices/kobo-setup.md +++ b/docs/user/devices/kobo-setup.md @@ -5,6 +5,7 @@ This guide will help you set up your Kobo e-reader to sync with Bookhoard for se ## What is Kobo Sync? Bookhoard implements a Kobo-compatible sync protocol that allows your Kobo device to: + - Sync reading progress across all your devices - Sync highlights and bookmarks - Sync reading statistics @@ -13,6 +14,7 @@ Bookhoard implements a Kobo-compatible sync protocol that allows your Kobo devic ## Prerequisites Before you begin, make sure you have: + - ✅ A Kobo e-reader device (Clara, Aura, Nia, Libra, Sage, Elipsa, etc.) - ✅ A Bookhoard instance running and accessible on your network - ✅ Your Bookhoard credentials (username and password) @@ -22,6 +24,7 @@ Before you begin, make sure you have: ## Supported Kobo Devices Bookhoard supports all Kobo devices that use the standard Kobo sync protocol: + - **Kobo Clara**: Clara 2E, Clara HD - **Kobo Aura**: Aura, Aura H2O, Aura ONE, Aura Edition 2 - **Kobo Libra**: Libra 2, Libra H2O @@ -53,6 +56,7 @@ Bookhoard supports all Kobo devices that use the standard Kobo sync protocol: 4. Click **Register Device** You'll receive: + - An **Auth URL** to approve the device - Instructions for manual configuration @@ -82,17 +86,20 @@ Your device is now registered and ready for configuration! ### Step 2: Edit Kobo Configuration File #### Windows Users + 1. Open **File Explorer** and navigate to your Kobo device 2. Open the `.kobo` folder (hidden folder) 3. Open `Kobo/Kobo eReader.conf` in a text editor (Notepad++, VS Code, etc.) #### Mac Users + 1. Kobo device appears on your Desktop 2. Right-click the Kobo volume and select **Show Package Contents** 3. Navigate to `.kobo/Kobo/Kobo eReader.conf` 4. Open in a text editor (TextEdit, VS Code, etc.) #### Linux Users + 1. Kobo mounts at `/media/USERNAME/Kobo` or similar 2. Navigate to `.kobo/Kobo/Kobo eReader.conf` 3. Open in a text editor @@ -116,10 +123,12 @@ SyncFrequency=5 ``` **Where to find these values**: + - `YOUR_COMPUTER_IP`: Your Bookhoard server's IP address (e.g., 192.168.1.100) - `YOUR_API_KEY`: Copy from Bookhoard Device Management → Your Kobo Device → "Copy Sync URL" **Example configuration**: + ```ini [Sync] ServerURL=http://192.168.1.100:8765/api/sync/kobo/dev_abc123def456 @@ -128,17 +137,20 @@ SyncFrequency=5 ``` **Important Notes**: + - The API key is generated during device registration - You can regenerate the API key anytime from Device Management if needed - Keep your API key confidential like a password - Bookhoard uses revocable API keys for security (not username/password) **Replace the following with your actual values**: + - `YOUR_COMPUTER_IP`: Your computer's local IP address (e.g., 192.168.1.100) - `YOUR_BOOKHOARD_USERNAME`: Your Bookhoard email or username - `YOUR_BOOKHOARD_PASSWORD`: Your Bookhoard password **Example configuration:** + ```ini [Sync] ServerURL=http://192.168.1.100:8765/api/sync/kobo @@ -166,6 +178,7 @@ Password=securePassword123 ### Reading Progress Sync Kobo syncs: + - **Percentage Read**: Overall book completion percentage - **Page Number**: Current page in fixed-layout books - **Time Spent**: Reading time statistics @@ -174,6 +187,7 @@ Kobo syncs: ### Annotations Sync Kobo syncs: + - **Bookmarks**: Page positions saved for quick access - **Highlights**: Highlighted text passages - **Notes**: Notes attached to highlights @@ -182,6 +196,7 @@ Kobo syncs: ### Shelf Management Kobo syncs: + - **Book Collections**: Your organized shelves - **Shelf Contents**: Books in each collection - **Sync Metadata**: When shelves were last updated @@ -272,6 +287,7 @@ OPDSCatalogURL=http://YOUR_COMPUTER_IP:8765/opds/devices/YOUR_DEVICE_ID/catalog? #### Format Support Kobo OPDS supports: + - **EPUB**: Standard ebook format (recommended) - **KEPUB**: Kobo-optimized EPUB (better page turns, fonts) - **PDF**: Fixed-layout documents @@ -281,6 +297,7 @@ Kobo OPDS supports: #### Progress Sync Books downloaded via OPDS automatically sync progress: + 1. Download a book via OPDS 2. Start reading on your Kobo 3. Progress syncs to Bookhoard automatically @@ -289,6 +306,7 @@ Books downloaded via OPDS automatically sync progress: #### Collection to Shelf Mapping Bookhoard maps your collections to Kobo shelves: + - Collection **"Science Fiction"** → Kobo shelf **"Sci-Fi"** - Collection **"To Read"** → Kobo shelf **"To Read"** - Customizable in Bookhoard Device Management @@ -300,6 +318,7 @@ Bookhoard maps your collections to Kobo shelves: **Problem**: Bookhoard catalog doesn't show in Kobo store **Solutions**: + 1. Verify OPDS URL is correct in config file 2. Check Kobo is connected to Wi-Fi 3. Try accessing OPDS URL in your browser @@ -311,6 +330,7 @@ Bookhoard maps your collections to Kobo shelves: **Problem**: Book download starts but fails partway through **Solutions**: + 1. Check Wi-Fi signal strength 2. Ensure Bookhoard server is running 3. Verify book file exists in Bookhoard library @@ -322,6 +342,7 @@ Bookhoard maps your collections to Kobo shelves: **Problem**: Downloaded book shows error when opening **Solutions**: + 1. Verify book format is supported (EPUB/KEPUB/PDF) 2. Check file isn't corrupted in Bookhoard 3. Try downloading via USB and opening @@ -333,6 +354,7 @@ Bookhoard maps your collections to Kobo shelves: **Problem**: Books take too long to download **Solutions**: + 1. Ensure strong Wi-Fi signal (stay near router) 2. Use 5GHz Wi-Fi if your Kobo supports it 3. Close other apps using bandwidth @@ -341,14 +363,14 @@ Bookhoard maps your collections to Kobo shelves: ### OPDS vs USB Transfer -| Feature | OPDS (Wireless) | USB Transfer | -|---------|----------------|--------------| -| **Convenience** | ⭐⭐⭐⭐⭐ No cable needed | ⭐⭐ Requires cable | -| **Speed** | ⭐⭐⭐ Fast (Wi-Fi dependent) | ⭐⭐⭐⭐⭐ Very fast | -| **Bulk Transfer** | ⭐⭐⭐ One at a time | ⭐⭐⭐⭐⭐ Many at once | -| **Progress Sync** | ⭐⭐⭐⭐⭐ Automatic | ⭐⭐⭐⭐ After first sync | -| **Setup Complexity** | ⭐⭐⭐ Moderate | ⭐⭐⭐⭐⭐ Simple | -| **Reliability** | ⭐⭐⭐⭐ Good | ⭐⭐⭐⭐⭐ Excellent | +| Feature | OPDS (Wireless) | USB Transfer | +| -------------------- | ----------------------------- | ------------------------- | +| **Convenience** | ⭐⭐⭐⭐⭐ No cable needed | ⭐⭐ Requires cable | +| **Speed** | ⭐⭐⭐ Fast (Wi-Fi dependent) | ⭐⭐⭐⭐⭐ Very fast | +| **Bulk Transfer** | ⭐⭐⭐ One at a time | ⭐⭐⭐⭐⭐ Many at once | +| **Progress Sync** | ⭐⭐⭐⭐⭐ Automatic | ⭐⭐⭐⭐ After first sync | +| **Setup Complexity** | ⭐⭐⭐ Moderate | ⭐⭐⭐⭐⭐ Simple | +| **Reliability** | ⭐⭐⭐⭐ Good | ⭐⭐⭐⭐⭐ Excellent | **Recommendation**: Use OPDS for convenience (1-5 books), use USB for bulk transfers (10+ books). @@ -455,6 +477,7 @@ Kobo will automatically trust the certificate if properly configured. **Problem**: Sync doesn't happen automatically **Solutions**: + 1. Check Kobo is connected to Wi-Fi 2. Verify `AutoSyncEnabled=true` in config 3. Check `SyncFrequency` is not set to 0 @@ -466,6 +489,7 @@ Kobo will automatically trust the certificate if properly configured. **Problem**: "Connection refused" or "Server not reachable" **Solutions**: + 1. Verify Bookhoard is running on your computer 2. Check the server URL and IP address are correct 3. Ensure Kobo is on same Wi-Fi network as computer @@ -475,8 +499,9 @@ Kobo will automatically trust the certificate if properly configured. ### Authentication Failed **Problem**: "Authentication failed" or "Invalid API key" - + **Solutions**: + 1. Verify the API key in your sync URL matches the one in Bookhoard Device Management 2. Check that device is approved in Bookhoard (not pending) 3. Try regenerating the API key from Device Management page @@ -488,6 +513,7 @@ Kobo will automatically trust the certificate if properly configured. **Problem**: Changes to `Kobo eReader.conf` are lost **Solutions**: + 1. Make sure Kobo is ejected safely after editing 2. Check file permissions (should be writable) 3. Try a different text editor (Notepad++, VS Code, Sublime Text) @@ -499,6 +525,7 @@ Kobo will automatically trust the certificate if properly configured. **Problem**: Manual sync works, but auto-sync doesn't **Solutions**: + 1. Verify `AutoSyncEnabled=true` in config 2. Check `SyncFrequency` is not 0 3. Kobo only syncs when connected to Wi-Fi @@ -510,6 +537,7 @@ Kobo will automatically trust the certificate if properly configured. **Problem**: Books added to Bookhoard don't show on Kobo **Solutions**: + 1. Kobo needs books to be sideloaded (manually transferred via USB) 2. Bookhoard syncs PROGRESS, not book files 3. Transfer book files to Kobo's `Documents` folder via USB @@ -521,6 +549,7 @@ Kobo will automatically trust the certificate if properly configured. **Problem**: Conflicts between devices aren't being detected **Solutions**: + 1. Check Bookhoard Conflicts page 2. Ensure both devices have synced recently 3. Conflicts only detected when progress differs within 5 minutes @@ -540,6 +569,7 @@ Kobo will automatically trust the certificate if properly configured. ### Local Network (Recommended) For home use, keep Kobo and Bookhoard on the same local network: + ``` Kobo Wi-Fi: 192.168.1.x Bookhoard: 192.168.1.x @@ -548,6 +578,7 @@ Bookhoard: 192.168.1.x ### Remote Access For access outside your home network: + 1. Set up port forwarding on your router (port 8765) 2. Configure SSL/TLS on Bookhoard 3. Use a dynamic DNS service for constant hostname @@ -562,6 +593,7 @@ For access outside your home network: ### Battery Life To extend Kobo battery life: + 1. Use longer sync intervals (15-30 minutes) 2. Sync only on Wi-Fi (not cellular if your Kobo has it) 3. Disable unnecessary Kobo features @@ -570,6 +602,7 @@ To extend Kobo battery life: ### Sync Speed To improve sync speed: + 1. Ensure strong Wi-Fi signal 2. Use local network (not remote access) 3. Keep Bookhoard and Kobo on same network @@ -603,6 +636,7 @@ A: Only if Wi-Fi is enabled and configured to stay active during sleep. ## Support If you encounter issues: + 1. Check the troubleshooting section above 2. Review Kobo sync logs in device settings 3. Check Bookhoard sync queue and device management pages diff --git a/docs/user/devices/koreader-setup.md b/docs/user/devices/koreader-setup.md index c003950..8245d3b 100644 --- a/docs/user/devices/koreader-setup.md +++ b/docs/user/devices/koreader-setup.md @@ -5,6 +5,7 @@ This guide will help you set up KOReader on your e-reader device to sync with Bo ## What is KOReader? KOReader is an open-source e-reader application that supports a wide range of e-reader devices including: + - Kindle devices (Paperwhite, Oasis, Voyage, etc.) - Kobo devices (Clara, Aura, Nia, etc.) - PocketBook devices @@ -13,6 +14,7 @@ KOReader is an open-source e-reader application that supports a wide range of e- ## Prerequisites Before you begin, make sure you have: + - ✅ A Bookhoard instance running and accessible on your network - ✅ Your Bookhoard credentials (username and password) - ✅ A KOReader-compatible e-reader device @@ -67,6 +69,7 @@ Before you begin, make sure you have: ### Step 1: Get Your Bookhoard Instance URL Find your Bookhoard instance URL. This will typically be one of: + - **Local Network**: `http://YOUR_COMPUTER_IP:8765` - **Localhost (if testing)**: `http://localhost:8765` - **Domain (if configured)**: `https://bookhoard.yourdomain.com` @@ -84,6 +87,7 @@ Find your Bookhoard instance URL. This will typically be one of: 4. Click **Register Device** You'll receive: + - An **Auth URL** to approve the device - A **Device Token** (automatically generated after approval) @@ -114,9 +118,11 @@ Your device is now registered and ready to sync! 1. **Enable Calibre Wireless Connection**: Toggle ON 2. **Server Address**: Enter your Bookhoard instance URL + ``` http://YOUR_COMPUTER_IP:8765/api/sync/koreader ``` + Replace `YOUR_COMPUTER_IP` with your actual IP address 3. **Set Custom Port** (if needed): Keep default or enter `8765` @@ -157,6 +163,7 @@ Your device is now registered and ready to sync! ### Initial Sync When you first enable sync, KOReader will: + 1. Connect to Bookhoard 2. Upload your current reading progress 3. Download any annotations from the server @@ -165,6 +172,7 @@ When you first enable sync, KOReader will: ### Reading Progress Sync As you read: + - Progress updates automatically sync based on your sync frequency - Page turns, chapter changes, and bookmark saves all trigger sync - Sync occurs in the background without interrupting reading @@ -179,11 +187,13 @@ As you read: ### Manual Sync To manually trigger a sync: + 1. Open the KOReader menu (≡) 2. Select **Tools** → **Calibre** 3. Tap **Sync Now** The sync status will display: + - 🟢 **Synced** - All changes uploaded - 🟡 **Syncing...** - In progress - 🔴 **Failed** - Check your network connection @@ -193,6 +203,7 @@ The sync status will display: ### Offline Mode KOReader automatically handles offline scenarios: + 1. Changes are queued locally when offline 2. Auto-sync resumes when connected 3. Queue processes all pending changes in priority order @@ -200,6 +211,7 @@ KOReader automatically handles offline scenarios: ### Checkpoint Sync For better battery life, use checkpoint mode: + 1. In KOReader Calibre settings 2. Set **Sync Mode** to "Checkpoint" 3. Set **Checkpoint Interval** (e.g., every 5 minutes) @@ -208,6 +220,7 @@ For better battery life, use checkpoint mode: ### Debug Mode Enable debug logging if sync isn't working: + 1. KOReader menu → Tools → Calibre 2. Enable **Debug Logging** 3. Sync and check logs at `/mnt/us/koreader/calibre.log` @@ -279,6 +292,7 @@ Your Bookhoard library now appears in KOReader's home screen! #### Supported Formats KOReader OPDS supports: + - **EPUB**: Standard ebook format - **KEPUB**: Kobo-optimized format (KOReader handles this well) - **PDF**: Fixed-layout documents @@ -289,6 +303,7 @@ KOReader OPDS supports: #### Automatic Book Matching Books downloaded via OPDS are automatically matched: + - Uses SHA-256 hashes for precise matching - Falls back to title/author matching - Links to your existing Bookhoard library @@ -297,6 +312,7 @@ Books downloaded via OPDS are automatically matched: #### Collection Integration Your Bookhoard collections appear in KOReader: + - Collection **"To Read"** → KOReader category - Collection **"Science Fiction"** → Browseable section - Custom collections → Preserved organization @@ -306,6 +322,7 @@ Your Bookhoard collections appear in KOReader: #### Update Interval Configure how often KOReader checks for new books: + 1. KOReader menu → Tools → OPDS 2. Set **Update Interval**: 5min, 15min, 1hr, manual 3. **Recommended**: 15min for balance @@ -313,6 +330,7 @@ Configure how often KOReader checks for new books: #### Download Location Choose where to store downloaded books: + 1. KOReader menu → File Browser 2. Set **Default Download Folder** 3. **Recommended**: `/mnt/us/Documents/` (Kindle) or `/mnt/onboard/Documents/` (Kobo) @@ -320,6 +338,7 @@ Choose where to store downloaded books: #### Auto-Download Automatically download new books from collections: + 1. KOReader menu → Tools → OPDS 2. Enable **Auto-Download New Books** 3. Select collections to monitor @@ -332,6 +351,7 @@ Automatically download new books from collections: **Problem**: Bookhoard catalog shows error or won't load **Solutions**: + 1. Verify device is connected to Wi-Fi 2. Check OPDS URL is correct in settings 3. Try accessing OPDS URL in your browser @@ -343,6 +363,7 @@ Automatically download new books from collections: **Problem**: Book download starts but fails **Solutions**: + 1. Check Wi-Fi signal strength 2. Ensure sufficient storage on device 3. Try downloading a smaller book @@ -354,6 +375,7 @@ Automatically download new books from collections: **Problem**: Downloaded book doesn't sync progress **Solutions**: + 1. Verify book is matched to Bookhoard library 2. Check device sync settings are enabled 3. Try manual sync from device @@ -365,6 +387,7 @@ Automatically download new books from collections: **Problem**: Books take too long to download **Solutions**: + 1. Stay close to Wi-Fi router 2. Use 5GHz Wi-Fi if available 3. Close other apps using bandwidth @@ -401,14 +424,14 @@ OPDSCompressionEnabled = true ### OPDS vs USB Transfer -| Feature | OPDS (Wireless) | USB Transfer | -|---------|----------------|--------------| -| **Convenience** | ⭐⭐⭐⭐⭐ No cable needed | ⭐⭐ Requires cable | -| **Speed** | ⭐⭐⭐ Fast (Wi-Fi dependent) | ⭐⭐⭐⭐⭐ Very fast | -| **Bulk Transfer** | ⭐⭐⭐ One at a time | ⭐⭐⭐⭐⭐ Many at once | -| **Progress Sync** | ⭐⭐⭐⭐⭐ Instant | ⭐⭐⭐⭐ After transfer | -| **Accessibility** | ⭐⭐⭐⭐⭐ Anywhere | ⭐⭐ At computer only | -| **Reliability** | ⭐⭐⭐⭐ Very good | ⭐⭐⭐⭐⭐ Excellent | +| Feature | OPDS (Wireless) | USB Transfer | +| ----------------- | ----------------------------- | ----------------------- | +| **Convenience** | ⭐⭐⭐⭐⭐ No cable needed | ⭐⭐ Requires cable | +| **Speed** | ⭐⭐⭐ Fast (Wi-Fi dependent) | ⭐⭐⭐⭐⭐ Very fast | +| **Bulk Transfer** | ⭐⭐⭐ One at a time | ⭐⭐⭐⭐⭐ Many at once | +| **Progress Sync** | ⭐⭐⭐⭐⭐ Instant | ⭐⭐⭐⭐ After transfer | +| **Accessibility** | ⭐⭐⭐⭐⭐ Anywhere | ⭐⭐ At computer only | +| **Reliability** | ⭐⭐⭐⭐ Very good | ⭐⭐⭐⭐⭐ Excellent | **Recommendation**: Use OPDS for daily reading (convenience), USB for bulk library transfers. @@ -427,6 +450,7 @@ OPDSCompressionEnabled = true **Problem**: "Connection refused" error **Solutions**: + - Verify Bookhoard is running on your computer - Check the server URL and port (8765) - Ensure device is on same Wi-Fi network @@ -437,6 +461,7 @@ OPDSCompressionEnabled = true **Problem**: "Authentication failed" error **Solutions**: + - Verify username and password - Check your account is active and not locked - Try logging in to Bookhoard web interface first @@ -447,6 +472,7 @@ OPDSCompressionEnabled = true **Problem**: Changes not appearing in Bookhoard **Solutions**: + - Enable debug logging in KOReader - Check Bookhoard Device Management page for errors - Verify sync is enabled in KOReader settings @@ -458,6 +484,7 @@ OPDSCompressionEnabled = true **Problem**: Sync conflicts when reading on multiple devices **Solutions**: + 1. Go to Bookhoard **Conflicts** page 2. Review conflicting progress from each device 3. Choose which device's progress to keep @@ -468,6 +495,7 @@ OPDSCompressionEnabled = true **Problem**: Large annotations or highlights fail to sync **Solutions**: + - Check Bookhoard sync queue for stuck items - Increase sync timeout in KOReader settings - Break up large highlights into smaller segments @@ -491,6 +519,7 @@ OPDSCompressionEnabled = true ## Support If you encounter issues: + 1. Check the troubleshooting section above 2. Enable debug logging and review KOReader logs 3. Check Bookhoard sync queue and device management pages diff --git a/docs/user/frontend-guide.md b/docs/user/frontend-guide.md index 7d2ff8a..f897de9 100644 --- a/docs/user/frontend-guide.md +++ b/docs/user/frontend-guide.md @@ -3,6 +3,7 @@ **Coming Soon** This guide will cover: + - Navigating the Bookhoard web interface - Browsing and searching your library - Managing collections @@ -12,4 +13,4 @@ This guide will cover: --- -*In the meantime, check out our [Device Setup Guides](devices/)* +_In the meantime, check out our [Device Setup Guides](devices/)_ diff --git a/docs/user/profile-guide.md b/docs/user/profile-guide.md index 2577e3e..4d690f4 100644 --- a/docs/user/profile-guide.md +++ b/docs/user/profile-guide.md @@ -47,6 +47,7 @@ Regular password changes are recommended for account security. ### What Gets Deleted When you delete your account: + - Your profile information - Reading progress and history - Device connections @@ -88,7 +89,7 @@ Personalize your reading experience with different color themes. - **One Dark Pro** - Atom editor inspired - **Material Dark** - Google Material Design - **Wood Light** - Light wood texture -- **Wood Dark** - Dark wood texture +- **Wood Dark** - Dark wood texture - **Wood Mahogany** - Reddish-brown wood ## For Admin Users diff --git a/docs/user/settings-guide.md b/docs/user/settings-guide.md index 59c7f0b..d008fd2 100644 --- a/docs/user/settings-guide.md +++ b/docs/user/settings-guide.md @@ -3,6 +3,7 @@ **Coming Soon** This guide will cover: + - Account settings - Display preferences (themes, fonts) - Sync configuration @@ -11,4 +12,4 @@ This guide will cover: --- -*In the meantime, check out our [Device Setup Guides](devices/)* +_In the meantime, check out our [Device Setup Guides](devices/)_ diff --git a/docs/user/sync-guide.md b/docs/user/sync-guide.md index 2db0ac6..b8adf15 100644 --- a/docs/user/sync-guide.md +++ b/docs/user/sync-guide.md @@ -1,6 +1,7 @@ # Bookhoard Universal Sync - User Guide ## Table of Contents + 1. [What is Universal Sync?](#what-is-universal-sync) 2. [Supported Devices](#supported-devices) 3. [Getting Started](#getting-started) @@ -36,19 +37,19 @@ ### Currently Supported ✅ -| Platform | Status | Sync Method | Notes | -|----------|--------|-------------|-------| -| **Web Browser** | ✅ Fully Supported | Real-time WebSocket | Any modern browser | -| **KOReader** | ✅ Fully Supported | Wi-Fi (Calibre-compatible) | Kindle, Kobo, PocketBook, etc. | -| **Kobo Devices** | ✅ Fully Supported | Wi-Fi (Kobo API-compatible) | Clara, Libra, Sage, etc. | +| Platform | Status | Sync Method | Notes | +| ---------------- | ------------------ | --------------------------- | ------------------------------ | +| **Web Browser** | ✅ Fully Supported | Real-time WebSocket | Any modern browser | +| **KOReader** | ✅ Fully Supported | Wi-Fi (Calibre-compatible) | Kindle, Kobo, PocketBook, etc. | +| **Kobo Devices** | ✅ Fully Supported | Wi-Fi (Kobo API-compatible) | Clara, Libra, Sage, etc. | ### Coming Soon 🚧 -| Platform | Expected Release | -|----------|------------------| -| **Mobile Apps** | Q2 2026 | -| **Kindle Devices** | Q3 2026 | -| **Remarkable Tablet** | Q4 2026 | +| Platform | Expected Release | +| --------------------- | ---------------- | +| **Mobile Apps** | Q2 2026 | +| **Kindle Devices** | Q3 2026 | +| **Remarkable Tablet** | Q4 2026 | --- @@ -79,17 +80,20 @@ For detailed device configuration instructions, see the appropriate setup guide: ### Quick Overview **Registration Process**: + 1. Register device in Bookhoard web interface (Settings → Devices) 2. Approve device via QR code or approval URL 3. Configure sync settings on your device 4. Start reading - progress syncs automatically! **Device Management**: + ``` Settings → Devices ``` You can: + - View all your registered devices - See last sync time and status - Disable or remove devices @@ -103,6 +107,7 @@ You can: ### What is Book Matching? When devices sync books, Bookhoard tries to automatically match them using: + - **SHA-256 hash** (most reliable) - Content-based fingerprint - **ISBN** - Standard book identifier - **UUID** - Unique identifier from EPUB metadata @@ -111,6 +116,7 @@ When devices sync books, Bookhoard tries to automatically match them using: ### Unlinked Books Sometimes a book on your device can't be automatically matched to your library. This happens when: + - The book was side-loaded (not downloaded via Bookhoard) - The file format was converted (EPUB → KEPUB) - The metadata doesn't match exactly @@ -143,6 +149,7 @@ Settings → Devices → Select Device → View Unlinked Books ### Reading Progress **What Syncs**: + - Current page number - Reading percentage - Chapter progress @@ -151,6 +158,7 @@ Settings → Devices → Select Device → View Unlinked Books - Reading position (viewport, zoom, scroll) **How It Works**: + ``` You turn page → Device sends progress → Server updates database ↓ @@ -160,6 +168,7 @@ You turn page → Device sends progress → Server updates database ``` **Supported Progress Types**: + - **EPUB/MOBI**: Percentage + EPUB CFI + Chapter - **PDF/DJVU**: Page number + Viewport position - **CBZ/CBR**: Page number + Panel coordinates @@ -167,6 +176,7 @@ You turn page → Device sends progress → Server updates database ### Highlights & Notes **What Syncs**: + - Highlighted text - Notes and annotations - Bookmark locations @@ -175,6 +185,7 @@ You turn page → Device sends progress → Server updates database **Universal Location References**: All highlights are stored with multiple location types: + - Page:offset (traditional) - EPUB CFI (EPUB files) - Percentage (0-100%) @@ -186,6 +197,7 @@ This ensures your highlights work across all devices, even with different page c ### Bookmarks **What Syncs**: + - Bookmark locations - Bookmark titles - Date created @@ -200,12 +212,14 @@ This ensures your highlights work across all devices, even with different page c **Best For**: Normal reading, page turns **Behavior**: + - Syncs every page turn - Real-time updates - Low latency - Higher bandwidth usage **Recommended Settings**: + - Auto-sync: ON - Sync frequency: Every page turn @@ -214,12 +228,14 @@ This ensures your highlights work across all devices, even with different page c **Best For**: Slow connections, battery saving **Behavior**: + - Batches changes - Syncs every 5 minutes or when connection allows - Lower bandwidth - Better for offline reading **Recommended Settings**: + - Auto-sync: ON - Sync frequency: Checkpoint mode @@ -247,11 +263,13 @@ This ensures your highlights work across all devices, even with different page c ### Offline Indicators **In Web Interface**: + - Yellow status icon: Device offline - Last seen timestamp - "Pending sync" badge on books **On Devices**: + - Sync icon: Gray = offline - Sync icon: Blue = syncing - Sync icon: Green = synced @@ -265,6 +283,7 @@ This ensures your highlights work across all devices, even with different page c **Symptoms**: Progress not updating across devices **Solutions**: + 1. Check device is online: `Settings → Devices` 2. Verify sync is enabled for the device 3. Check sync URL is correct @@ -276,6 +295,7 @@ This ensures your highlights work across all devices, even with different page c **Cause**: Device not registered or authorization revoked **Solutions**: + 1. Re-register the device 2. Check device hasn't been removed 3. Verify correct device type selected @@ -285,6 +305,7 @@ This ensures your highlights work across all devices, even with different page c **Cause**: Too many sync requests **Solutions**: + 1. Wait a few seconds 2. Switch to checkpoint mode 3. Contact admin to increase limits @@ -296,6 +317,7 @@ This ensures your highlights work across all devices, even with different page c **Cause**: Same book being read on multiple devices simultaneously **Solutions**: + 1. Go to `Settings → Conflicts` 2. Review both device progress 3. Choose which device's progress to keep @@ -306,6 +328,7 @@ This ensures your highlights work across all devices, even with different page c **Cause**: Immediate sync mode with frequent page turns **Solutions**: + 1. Switch to checkpoint mode 2. Increase sync interval 3. Use Wi-Fi instead of cellular (for mobile) @@ -317,6 +340,7 @@ This ensures your highlights work across all devices, even with different page c ### For Optimal Performance ✅ **DO**: + - Use checkpoint mode when on cellular data - Keep device firmware updated - Use Wi-Fi when available @@ -324,6 +348,7 @@ This ensures your highlights work across all devices, even with different page c - Regularly check conflict resolution ❌ **DON'T**: + - Read same book on multiple devices simultaneously - Ignore conflict notifications - Register public/shared devices @@ -332,6 +357,7 @@ This ensures your highlights work across all devices, even with different page c ### Organizing Your Library **For Best Sync Experience**: + - Use consistent metadata (titles, authors) - Avoid duplicate books in library - Match files by ISBN when possible @@ -340,11 +366,13 @@ This ensures your highlights work across all devices, even with different page c ### Managing Multiple Devices **Recommended Setup**: + - **Primary Device**: KOReader on e-reader - **Secondary Device**: Web browser (work/home) - **Mobile Device**: Phone app (commute) **Sync Strategy**: + 1. Read mainly on primary device 2. Check progress on web/secondary devices 3. Let auto-sync handle updates @@ -357,16 +385,19 @@ This ensures your highlights work across all devices, even with different page c ### Conflict Resolution **Automatic Resolution**: + - Most recent progress wins - Timestamp-based comparison - 5-minute window for conflict detection **Manual Resolution**: + ``` Settings → Conflicts → Select conflict → Choose winner ``` **Options**: + - **Keep Device A**: Use this device's progress - **Keep Device B**: Use other device's progress - **Merge**: Keep furthest progress (combination) @@ -375,17 +406,20 @@ Settings → Conflicts → Select conflict → Choose winner ### Sync Queue Management **View Queue Status**: + ``` Settings → Devices → Select Device → View Queue ``` **Queue Stats**: + - Pending: Waiting to sync - Processing: Currently syncing - Failed: Retry scheduled - Completed: Successfully synced **Manual Actions**: + - **Retry All**: Retry all failed items - **Clear Queue**: Remove all pending items - **Priority Sync**: Sync specific book immediately @@ -393,17 +427,20 @@ Settings → Devices → Select Device → View Queue ### Reading History **Automatic Tracking**: + - Every sync session logged - Time spent reading calculated - Pages read tracked - Device used recorded **View History**: + ``` Book → Reading History ``` **Privacy**: + - Only you can see your history - History kept for 365 days - Exportable for backup @@ -415,6 +452,7 @@ Book → Reading History ### Device Authentication **Secure by Design**: + - ✅ No passwords stored on devices - ✅ Web-based approval required - ✅ Unique tokens per device @@ -424,12 +462,14 @@ Book → Reading History ### Data Protection **What We Store**: + - Reading progress (page, percentage) - Highlights and notes - Device identifiers - Sync timestamps **What We DON'T Store**: + - Passwords on devices - Reading content (your books) - Unencrypted personal data @@ -438,11 +478,13 @@ Book → Reading History ### Access Control **Your Data**: + - Only you can see your progress - Admins cannot read your annotations - Shared only with devices you approve **Device Access**: + - Each device sees only your libraries - Devices cannot access other users - Revoking removes all access @@ -530,6 +572,7 @@ A: Yes, HTTPS/TLS 1.3 for all sync traffic. ## Changelog ### Version 1.0.0 (January 2026) + - ✅ Initial release - ✅ KOReader sync support - ✅ Kobo device support diff --git a/docs/user/user-areas.md b/docs/user/user-areas.md index 9483fff..b6b8988 100644 --- a/docs/user/user-areas.md +++ b/docs/user/user-areas.md @@ -3,6 +3,7 @@ **Coming Soon** This guide will cover: + - Managing your personal library - Uploading and organizing books - Creating and managing collections @@ -11,4 +12,4 @@ This guide will cover: --- -*In the meantime, check out our [Sync Guide](sync-guide.md)* +_In the meantime, check out our [Sync Guide](sync-guide.md)_ diff --git a/docs/user/user-guide.md b/docs/user/user-guide.md index 0a3779f..43d96f0 100644 --- a/docs/user/user-guide.md +++ b/docs/user/user-guide.md @@ -30,22 +30,26 @@ Learn how to configure your e-reader devices to sync with Bookhoard: ## 🎨 Frontend Guide **[Frontend Guide](frontend-guide.md)** - Learn how to use the Bookhoard web interface -- *Coming Soon* + +- _Coming Soon_ ## 👤 User Areas **[User Areas Guide](user-areas.md)** - Managing your personal library and settings -- *Coming Soon* + +- _Coming Soon_ ## ⚙️ Settings **[Settings Guide](settings-guide.md)** - Configuring your Bookhoard preferences -- *Coming Soon* + +- _Coming Soon_ ## 🔐 Admin Features **[Admin Guide](admin-guide.md)** - Administrative functions and management -- *Coming Soon* + +- _Coming Soon_ --- diff --git a/scripts/README.md b/scripts/README.md index 728e4c3..d026f66 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -5,9 +5,11 @@ This directory contains verification scripts that enforce compliance with PROJEC ## Scripts Overview ### `verify-guidelines.sh` (Comprehensive Verification) + **Purpose:** Full validation of all project guidelines including documentation compliance **Usage:** `make verify-guidelines` or `./scripts/verify-guidelines.sh` **Features:** + - 16 comprehensive checks covering all aspects of PROJECT_GUIDELINES.md - High-recall pattern detection for documentation routing validation - AI behavior protocol with multi-layered safeguards @@ -15,6 +17,7 @@ This directory contains verification scripts that enforce compliance with PROJEC - Detailed analysis of recent changes and commit quality **Checks Included:** + 1. No Custom CSS (Frontend & Styling) 2. No JavaScript Files (Use TypeScript) 3. No Secrets Committed @@ -33,15 +36,18 @@ This directory contains verification scripts that enforce compliance with PROJEC 16. Documentation Completeness Validation ### `verify-quick.sh` (Critical-Only Verification) + **Purpose:** Fast development-time checks focusing on critical violations only **Usage:** `make verify-quick` or `./scripts/verify-quick.sh` **Features:** + - 7 core sections covering critical prohibitions and essential requirements - Optimized for speed during active development - AI behavior protocol with comprehensive safeguards - Focus on blocking issues (errors) vs. improvement suggestions (warnings) **Sections Included:** + - 🚨 CRITICAL PROHIBITIONS: Backend & Database - 🚨 CRITICAL PROHIBITIONS: Frontend & Styling - 🚨 CRITICAL PROHIBITIONS: General @@ -55,6 +61,7 @@ This directory contains verification scripts that enforce compliance with PROJEC Both scripts include comprehensive AI behavior safeguards to prevent automatic fixing of guideline violations: ### Protocol Requirements: + 1. **NEVER** automatically fix anything without explicit user permission 2. **FOR EACH** issue found: - Explain what the issue is @@ -69,6 +76,7 @@ Both scripts include comprehensive AI behavior safeguards to prevent automatic f 7. **APPLY** to ALL file modifications, not just verification issues ### Implementation: + - **Header Protocol**: Comprehensive instructions at script start - **Function Reminders**: Each error/warning includes AI instruction reminder - **Final Reminder**: End-of-script reinforcement of protocol @@ -79,7 +87,9 @@ Both scripts include comprehensive AI behavior safeguards to prevent automatic f ## Usage Guidelines ### During Active Development: + Use `make verify-quick` for fast feedback on critical violations: + - No local binaries - No custom CSS/JavaScript violations - No secrets committed @@ -87,7 +97,9 @@ Use `make verify-quick` for fast feedback on critical violations: - Essential build requirements ### Pre-Commit / CI/CD: + Use `make verify-guidelines` for comprehensive validation: + - All critical prohibitions - Documentation compliance validation - Bruno API test coverage @@ -97,9 +109,11 @@ Use `make verify-guidelines` for comprehensive validation: ### When Issues Are Found: #### Enhanced Error/Warning Output (Latest): + Both scripts now provide detailed information for all issues found: #### For Errors (Critical Violations): + ```bash # Example: AI detects local binary ❌ ERROR: Found 1 binary files (should build through Dockerfile) @@ -114,6 +128,7 @@ Do you want me to fix this? [y/n]" ``` #### For Warnings (Documentation/Style Issues): + ```bash # Example: AI finds API content in README ⚠ WARNING: Found 2 API patterns in README.md (verify placement per guidelines) @@ -126,7 +141,7 @@ Found patterns: "I found API documentation patterns in README.md. According to the guidelines, API endpoints should be in 'docs/api//.md' unless this is a breaking change. The specific patterns found are: -- Line 81: Rate limiting information +- Line 81: Rate limiting information - Line 98: WebSocket API reference Do you want me to fix this? [y/n]" @@ -135,16 +150,18 @@ Do you want me to fix this? [y/n]" #### Complete Enhanced Output Examples: ##### 1. Build Failure with Detailed Errors: + ```bash ❌ ERROR: Build failed (violation: must compile after edits) AI INSTRUCTION: Ask user before fixing this issue Build error details: # cmd/server/main.go:45:2: syntax error: unexpected newline, expecting } -# +# # Try running: go build ./cmd/server ``` ##### 2. Missing Configuration Files: + ```bash ❌ ERROR: .env not in .gitignore (violation: secrets might be committed) AI INSTRUCTION: Ask user before fixing this issue @@ -163,6 +180,7 @@ build/ ``` ##### 3. API Content Detection with Line Numbers: + ```bash ⚠ WARNING: Found 2 API patterns in README.md (verify placement per guidelines) AI INSTRUCTION: Ask user before fixing this issue @@ -172,6 +190,7 @@ Found patterns: ``` ##### 4. File Organization Violations: + ```bash ❌ ERROR: Found 3 .js files (violation: JavaScript prohibited, use TypeScript) AI INSTRUCTION: Ask user before fixing this issue @@ -182,6 +201,7 @@ Found files: ``` ##### 5. Documentation Structure Validation: + ```bash ✓ PASS: docs/api directory exists ✓ PASS: docs/devices directory missing @@ -197,6 +217,7 @@ Expected directory structure: ``` ##### 6. Bruno API Tests Coverage Analysis: + ```bash ✓ PASS: Found 47 Bruno test files ⚠ WARNING: Bruno test files (47) fewer than API docs (52) @@ -205,6 +226,7 @@ Coverage gap: API docs (52) vs Bruno tests (47) ``` ##### 7. Git Commit Quality Analysis: + ```bash ⚠ WARNING: Found 3 recent commits changing >15 files each (should use multiple commits) AI INSTRUCTION: Ask user before fixing this issue @@ -215,6 +237,7 @@ b7c8f9e: fix: resolve merge conflicts (16 files) ``` ##### 8. CSS Template Violations: + ```bash ❌ ERROR: Found 12 templates with @@ -634,6 +697,7 @@ Do you want me to fix this? [y/n]" ### Advanced Troubleshooting #### Debugging Verification Issues: + ```bash # Test individual components: bash -x scripts/verify-quick.sh 2>&1 | tee debug.log @@ -649,6 +713,7 @@ grep -E "## API|endpoint" README.md | wc -l ``` #### Handling Edge Cases: + ```bash # Missing directories (graceful handling): if [ ! -d "docs/api" ]; then @@ -668,6 +733,7 @@ echo "$LONG_OUTPUT" | less # or | head -20 ``` #### Performance Optimization: + ```bash # Quick development checks (skip expensive operations): export FAST_MODE=true @@ -682,9 +748,9 @@ make verify-guidelines check_binary_files ) & ( - check_css_violations + check_css_violations ) & wait # Wait for both to complete ``` -This comprehensive documentation ensures verification scripts are fully understood and can be effectively integrated into any development workflow. \ No newline at end of file +This comprehensive documentation ensures verification scripts are fully understood and can be effectively integrated into any development workflow.