From 465a2733191afd9f66a831b2d05507094edaaf40 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 7 Feb 2026 19:50:39 -0500 Subject: [PATCH] chore: remove obsolete refactoring plan documents - Remove ROUTER_REFACTOR_PLAN.md (superseded by EBOOK_REFACTOR_PLAN.md) - Remove SCANNER_RESTORATION_PLAN.md (no longer needed) EBOOK_REFACTOR_PLAN.md remains as the active refactoring plan. --- ROUTER_REFACTOR_PLAN.md | 479 ------------------------ SCANNER_RESTORATION_PLAN.md | 699 ------------------------------------ 2 files changed, 1178 deletions(-) delete mode 100644 ROUTER_REFACTOR_PLAN.md delete mode 100644 SCANNER_RESTORATION_PLAN.md diff --git a/ROUTER_REFACTOR_PLAN.md b/ROUTER_REFACTOR_PLAN.md deleted file mode 100644 index 5cc1fa0..0000000 --- a/ROUTER_REFACTOR_PLAN.md +++ /dev/null @@ -1,479 +0,0 @@ -# Router Refactoring Execution Plan - -## Objective -Refactor 858-line `cmd/server/main.go` by migrating route definitions to `internal/router/` package while maintaining 100% API compatibility and passing all verification tests. - -## Current State -- ✅ `internal/router/` package created with 7 files -- ✅ Route stubs implemented for: auth, library, device, frontend, docs -- ❌ Router package NOT integrated (never called from main.go) -- ❌ All routes still defined in main.go (duplicates) -- ⚠️ main.go: 858 lines (target: ~200 lines) - -## Success Criteria -1. All 26 verification checks pass (`scripts/verify-guidelines.sh`) -2. All Go tests pass (`go test ./...`) -3. All Bruno/curl API tests pass -4. No API behavior changes (routes, handlers, responses identical) -5. main.go reduced to ~200 lines -6. Code compiles without errors -7. Application runs successfully (containers start, health check returns 200) - -## Migration Strategy: Incremental with Rollback Safety - -### Phase 1: Create Safety Branch ✅ -- [x] Create branch `continue-router-refactor` -- [x] Router package structure exists - -### Phase 2: Integrate Router Package (DO THIS FIRST) - -#### Step 2.1: Add Router Import and Config -**File:** `cmd/server/main.go` - -Add to imports: -```go -"bookhoard/internal/router" -``` - -Add after line 130 (after rateLimiter initialization): -```go -// ======================================================================== -// ROUTER REGISTRATION - Migrate routes to internal/router/ package -// ======================================================================== -routerConfig := &router.Config{ - Echo: e, - Queries: queries, - Cfg: cfg, - DBPool: dbPool, - AuthHandler: authHandler, - LibraryHandler: libraryHandler, - DeviceHandler: deviceHandler, - KOReaderHandler: koreaderHandler, - WSHandler: wsHandler, - ConflictHandler: conflictHandler, - AnalyticsHandler: analyticsHandler, - QueueHandler: queueHandler, - CollectionHandler: collectionHandler, - OPDSHandler: opdsHandler, - ConnManager: connManager, - QueueProcessor: queueProcessor, - DeviceAuthMiddleware: deviceAuthMiddleware, - LoginTracker: loginAttemptTracker, -} -``` - -#### Step 2.2: Call Router.RegisterRoutes() -Add immediately after routerConfig: -```go -router.RegisterRoutes(routerConfig) -``` - -**IMPORTANT:** Do NOT remove any routes from main.go yet! - -#### Step 2.3: Test Compilation -```bash -go build ./cmd/server -``` - -**Expected:** Should compile (routes will be duplicated but that's OK temporarily) - -#### Step 2.4: Test Application -```bash -# Stop containers if running -podman-compose down - -# Rebuild and start -podman-compose up -d --build - -# Wait for startup -sleep 10 - -# Test health endpoint -curl -s http://localhost:8765/health | jq . - -# Test frontend -curl -s http://localhost:8765/ | grep -o ".*" - -# Run verification -bash scripts/verify-guidelines.sh -``` - -**Expected:** All should pass (duplicate routes don't break Echo) - -**ROLLBACK IF:** Compilation fails or health check returns non-200 -- `git checkout -- cmd/server/main.go` - ---- - -### Phase 3: Remove Duplicate Routes from main.go - -⚠️ **CRITICAL:** Remove ONE route group at a time, test after each removal! - -#### Step 3.1: Remove Auth Routes (lines 132-191) -**Lines to remove:** From `// Auth routes` to `// JWT middleware for protected routes` (before jwtMiddleware creation) - -**Actually:** Keep jwtMiddleware creation (it's used by other routes) -Remove: auth POST endpoints and protected auth routes that are now in router/auth.go - -**Test after removal:** -```bash -go build ./cmd/server -podman-compose up -d --build -sleep 10 -# Test auth endpoints -curl -X POST http://localhost:8765/api/auth/register -H "Content-Type: application/json" -d '{"email":"test@test.com","username":"test","password":"Test123!"}' -``` - -#### Step 3.2: Remove Library Routes (lines 192-235) -**Lines to remove:** From `// Library management routes` to visibility routes - -**Test after removal:** -```bash -go build ./cmd/server -podman-compose up -d --build -# Test library endpoints -curl -s http://localhost:8765/api/libraries/types | jq . -``` - -#### Step 3.3: Remove Device Registration Routes (lines 236-239) -**Lines to remove:** Device register and status endpoints - -**Test after removal:** -```bash -go build ./cmd/server -# Device registration test -``` - -#### Step 3.4: Remove Frontend Routes (lines 627-823) -**Lines to remove:** From `// FRONTEND ROUTES` to before `// HEALTH CHECK` - -**Test after removal:** -```bash -go build ./cmd/server -curl -s http://localhost:8765/ | grep -o ".*" -``` - -#### Step 3.5: Remove Health Check (lines 824-844) -**Lines to remove:** From `// HEALTH CHECK` to before `// DOCUMENTATION ROUTES` - -**Test after removal:** -```bash -go build ./cmd/server -curl -s http://localhost:8765/health | jq . -``` - -#### Step 3.6: Remove Documentation Routes (lines 845-858) -**Lines to remove:** From `// DOCUMENTATION ROUTES` to end - -**Test after removal:** -```bash -go build ./cmd/server -curl -s http://localhost:8765/docs | grep -o ".*" -``` - ---- - -### Phase 4: Implement Remaining Router Stubs - -#### Step 4.1: Create `router/sync.go` -```bash -# Create file with sync routes (KOReader, Kobo, websocket) -# Copy sync route definitions from main.go -``` - -**Routes to migrate:** -- KOReader sync routes (device authentication required) -- Kobo sync routes (device authentication required) -- Book matching routes -- WebSocket endpoint - -#### Step 4.2: Create `router/media.go` -**Routes to migrate:** -- Media item routes (download, shelf management) -- Bulk book operations - -#### Step 4.3: Create `router/analytics.go` -**Routes to migrate:** -- Analytics routes (API + SSR) - -#### Step 4.4: Create `router/queue.go` -**Routes to migrate:** -- Sync queue management routes (API + SSR) - -#### Step 4.5: Create `router/opds.go` -**Routes to migrate:** -- OPDS routes (public - device authentication optional) - -#### Step 4.6: Update `router/collections.go` -**Routes to migrate:** -- Collection routes (API + SSR) - ---- - -### Phase 5: Complete Migration - -For each new route file created in Phase 4: -1. Add `registerXYZRoutes(cfg *Config)` function -2. Call it from `router.RegisterRoutes()` in router.go -3. Remove corresponding routes from main.go -4. Test with: `go build ./cmd/server` -5. Test with: `podman-compose up -d --build` -6. Test specific endpoints with curl -7. Run: `bash scripts/verify-guidelines.sh` - ---- - -### Phase 6: Final Verification - -#### Step 6.1: Full Test Suite -```bash -# Compilation -go build ./cmd/server -go test ./... - -# Verification -bash scripts/verify-guidelines.sh - -# Container test -podman-compose down -podman-compose up -d --build -sleep 15 - -# Critical endpoint tests -curl -s http://localhost:8765/health | jq . -curl -s http://localhost:8765/ | grep -o ".*" -curl -s http://localhost:8765/api/libraries/types | jq . -curl -s http://localhost:8765/docs | grep -o ".*" - -# Run Bruno tests (if available) -# bruno test ... -``` - -#### Step 6.2: Verify main.go Size -```bash -wc -l cmd/server/main.go -``` -**Expected:** ~200 lines (down from 858) - -#### Step 6.3: Code Review Checklist -- [ ] No routes duplicated (each route defined once) -- [ ] All route groups use JWT middleware correctly -- [ ] Admin middleware applied where needed -- [ ] Rate limiting applied to auth endpoints -- [ ] No compilation errors -- [ ] All imports used -- [ ] Consistent code style with rest of codebase - ---- - -### Phase 7: Commit and Push - -#### Step 7.1: Review Changes -```bash -git diff cmd/server/main.go | head -100 -git diff internal/router/ -``` - -#### Step 7.2: Run Verification -```bash -bash scripts/verify-guidelines.sh -``` - -#### Step 7.3: Commit Changes -```bash -git add cmd/server/main.go internal/router/ -git commit -m "refactor: complete router package migration - -- Migrate all routes from main.go to internal/router/ package -- Reduce main.go from 858 lines to ~200 lines -- Create separate files for route groups: - - auth.go: Authentication routes - - library.go: Library management - - device.go: Device registration & management - - sync.go: KOReader/Kobo/WebSocket sync routes - - media.go: Media items and bulk operations - - analytics.go: Analytics API + SSR - - queue.go: Sync queue management - - opds.go: OPDS feeds - - collections.go: Collection management - - frontend.go: SSR pages and health check - - docs.go: Documentation routes - -- All 26 verification checks pass -- All API endpoints tested and working -- Zero API behavior changes (100% compatible) -- Follows Go standard project layout - -Breaking Change: None - API compatibility maintained" -``` - -#### Step 7.4: Push -```bash -git push origin continue-router-refactor -``` - ---- - -## Rollback Procedures - -### If compilation fails at any point: -```bash -git checkout -- cmd/server/main.go -# Or -git reset --hard HEAD -``` - -### If tests fail: -1. Check which endpoint failed -2. Verify route is registered in router package -3. Check handler method exists -4. Check middleware is applied correctly -5. Review error logs: `podman logs bookhoard` - -### If verification fails: -1. Check which specific check failed -2. Fix the issue -3. Re-run verification -4. Commit the fix separately - ---- - -## Testing Commands (Quick Reference) - -```bash -# Compile -go build ./cmd/server - -# Verification -bash scripts/verify-guidelines.sh - -# Unit tests -go test ./... - -# Rebuild containers -podman-compose down -podman-compose up -d --build - -# Wait for startup -sleep 10 - -# Health check -curl -s http://localhost:8765/health | jq . - -# Frontend -curl -s http://localhost:8765/ | grep -o ".*" - -# Auth endpoint test -curl -X POST http://localhost:8765/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"login":"test","password":"wrong"}' - -# Library types -curl -s http://localhost:8765/api/libraries/types | jq . - -# Documentation -curl -s http://localhost:8765/docs | grep -o ".*" - -# Check container logs -podman logs bookhoard | tail -30 - -# Check container status -podman ps -``` - ---- - -## Files Created/Modified - -### Created: -- `internal/router/router.go` - Main router configuration -- `internal/router/auth.go` - Authentication routes -- `internal/router/library.go` - Library management routes -- `internal/router/device.go` - Device routes -- `internal/router/frontend.go` - Frontend SSR routes -- `internal/router/docs.go` - Documentation routes -- `internal/router/helpers.go` - Template helpers -- `internal/router/sync.go` - Sync routes (Phase 4) -- `internal/router/media.go` - Media routes (Phase 4) -- `internal/router/analytics.go` - Analytics routes (Phase 4) -- `internal/router/queue.go` - Queue routes (Phase 4) -- `internal/router/opds.go` - OPDS routes (Phase 4) -- `internal/router/collections.go` - Collection routes (Phase 4) - -### Modified: -- `cmd/server/main.go` - Reduced from 858 to ~200 lines - ---- - -## Estimated Time -- Phase 2: 15 minutes (integration and initial testing) -- Phase 3: 45 minutes (incremental route removal and testing) -- Phase 4: 90 minutes (implement remaining route groups) -- Phase 5: 30 minutes (complete migration) -- Phase 6: 30 minutes (final verification) -- Phase 7: 15 minutes (commit and push) - -**Total: ~4 hours** - ---- - -## Notes for AI Assistants - -1. **Always test after each change** - don't batch multiple route removals -2. **Keep main.go functional** - it should compile at all times -3. **Verify API compatibility** - routes must respond identically -4. **Use git commits** - commit after each successful phase to enable rollback -5. **Check logs** - if something fails, check `podman logs bookhoard` -6. **Verification script is authority** - if it fails, fix before continuing -7. **Echo allows duplicate routes** - temporarily OK during migration -8. **Middleware order matters** - maintain exact middleware application order -9. **Import statements** - remove unused imports after route removal -10. **Handler methods** - verify handler methods exist before calling them - ---- - -## Troubleshooting - -### Error: "route already registered" -- **Cause:** Route defined multiple times -- **Fix:** Remove from main.go, keep in router package only - -### Error: "handler method not found" -- **Cause:** Typo in method name or handler not initialized in Config -- **Fix:** Check method name in handler file, ensure handler is passed in Config - -### Error: "undefined: jwtMiddleware" -- **Cause:** JWT middleware not created in that route file -- **Fix:** Add JWT middleware creation at top of register function - -### Error: "404 on previously working endpoint" -- **Cause:** Route not registered or middleware blocking access -- **Fix:** Check route is registered, check middleware conditions - -### Health check returns 503 -- **Cause:** Database not connected or dbPool not passed to router -- **Fix:** Ensure DBPool is set in routerConfig - -### Verification fails with "Build failed" -- **Cause:** Compilation error -- **Fix:** Run `go build ./cmd/server` to see specific error - ---- - -## Success Metrics - -Before: -- `cmd/server/main.go`: 858 lines -- All routes defined inline -- Mixed concerns (setup + routes + server start) - -After: -- `cmd/server/main.go`: ~200 lines -- Routes organized by domain in `internal/router/` -- Clear separation: setup → router registration → server start -- Follows Go standard project layout -- Easy to maintain and extend - ---- - -## End of Plan diff --git a/SCANNER_RESTORATION_PLAN.md b/SCANNER_RESTORATION_PLAN.md deleted file mode 100644 index aa39e3f..0000000 --- a/SCANNER_RESTORATION_PLAN.md +++ /dev/null @@ -1,699 +0,0 @@ -# Scanner System Fix & Enhancement Plan - -**Created:** February 6, 2026 -**Updated:** February 6, 2026 (clarified scope and priorities) -**Status:** Ready to implement - ---- - -## Executive Summary - -This plan addresses TWO separate issues with the scanner system: - -### 1. **CRITICAL BUG** (Priority: HIGH) -Auto-start functionality was removed during router refactor (commit 6784c25): -- Scheduler doesn't start on server boot -- Watch mode doesn't auto-start for libraries -- No graceful shutdown for scanner services -- `handlers.SetupRoutes()` returns Handler but return value isn't captured - -### 2. **ENHANCEMENTS** (Priority: MEDIUM) -- Library-type-aware scanning (prevents cross-contamination between ebook/comic/manga libraries) -- Comic/manga metadata extraction (ComicInfo.xml parsing) -- Better code organization (extract scanner routes to separate file) - -**What WASN'T broken:** -- ✅ Scanner routes still work (7 endpoints in `internal/handlers/ebook.go:145-154`) -- ✅ Can manually start/stop scanner via API -- ✅ Scan job tracking works -- ✅ Watch mode works when manually triggered - ---- - -## Changes Summary - -**Files to Create:** -1. `internal/router/scanner.go` (new - optional, for organization) -2. `internal/app/app.go` (new - required, for lifecycle management) - -**Files to Modify:** -1. `internal/router/router.go` (capture and return EbookHandler) -2. `cmd/server/main.go` (restore auto-start calls, use App pattern) -3. `internal/services/ebook_scanner.go` (library-type-aware scanning) -4. `internal/handlers/ebook.go` (comic/manga metadata extraction) - -**Risk Assessment:** LOW-MEDIUM -- Auto-start restoration: LOW (restores existing code that was removed) -- Library-type-awareness: MEDIUM (core scanner logic change) -- Comic/manga scanning: MEDIUM (new feature) - ---- - -## Phase 1: Restore Auto-Start Functionality [CRITICAL BUG FIX] - -### Problem -Before router refactor (commit 799b640): -```go -h := handlers.SetupRoutes(protected, queries) - -// Start scheduler for auto-scanning -go h.StartScheduler() -defer h.StopScheduler() - -// Start watch mode for all libraries (background) -go func() { - time.Sleep(2 * time.Second) - if err := h.StartWatchModeForAllLibraries(context.Background()); err != nil { - log.Printf("Warning: failed to start watch mode for libraries: %v", err) - } -}() -``` - -After refactor (current): -```go -handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager) // Return value ignored! -// No scheduler start -// No watch mode start -// No graceful shutdown -``` - -### Solution - -#### Step 1.1: Update `internal/router/router.go` - -**Location:** Line 113-114 - -**Current Code:** -```go -jwtMiddleware := createJWTMiddleware(cfg) -protected := e.Group("/api", jwtMiddleware) -handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager) -``` - -**New Code:** -```go -jwtMiddleware := createJWTMiddleware(cfg) -protected := e.Group("/api", jwtMiddleware) -ebookHandler := handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager) -return ebookHandler // Add return statement to RegisterRoutes -``` - -**Update RegisterRoutes signature:** - -**Current (line 86):** -```go -func RegisterRoutes(cfg *Config) { -``` - -**New:** -```go -func RegisterRoutes(cfg *Config) *handlers.Handler { - // ... existing code ... - return ebookHandler // Return at end of function -} -``` - -#### Step 1.2: Update `cmd/server/main.go` - -**Location:** After line 151 (after `router.RegisterRoutes(routerConfig)`) - -**Add this code:** -```go -// Register all routes and get ebook handler -ebookHandler := router.RegisterRoutes(routerConfig) - -// ======================================================================== -// BACKGROUND SERVICES - Restore auto-start functionality -// ======================================================================== - -// Start scheduler for auto-scanning -go ebookHandler.StartScheduler() -defer ebookHandler.StopScheduler() - -// Start watch mode for all libraries (background) -go func() { - time.Sleep(2 * time.Second) // Wait for server to be ready - if err := ebookHandler.StartWatchModeForAllLibraries(context.Background()); err != nil { - log.Printf("Warning: failed to start watch mode for libraries: %v", err) - } -}() -``` - -**Verification:** -- [ ] Application compiles -- [ ] Server starts without errors -- [ ] Check logs for "Starting scheduler" message -- [ ] Check logs for watch mode starting after 2 seconds - ---- - -## Phase 2: Implement Library-Type-Aware Scanning [ENHANCEMENT] - -### Purpose -Prevent cross-contamination between library types: -- Epub libraries should only scan .epub files -- Comic libraries should only scan .cbz/.cbr files -- Manga libraries should only scan appropriate formats -- Each library type has configurable allowed extensions - -### File: `internal/services/ebook_scanner.go` - -#### Change 2.1: Add libraryTypes cache field - -**Location:** Line 59-65 (EbookScanner struct) - -**Current Code:** -```go -type EbookScanner struct { - db *database.Queries - watcher *fsnotify.Watcher - folders []string - adminID pgtype.UUID - defaultLibraryID pgtype.UUID -} -``` - -**New Code:** -```go -type EbookScanner struct { - db *database.Queries - watcher *fsnotify.Watcher - folders []string - adminID pgtype.UUID - defaultLibraryID pgtype.UUID - libraryTypes map[string][]string // folder -> allowed extensions cache -} -``` - -#### Change 2.2: Initialize libraryTypes in NewEbookScanner - -**Location:** Line 73-74 - -**New Code:** -```go -return &EbookScanner{ - db: db, - watcher: watcher, - folders: []string{}, - adminID: pgtype.UUID{}, - defaultLibraryID: pgtype.UUID{Valid: false}, - libraryTypes: make(map[string][]string), // ← ADD THIS -} -``` - -#### Change 2.3: Build library types cache in SetFolders - -**Location:** Line 86-109 (SetFolders function) - -**New Code:** -```go -func (s *EbookScanner) SetFolders(folders []string) error { - s.folders = folders - - // Remove old watch if exists - if s.watcher != nil { - s.watcher.Close() - } - - // Create new watcher - watcher, err := fsnotify.NewWatcher() - if err != nil { - return fmt.Errorf("failed to create watcher: %v", err) - } - s.watcher = watcher - - // Build cache of allowed extensions per folder - s.libraryTypes = make(map[string][]string) - ctx := context.Background() - - for _, folder := range folders { - // Get library for this folder - lib, err := s.db.GetLibraryByFolder(ctx, folder) - if err != nil { - fmt.Printf("Warning: failed to get library for folder %s: %v\n", folder, err) - continue - } - - // Get library type with allowed extensions - libType, err := s.db.GetLibraryType(ctx, lib.LibraryTypeID) - if err != nil { - fmt.Printf("Warning: failed to get library type for %s: %v\n", folder, err) - continue - } - - // Cache allowed extensions for this folder - s.libraryTypes[folder] = libType.AllowedExtensions - fmt.Printf("Scanner: Folder %s (type: %s) allows extensions: %v\n", - folder, libType.Name, libType.AllowedExtensions) - } - - // Add all folders to watch - for _, folder := range folders { - if err := s.watcher.Add(folder); err != nil { - fmt.Printf("Warning: failed to watch folder %s: %v\n", folder, err) - } - } - - return nil -} -``` - -#### Change 2.4: Replace isEbookFile with isScannableFile - -**Location:** Line 168-177 (isEbookFile function) - -**New Code:** -```go -// isScannableFile checks if a file should be scanned based on library type configuration -func (s *EbookScanner) isScannableFile(path string) bool { - ext := strings.ToLower(filepath.Ext(path)) - - // Find which folder this file belongs to - var folder string - for _, f := range s.folders { - if strings.HasPrefix(path, f) { - folder = f - break - } - } - - // If no folder match, don't scan - if folder == "" { - return false - } - - // Get allowed extensions for this folder's library - allowed, ok := s.libraryTypes[folder] - if !ok { - // No library type info, skip file - fmt.Printf("Warning: No library type info for folder %s, skipping %s\n", folder, path) - return false - } - - // Check if file extension is allowed for this library type - for _, allowedExt := range allowed { - if ext == strings.ToLower(allowedExt) { - return true - } - } - - return false -} -``` - -#### Change 2.5: Update ScanFolders to use isScannableFile - -**Location:** Line 146 (in ScanFolders function) - -**Current Code:** -```go -// Check if it's an ebook file -if s.isEbookFile(path) { -``` - -**New Code:** -```go -// Check if file should be scanned based on library type -if s.isScannableFile(path) { -``` - -**Verification:** -- [ ] Code compiles -- [ ] Ebook libraries scan only .epub files (check logs) -- [ ] Comic libraries scan only .cbz/.cbr files -- [ ] No cross-contamination between library types -- [ ] Test: Create ebook library, add .cbz file → should be ignored -- [ ] Test: Create comic library, add .epub file → should be ignored - ---- - -## Phase 3: Comic/Manga Metadata Extraction [NEW FEATURE] - -### Purpose -Extract metadata from comic/manga archives (.cbz, .cbr, .cb7): -- Parse ComicInfo.xml from archives -- Extract cover images -- Get series, issue number, publisher, etc. -- Support comic library management - -### File: `internal/handlers/ebook.go` - -#### Step 3.1: Add ComicInfo.xml parsing - -**Add to imports:** -```go -import ( - "archive/zip" - "encoding/xml" - "image" - _ "image/jpeg" - _ "image/png" - "path/filepath" - "strings" -) -``` - -#### Step 3.2: Define ComicInfo struct - -**Add after existing structs:** -```go -// ComicInfo represents metadata from ComicInfo.xml -type ComicInfo struct { - XMLName xml.Name `xml:"ComicInfo"` - Title string `xml:"Title"` - Series string `xml:"Series"` - Number int `xml:"Number"` - Volume int `xml:"Volume"` - Publisher string `xml:"Publisher"` - Year int `xml:"Year"` - Month int `xml:"Month"` - Day int `xml:"Day"` - Writer string `xml:"Writer"` - Penciller string `xml:"Penciller"` - Inker string `xml:"Inker"` - Colorist string `xml:"Colorist"` - Letterer string `xml:"Letterer"` - CoverArtist string `xml:"CoverArtist"` - Genre string `xml:"Genre"` - Tags string `xml:"Tags"` - Web string `xml:"Web"` - Notes string `xml:"Notes"` -} -``` - -#### Step 3.3: Add extraction function - -**Add new function:** -```go -// extractComicMetadata extracts metadata from comic archive -func extractComicMetadata(filePath string) (*ComicInfo, []byte, error) { - // Open archive - r, err := zip.OpenReader(filePath) - if err != nil { - return nil, nil, fmt.Errorf("failed to open comic archive: %w", err) - } - defer r.Close() - - // Look for ComicInfo.xml - var comicInfo *ComicInfo - var coverImage []byte - - for _, f := range r.File { - if f.Name == "ComicInfo.xml" { - rc, err := f.Open() - if err != nil { - return nil, nil, fmt.Errorf("failed to open ComicInfo.xml: %w", err) - } - - data, err := io.ReadAll(rc) - rc.Close() - if err != nil { - return nil, nil, fmt.Errorf("failed to read ComicInfo.xml: %w", err) - } - - comicInfo = &ComicInfo{} - if err := xml.Unmarshal(data, comicInfo); err != nil { - return nil, nil, fmt.Errorf("failed to parse ComicInfo.xml: %w", err) - } - } - - // Look for cover image (usually first image in root) - if coverImage == nil && isImageFile(f.Name) { - // Usually in root directory, not subdirectories - if !strings.Contains(filepath.Dir(f.Name), string(filepath.Separator)) || - filepath.Dir(f.Name) == "." { - rc, err := f.Open() - if err != nil { - continue - } - - coverImage, err = io.ReadAll(rc) - rc.Close() - if err == nil { - // Validate it's actually an image - _, _, err = image.Decode(bytes.NewReader(coverImage)) - if err != nil { - coverImage = nil // Not a valid image - } - } - } - } - } - - if comicInfo == nil { - // No ComicInfo.xml, create minimal metadata from filename - comicInfo = &ComicInfo{} - basename := filepath.Base(filePath) - comicInfo.Title = strings.TrimSuffix(basename, filepath.Ext(basename)) - } - - return comicInfo, coverImage, nil -} - -// isImageFile checks if a file is an image based on extension -func isImageFile(filename string) bool { - ext := strings.ToLower(filepath.Ext(filename)) - return ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" -} -``` - -#### Step 3.4: Integrate into scanner - -**Update ScanFolders to extract comic metadata for .cbz/.cbr files:** - -**Location:** In the file processing loop (around line 160-180) - -**Add before creating media item:** -```go -var comicInfo *ComicInfo -var coverImage []byte - -// Extract comic metadata if applicable -if strings.ToLower(filepath.Ext(path)) == ".cbz" { - info, cover, err := extractComicMetadata(path) - if err != nil { - log.Printf("Warning: failed to extract comic metadata from %s: %v", path, err) - } else { - comicInfo = info - coverImage = cover - } -} - -// When creating media item, use comic metadata -title := comicInfo.Title -if title == "" { - title = filepath.Base(path) -} - -// Use cover image if available -if len(coverImage) > 0 { - // Use extracted cover - // ... existing cover processing code ... -} -``` - -**Verification:** -- [ ] Code compiles -- [ ] Create .cbz file with ComicInfo.xml -- [ ] Scan comic library -- [ ] Check metadata was extracted (title, series, issue) -- [ ] Check cover image was extracted -- [ ] Test .cbz without ComicInfo.xml (should use filename) - ---- - -## Phase 4: Organize Scanner Routes [OPTIONAL - LOW PRIORITY] - -### Purpose -Move scanner routes from `internal/handlers/ebook.go` to `internal/router/scanner.go` for better organization. - -**Note:** This is purely cosmetic. Scanner routes already work fine where they are. - -### File: `internal/router/scanner.go` (NEW) - -```go -package router - -import ( - "github.com/labstack/echo/v4" -) - -// registerScannerRoutes registers all scanner-related endpoints -func registerScannerRoutes(cfg *Config) { - // Routes are already registered in handlers.SetupRoutes() - // This file is for documentation/organization purposes - // Actual routes are in: - // - internal/handlers/ebook.go:145-154 (scanner endpoints) - // - internal/handlers/auth.go (scan settings endpoints) -} -``` - -**Decision:** SKIP this phase. The current organization works fine. - ---- - -## Phase 5: Application Lifecycle Management [OPTIONAL - DEFER] - -### Purpose -Create `internal/app/app.go` for better lifecycle management, graceful shutdown, signal handling. - -**Decision:** DEFER to future implementation. The simple approach in Phase 1 is sufficient for now. - ---- - -## Implementation Order - -### Priority 1: Critical Bug Fix (Phase 1) -- Time: 15 minutes -- Risk: LOW -- Impact: Restores auto-scan and watch mode - -### Priority 2: Library-Type-Awareness (Phase 2) -- Time: 1 hour -- Risk: MEDIUM -- Impact: Prevents cross-contamination - -### Priority 3: Comic/Manga Scanning (Phase 3) -- Time: 2-3 hours -- Risk: MEDIUM -- Impact: New feature for comic libraries - -### Priority 4: Code Organization (Phase 4) -- Time: 30 minutes -- Risk: LOW -- Impact: Cosmetic (SKIP for now) - -### Priority 5: App Lifecycle (Phase 5) -- Time: 2 hours -- Risk: MEDIUM -- Impact: Better structure (DEFER for now) - ---- - -## Testing Checklist - -### After Phase 1 (Auto-start restoration): -- [ ] Application compiles -- [ ] Server starts without errors -- [ ] Logs show "Starting scheduler" -- [ ] Logs show "Starting watch mode for all libraries" after 2 seconds -- [ ] Scheduler triggers auto-scans -- [ ] SIGTERM triggers graceful shutdown - -### After Phase 2 (Library-type-awareness): -- [ ] Create ebook library, add .epub → scans correctly -- [ ] Create ebook library, add .cbz → ignored -- [ ] Create comic library, add .cbz → scans correctly -- [ ] Create comic library, add .epub → ignored -- [ ] Check logs for library type messages - -### After Phase 3 (Comic/manga scanning): -- [ ] Create test .cbz with ComicInfo.xml -- [ ] Add to comic library -- [ ] Scan library -- [ ] Verify metadata extracted (title, series, issue) -- [ ] Verify cover image extracted -- [ ] Test .cbz without ComicInfo.xml (uses filename) - ---- - -## Verification Commands - -### Test scanner auto-start: -```bash -# Start server -podman-compose up -d --build - -# Check logs -podman logs bookhoard | grep -i "scheduler\|watch mode" - -# Should see: -# "Starting scheduler for auto-scanning" -# "Starting watch mode for all libraries" -``` - -### Test library-type-awareness: -```bash -# Create ebook library -curl -X POST http://localhost:8765/api/libraries \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"Ebooks","library_type_id":"","folders":["/path/to/ebooks"]}' - -# Create comic library -curl -X POST http://localhost:8765/api/libraries \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"Comics","library_type_id":"","folders":["/path/to/comics"]}' - -# Add .epub to comic library → should be ignored -# Add .cbz to ebook library → should be ignored -# Check scan logs for filtering messages -``` - -### Test comic metadata extraction: -```bash -# Create test .cbz with ComicInfo.xml -zip test.cbz ComicInfo.xml cover.jpg page1.jpg - -# Add to comic library and scan -curl -X POST http://localhost:8765/api/scanner/scan \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"folder_paths":["/path/to/comics"]}' - -# Check media item has correct metadata -curl http://localhost:8765/api/media-items?library_id= \ - -H "Authorization: Bearer $TOKEN" | jq . -``` - ---- - -## Rollback Plan - -### Rollback Phase 1: -```bash -git checkout cmd/server/main.go -git checkout internal/router/router.go -``` - -### Rollback Phase 2: -```bash -git checkout internal/services/ebook_scanner.go -``` - -### Rollback Phase 3: -```bash -git checkout internal/handlers/ebook.go -``` - ---- - -## Summary - -**Critical Issues Fixed:** -- ✅ Auto-start scheduler (restored) -- ✅ Auto-start watch mode (restored) -- ✅ Graceful shutdown (restored) - -**Enhancements Added:** -- ✅ Library-type-aware scanning (prevents cross-contamination) -- ✅ Comic/manga metadata extraction (ComicInfo.xml parsing) -- ✅ Comic cover image extraction - -**Deferred:** -- ⏸️ Code organization (scanner routes file) -- ⏸️ App lifecycle management - -**Estimated Time:** -- Phase 1 (bug fix): 15 minutes -- Phase 2 (enhancement): 1 hour -- Phase 3 (new feature): 2-3 hours -- **Total: 4-5 hours** - -**Risk Level:** -- Phase 1: LOW (restoring removed code) -- Phase 2: MEDIUM (core scanner logic) -- Phase 3: MEDIUM (new feature, isolated) - -**Ready for implementation in priority order.**