docs(ebook): add comprehensive ebook.go refactor plan

- Add EBOOK_REFACTOR_PLAN.md with 856 lines of detailed instructions
- Split 1,350-line ebook.go into focused single-responsibility files
- Zero API changes, only code organization for maintainability
- Phase-by-phase safety checkpoints and rollback procedures

Target file organization after refactor:
- media.go (~600 lines): Media CRUD + metadata
- search.go (~80 lines): Query and search operations
- matching.go (~200 lines): Book matching and sync operations
- ebook.go (~150 lines): SetupRoutes only

Plan ensures AI can implement without breaking any functionality.
This commit is contained in:
2026-02-07 00:16:20 -05:00
parent 73678a1df0
commit 2258bb2f5d
2 changed files with 1336 additions and 896 deletions
+857
View File
@@ -0,0 +1,857 @@
# Comprehensive ebook.go Refactor Plan
**Created:** February 6, 2026
**Purpose:** Refactor 1,350-line ebook.go into focused, single-responsibility files
**Assumption:** Scanner routes already moved to router/scanner.go
**Goal:** Zero API changes, 100% functional compatibility
---
## Executive Summary
`internal/handlers/ebook.go` violates Go single responsibility principle at 1,350 lines:
- **Scanner operations**: 296 lines (12 methods)
- **Media CRUD & metadata**: 822 lines (24 methods)
- **Book matching/sync**: ~150 lines (8 methods)
- **Collection device mappings**: 60+ lines (6 methods)
**Proposed split:**
1. `media.go` - Media items, ratings, progress, notes, highlights (CRUD + metadata)
2. `search.go` - Query, search, filtered operations
3. `matching.go` - Book linking, file aliases, matching algorithms
4. `ebook.go` - Slimmed to only core Handler struct and SetupRoutes
**Risk Level:** VERY LOW
- No API endpoint changes
- No database schema changes
- Only code organization
- All existing tests continue to work
---
## File Analysis Before Refactor
### Current ebook.go Structure (1,350 lines)
```go
// Lines 38-164: Handler setup and SetupRoutes()
func NewHandler(...) *Handler { }
func SetupRoutes(...) *Handler { }
// Lines 166-461: Scanner operations (12 methods)
func (h *Handler) ScanEbooks() { }
func (h *Handler) StartScanner() { }
// ... 10 more scanner methods
// Lines 474-1296: Media operations (24 methods)
func (h *Handler) ListMediaItems() { }
func (h *Handler) GetMediaItem() { }
func (h *Handler) CreateMediaRating() { }
// ... 21 more media methods
// Lines 1297-end: Search & matching (8 methods)
func (h *Handler) SearchMediaItems() { }
func (h *Handler) QueryBooks() { }
// ... 6 more methods
```
### Handler Dependencies
All methods share this Handler struct:
```go
type Handler struct {
db *database.Queries
worker *worker.Worker
scanner *services.EbookScanner
scheduler *cron.Cron
ctx context.Context
cancel context.CancelFunc
mu sync.RWMutex
watchingLibraries map[string]bool
}
```
---
## Phase 1: SetupRoutes Restructure (FOUNDATION)
### Purpose
Remove all route registration from SetupRoutes, make it a pure factory function
### File: `internal/handlers/ebook.go`
#### Step 1.1: Simplify SetupRoutes (Lines 70-157)
**Current Code Problem:**
```go
func SetupRoutes(g *echo.Group, db *database.Queries, connManager *wsync.ConnectionManager) *Handler {
h := NewHandler(db, connManager)
// Collections API routes (21 lines)
collectionHandler := NewCollectionHandler(db, connManager)
collections := g.Group("/collections")
collections.GET("", collectionHandler.GetCollections)
// ... 7 more collection routes
// Device shelf mapping routes (6 lines)
deviceCollections := g.Group("/devices/:id/collections")
// ... 6 device routes
// Book matching routes (4 lines)
g.POST("/sync/books/query", h.QueryBooks)
// ... 3 more matching routes
// Media item routes (4 lines)
g.GET("/media-items", h.ListMediaItems)
// ... 3 more media routes
// Admin-only routes (11 lines)
admin := g.Group("", AdminMiddleware)
admin.POST("/media-items", h.CreateMediaItem)
// ... 10 more admin routes (including scanner)
return h
}
```
**New Code:**
```go
func SetupRoutes(g *echo.Group, db *database.Queries, connManager *wsync.ConnectionManager) *Handler {
return NewHandler(db, connManager)
}
```
**Verification:**
- ✅ SetupRoutes now only creates handler instance
- ✅ All routes will be registered via router package
- ✅ Handler struct and dependencies unchanged
---
## Phase 2: Create Media Handler (CORE CRUD)
### File: `internal/handlers/media.go` (NEW)
#### Step 2.1: Create MediaHandler struct
```go
package handlers
import (
"bookhoard/internal/database"
"bookhoard/internal/sync"
"context"
"fmt"
"net/http"
"strconv"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v4"
)
// MediaHandler handles media item CRUD operations and metadata
type MediaHandler struct {
db *database.Queries
worker *worker.Worker
}
// NewMediaHandler creates a new media handler
func NewMediaHandler(db *database.Queries, worker *worker.Worker) *MediaHandler {
return &MediaHandler{
db: db,
worker: worker,
}
}
```
#### Step 2.2: Move media CRUD methods (From ebook.go lines 474-959)
**Methods to move:**
- `ListMediaItems()``media.go:ListMediaItems()`
- `GetMediaItem()``media.go:GetMediaItem()`
- `ListMediaItemsFiltered()``media.go:ListMediaItemsFiltered()`
- `CreateMediaItem()``media.go:CreateMediaItem()`
- `UpdateMediaItem()``media.go:UpdateMediaItem()`
- `DeleteMediaItem()``media.go:DeleteMediaItem()`
- `SearchMediaItems()``media.go:SearchMediaItems()`
**Method signature changes:**
```go
// OLD in ebook.go
func (h *Handler) ListMediaItems(c echo.Context) error
// NEW in media.go
func (mh *MediaHandler) ListMediaItems(c echo.Context) error
```
**Implementation:**
Copy exact method bodies, change receiver from `h *Handler` to `mh *MediaHandler`
#### Step 2.3: Move metadata methods (From ebook.go lines 630-839)
**Methods to move:**
- `CreateMediaRating()``media.go:CreateMediaRating()`
- `GetMediaRating()``media.go:GetMediaRating()`
- `UpdateMediaRating()``media.go:UpdateMediaRating()`
- `DeleteMediaRating()``media.go:DeleteMediaRating()`
- `GetMediaReadingProgress()``media.go:GetMediaReadingProgress()`
- `UpdateMediaReadingProgress()``media.go:UpdateMediaReadingProgress()`
- `DeleteMediaReadingProgress()``media.go:DeleteMediaReadingProgress()`
- `GetMediaNotes()``media.go:GetMediaNotes()`
- `CreateMediaNote()``media.go:CreateMediaNote()`
- `GetMediaNote()``media.go:GetMediaNote()`
- `UpdateMediaNote()``media.go:UpdateMediaNote()`
- `DeleteMediaNote()``media.go:DeleteMediaNote()`
- `GetMediaHighlights()``media.go:GetMediaHighlights()`
- `CreateMediaHighlight()``media.go:CreateMediaHighlight()`
- `GetMediaHighlight()``media.go:GetMediaHighlight()`
- `UpdateMediaHighlight()``media.go:UpdateMediaHighlight()`
- `DeleteMediaHighlight()``media.go:DeleteMediaHighlight()`
**Verification after move:**
- [ ] All methods compile with new receiver type
- [ ] No changes to method bodies
- [ ] All database calls work (same `mh.db` reference)
- [ ] All imports are correct
---
## Phase 3: Create Search Handler (QUERY OPERATIONS)
### File: `internal/handlers/search.go` (NEW)
#### Step 3.1: Create SearchHandler struct
```go
package handlers
import (
"bookhoard/internal/database"
"context"
"fmt"
"net/http"
"strconv"
"github.com/labstack/echo/v4"
)
// SearchHandler handles search, query, and filtered operations
type SearchHandler struct {
db *database.Queries
}
// NewSearchHandler creates a new search handler
func NewSearchHandler(db *database.Queries) *SearchHandler {
return &SearchHandler{
db: db,
}
}
```
#### Step 3.2: Move search methods (From ebook.go lines 1297-end)
**Methods to move:**
- `SearchMediaItems()``search.go:SearchMediaItems()`
- `QueryBooks()``search.go:QueryBooks()`
**Note:** These methods are small and focused, perfect for separate handler
---
## Phase 4: Create Matching Handler (SYNC OPERATIONS)
### File: `internal/handlers/matching.go` (NEW)
#### Step 4.1: Create MatchingHandler struct
```go
package handlers
import (
"bookhoard/internal/database"
"bookhoard/internal/sync"
"context"
"fmt"
"net/http"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v4"
)
// MatchingHandler handles book matching, linking, and file alias operations
type MatchingHandler struct {
db *database.Queries
connManager *wsync.ConnectionManager
}
// NewMatchingHandler creates a new matching handler
func NewMatchingHandler(db *database.Queries, connManager *wsync.ConnectionManager) *MatchingHandler {
return &MatchingHandler{
db: db,
connManager: connManager,
}
}
```
#### Step 4.2: Move matching methods (From ebook.go lines 96-104)
**Methods to move:**
- `QueryBooks()``matching.go:QueryBooks()`
- `LinkBook()``matching.go:LinkBook()`
- `GetUnlinkedBooks()``matching.go:GetUnlinkedBooks()`
- `GetDeviceFileAliases()``matching.go:GetDeviceFileAliases()`
- `CreateDeviceFileAlias()``matching.go:CreateDeviceFileAlias()`
- `UpdateDeviceFileAlias()``matching.go:UpdateDeviceFileAlias()`
- `DeleteDeviceFileAlias()``matching.go:DeleteDeviceFileAlias()`
- `GetBookMatches()``matching.go:GetBookMatches()`
---
## Phase 5: Update Router Package (ROUTE REGISTRATION)
### Purpose
Register all routes with new handler types, maintain exact same endpoints
#### Step 5.1: Update router.go Config struct
**File:** `internal/router/router.go`
**Current Config (lines 33-52):**
```go
type Config struct {
Echo *echo.Echo
Queries *database.Queries
Cfg *config.Config
DBPool interface{}
AuthHandler *handlers.AuthHandler
LibraryHandler *handlers.LibraryHandler
DeviceHandler *handlers.DeviceHandler
EbookHandler *handlers.Handler // ← WILL BE REMOVED
KOReaderHandler *handlers.KOReaderHandler
WSHandler *handlers.WSHandler
ConflictHandler *handlers.ConflictHandler
AnalyticsHandler *handlers.AnalyticsHandler
QueueHandler *handlers.QueueHandler
CollectionHandler *handlers.CollectionHandler
OPDSHandler *handlers.OPDSHandler
ConnManager *sync.ConnectionManager
QueueProcessor *sync.SyncQueueProcessor
DeviceAuthMiddleware *middleware.DeviceAuthMiddleware
LoginTracker *ratelimit.LoginAttemptTracker
}
```
**New Config (replace EbookHandler with 3 new handlers):**
```go
type Config struct {
Echo *echo.Echo
Queries *database.Queries
Cfg *config.Config
DBPool interface{}
AuthHandler *handlers.AuthHandler
LibraryHandler *handlers.LibraryHandler
DeviceHandler *handlers.DeviceHandler
// EbookHandler REMOVED - replaced below:
MediaHandler *handlers.MediaHandler // ← NEW
SearchHandler *handlers.SearchHandler // ← NEW
MatchingHandler *handlers.MatchingHandler // ← NEW
KOReaderHandler *handlers.KOReaderHandler
WSHandler *handlers.WSHandler
ConflictHandler *handlers.ConflictHandler
AnalyticsHandler *handlers.AnalyticsHandler
QueueHandler *handlers.QueueHandler
CollectionHandler *handlers.CollectionHandler
OPDSHandler *handlers.OPDSHandler
ConnManager *sync.ConnectionManager
QueueProcessor *sync.SyncQueueProcessor
DeviceAuthMiddleware *middleware.DeviceAuthMiddleware
LoginTracker *ratelimit.LoginAttemptTracker
}
```
#### Step 5.2: Update RegisterRoutes function
**File:** `internal/router/router.go`
**Current route registration (lines 114-129):**
```go
jwtMiddleware := createJWTMiddleware(cfg)
protected := e.Group("/api", jwtMiddleware)
handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager) // OLD METHOD
// Register route groups
registerAuthRoutes(cfg, rateLimitMiddleware)
registerLibraryRoutes(cfg)
registerDeviceRoutes(cfg)
registerSyncRoutes(cfg)
registerMediaRoutes(cfg)
registerConflictRoutes(cfg)
registerAnalyticsRoutes(cfg)
registerQueueRoutes(cfg)
registerOPDSRoutes(cfg)
registerWebSocketRoutes(cfg)
registerFrontendRoutes(cfg)
registerDocumentationRoutes(cfg)
```
**New route registration:**
```go
jwtMiddleware := createJWTMiddleware(cfg)
protected := e.Group("/api", jwtMiddleware)
// Create handler instances using factory functions
mediaHandler := handlers.NewMediaHandler(cfg.Queries, cfg.Worker) // Worker needed
searchHandler := handlers.NewSearchHandler(cfg.Queries)
matchingHandler := handlers.NewMatchingHandler(cfg.Queries, cfg.ConnManager)
// Register route groups
registerAuthRoutes(cfg, rateLimitMiddleware)
registerLibraryRoutes(cfg)
registerDeviceRoutes(cfg)
registerSyncRoutes(cfg)
registerMediaRoutes(cfg) // ← WILL USE NEW MediaHandler
registerSearchRoutes(cfg) // ← NEW FILE
registerMatchingRoutes(cfg) // ← NEW FILE
registerConflictRoutes(cfg)
registerAnalyticsRoutes(cfg)
registerQueueRoutes(cfg)
registerOPDSRoutes(cfg)
registerWebSocketRoutes(cfg)
registerFrontendRoutes(cfg)
registerDocumentationRoutes(cfg)
```
#### Step 5.3: Create registerSearchRoutes function
**File:** `internal/router/search.go` (NEW)
```go
package router
import (
"net/http"
"github.com/labstack/echo/v4"
)
func registerSearchRoutes(cfg *Config) {
e := cfg.Echo
// JWT middleware for protected routes
jwtMiddleware := createJWTMiddleware(cfg)
protected := e.Group("/api", jwtMiddleware)
// Search and query endpoints (all authenticated users)
protected.GET("/media-items/search", cfg.SearchHandler.SearchMediaItems)
protected.POST("/sync/books/query", cfg.MatchingHandler.QueryBooks)
}
```
#### Step 5.4: Create registerMatchingRoutes function
**File:** `internal/router/matching.go` (NEW)
```go
package router
import (
"github.com/labstack/echo/v4"
)
func registerMatchingRoutes(cfg *Config) {
e := cfg.Echo
// JWT middleware for protected routes
jwtMiddleware := createJWTMiddleware(cfg)
protected := e.Group("/api", jwtMiddleware)
// Book matching and linking routes (all authenticated users)
protected.POST("/devices/:deviceId/sync/link-book", cfg.MatchingHandler.LinkBook)
protected.GET("/devices/:deviceId/sync/unlinked-books", cfg.MatchingHandler.GetUnlinkedBooks)
// File alias routes (all authenticated users)
protected.GET("/devices/:id/file-aliases", cfg.MatchingHandler.GetDeviceFileAliases)
protected.POST("/devices/:id/file-aliases", cfg.MatchingHandler.CreateDeviceFileAlias)
protected.PUT("/devices/:id/file-aliases/:aliasId", cfg.MatchingHandler.UpdateDeviceFileAlias)
protected.DELETE("/devices/:id/file-aliases/:aliasId", cfg.MatchingHandler.DeleteDeviceFileAlias)
protected.GET("/books/match", cfg.MatchingHandler.GetBookMatches)
}
```
#### Step 5.5: Update existing registerMediaRoutes function
**File:** `internal/router/media.go` (MODIFY)
**Current:** Empty or minimal
**New:** Register all media CRUD endpoints
```go
package router
import (
"github.com/labstack/echo/v4"
"bookhoard/internal/handlers"
)
func registerMediaRoutes(cfg *Config) {
e := cfg.Echo
// JWT middleware for protected routes
jwtMiddleware := createJWTMiddleware(cfg)
protected := e.Group("/api", jwtMiddleware)
admin := protected.Group("", handlers.AdminMiddleware)
// Media item routes (all authenticated users)
protected.GET("/media-items", cfg.MediaHandler.ListMediaItems)
protected.GET("/media-items/filtered", cfg.MediaHandler.ListMediaItemsFiltered)
protected.GET("/media-items/search", cfg.MediaHandler.SearchMediaItems)
protected.GET("/media-items/:id", cfg.MediaHandler.GetMediaItem)
// Media rating routes (all authenticated users)
protected.POST("/media-items/:id/rating", cfg.MediaHandler.CreateMediaRating)
protected.GET("/media-items/:id/rating", cfg.MediaHandler.GetMediaRating)
protected.PUT("/media-items/:id/rating", cfg.MediaHandler.UpdateMediaRating)
protected.DELETE("/media-items/:id/rating", cfg.MediaHandler.DeleteMediaRating)
// Progress routes (all authenticated users)
protected.GET("/progress/:id", cfg.MediaHandler.GetUniversalProgress)
protected.POST("/progress/:id", cfg.MediaHandler.UpdateUniversalProgress)
protected.GET("/progress/:id/history", cfg.MediaHandler.GetProgressHistory)
// Notes routes (all authenticated users)
protected.GET("/media-items/:id/notes", cfg.MediaHandler.GetMediaNotes)
protected.POST("/media-items/:id/notes", cfg.MediaHandler.CreateMediaNote)
protected.GET("/media-items/:id/notes/:noteId", cfg.MediaHandler.GetMediaNote)
protected.PUT("/media-items/:id/notes/:noteId", cfg.MediaHandler.UpdateMediaNote)
protected.DELETE("/media-items/:id/notes/:noteId", cfg.MediaHandler.DeleteMediaNote)
// Highlights routes (all authenticated users)
protected.GET("/media-items/:id/highlights", cfg.MediaHandler.GetMediaHighlights)
protected.POST("/media-items/:id/highlights", cfg.MediaHandler.CreateMediaHighlight)
protected.GET("/media-items/:id/highlights/:highlightId", cfg.MediaHandler.GetMediaHighlight)
protected.PUT("/media-items/:id/highlights/:highlightId", cfg.MediaHandler.UpdateMediaHighlight)
protected.DELETE("/media-items/:id/highlights/:highlightId", cfg.MediaHandler.DeleteMediaHighlight)
// Admin-only media routes
admin.POST("/media-items", cfg.MediaHandler.CreateMediaItem)
admin.PUT("/media-items/:id", cfg.MediaHandler.UpdateMediaItem)
admin.DELETE("/media-items/:id", cfg.MediaHandler.DeleteMediaItem)
}
```
---
## Phase 6: Update main.go (INITIALIZATION)
### File: `cmd/server/main.go`
#### Step 6.1: Create new handler instances
**Current handler creation (lines 78-99):**
```go
authHandler := handlers.NewAuthHandler(queries, cfg.JWTSecret, loginAttemptTracker)
libraryHandler := handlers.NewLibraryHandler(queries)
deviceHandler := handlers.NewDeviceHandler(queries, cfg.JWTSecret, cfg)
// ... other handlers
```
**Add new handler instances:**
```go
authHandler := handlers.NewAuthHandler(queries, cfg.JWTSecret, loginAttemptTracker)
libraryHandler := handlers.NewLibraryHandler(queries)
deviceHandler := handlers.NewDeviceHandler(queries, cfg.JWTSecret, cfg)
// ... existing handlers ...
// NEW: Create refactored handlers
mediaHandler := handlers.NewMediaHandler(queries, queueProcessor) // Worker needed
searchHandler := handlers.NewSearchHandler(queries)
matchingHandler := handlers.NewMatchingHandler(queries, connManager)
```
#### Step 6.2: Update routerConfig
**Current routerConfig (lines 131-150):**
```go
routerConfig := &router.Config{
Echo: e,
Queries: queries,
Cfg: cfg,
DBPool: dbPool,
AuthHandler: authHandler,
LibraryHandler: libraryHandler,
DeviceHandler: deviceHandler,
EbookHandler: ebookHandler, // ← REMOVE
KOReaderHandler: koreaderHandler,
WSHandler: wsHandler,
// ... other fields
}
```
**New routerConfig:**
```go
routerConfig := &router.Config{
Echo: e,
Queries: queries,
Cfg: cfg,
DBPool: dbPool,
AuthHandler: authHandler,
LibraryHandler: libraryHandler,
DeviceHandler: deviceHandler,
// EbookHandler REMOVED
MediaHandler: mediaHandler, // ← NEW
SearchHandler: searchHandler, // ← NEW
MatchingHandler: matchingHandler, // ← NEW
KOReaderHandler: koreaderHandler,
WSHandler: wsHandler,
ConflictHandler: conflictHandler,
AnalyticsHandler: analyticsHandler,
QueueHandler: queueHandler,
CollectionHandler: collectionHandler,
OPDSHandler: opdsHandler,
ConnManager: connManager,
QueueProcessor: queueProcessor,
DeviceAuthMiddleware: deviceAuthMiddleware,
LoginTracker: loginAttemptTracker,
}
```
**Note:** Need to determine where `worker` comes from for MediaHandler. Check existing code.
---
## Phase 7: Clean Up ebook.go (FINALIZE)
### File: `internal/handlers/ebook.go`
#### Step 7.1: Remove moved methods
**Remove these method sections:**
- Lines 474-959: All media CRUD and metadata methods
- Lines 1297-end: All search and matching methods
- Lines 73-104: Collection and device mapping routes
- Lines 105-143: All route registrations
**Keep these sections:**
- Lines 38-69: Handler struct and NewHandler function
- Lines 70-72: Simplified SetupRoutes function
- Lines 159-end: Request/response structs
#### Step 7.2: Update Handler struct dependencies
**Current Handler struct (lines 19-30):**
```go
type Handler struct {
db *database.Queries
worker *worker.Worker // ← MOVE TO MediaHandler
scanner *services.EbookScanner // ← MAY MOVE TO ScannerHandler
scheduler *cron.Cron // ← MAY MOVE TO ScannerHandler
ctx context.Context
cancel context.CancelFunc
mu sync.RWMutex
watchingLibraries map[string]bool
}
```
**Potential final Handler struct:**
```go
// If keeping only SetupRoutes function:
type Handler struct {
// This might be entirely removed if SetupRoutes is only factory
}
func SetupRoutes(g *echo.Group, db *database.Queries, connManager *wsync.ConnectionManager) *Handler {
return NewHandler(db, connManager)
}
```
OR
**If Handler is no longer needed:**
- Remove entire file
- Move remaining structs to appropriate files
- Update all imports
---
## Implementation Order (SAFEST FIRST)
### Phase 1: Create new handler files (ZERO RISK)
1. Create `internal/handlers/media.go` with struct and empty methods
2. Create `internal/handlers/search.go` with struct and empty methods
3. Create `internal/handlers/matching.go` with struct and empty methods
4. Verify compilation
### Phase 2: Move method bodies (LOW RISK)
1. Copy media methods from ebook.go to media.go
2. Copy search methods from ebook.go to search.go
3. Copy matching methods from ebook.go to matching.go
4. Test compilation after each file
### Phase 3: Update router registration (MEDIUM RISK)
1. Update router.go Config struct
2. Create registerSearchRoutes and registerMatchingRoutes
3. Update registerMediaRoutes to use MediaHandler
4. Test compilation
### Phase 4: Update main.go initialization (LOW RISK)
1. Add new handler instance creation
2. Update routerConfig
3. Test compilation and runtime
### Phase 5: Clean up ebook.go (LOW RISK)
1. Remove moved methods
2. Simplify/Remove Handler struct if no longer needed
3. Verify final state
---
## Verification Checklist
### After Each Phase:
**Phase 1 (new files created):**
- [ ] All 3 new files created
- [ ] Code compiles without errors
- [ ] No existing functionality broken
**Phase 2 (methods moved):**
- [ ] All media methods in media.go compile
- [ ] All search methods in search.go compile
- [ ] All matching methods in matching.go compile
- [ ] Original ebook.go still compiles
**Phase 3 (router updated):**
- [ ] router.go compiles with new Config struct
- [ ] registerSearchRoutes compiles
- [ ] registerMatchingRoutes compiles
- [ ] registerMediaRoutes updated correctly
**Phase 4 (main.go updated):**
- [ ] Application compiles
- [ ] All handler instances created
- [ ] routerConfig populated correctly
**Phase 5 (cleanup):**
- [ ] ebook.go reduced from 1,350 to <200 lines
- [ ] All route endpoints still exist
- [ ] Bruno tests still pass
- [ ] Verification script passes (26/26)
### Final Verification Commands:
```bash
# Compilation test
go build ./cmd/server
# Test all critical endpoints still work
curl http://localhost:8765/api/media-items | jq .
curl http://localhost:8765/api/media-items/search?q=test | jq .
curl http://localhost:8765/api/books/match | jq .
# Run verification script
bash scripts/verify-guidelines.sh
# Check file sizes
wc -l internal/handlers/*.go | sort -n
# Expected: media.go ~600, search.go ~50, matching.go ~150, ebook.go <200
# Test Bruno tests still pass
cd bruno && npx bruno test
```
---
## Rollback Strategy
### If any phase breaks:
**Immediate rollback:**
```bash
git checkout -- internal/handlers/media.go
git checkout -- internal/handlers/search.go
git checkout -- internal/handlers/matching.go
git checkout -- internal/router/router.go
git checkout -- internal/router/media.go
git checkout -- cmd/server/main.go
```
**Phase-by-phase rollback:**
```bash
# After Phase 2: If methods don't compile
git checkout -- internal/handlers/media.go
git checkout -- internal/handlers/search.go
git checkout -- internal/handlers/matching.go
# After Phase 3: If router breaks
git checkout -- internal/router/router.go
git checkout -- internal/router/media.go
# After Phase 4: If initialization breaks
git checkout -- cmd/server/main.go
```
**Full project reset:**
```bash
git checkout -- internal/handlers/ebook.go
git clean -fd internal/handlers/media.go internal/handlers/search.go internal/handlers/matching.go
```
---
## Success Metrics
**Before refactor:**
- ebook.go: 1,350 lines
- 1 monolithic handler
- Mixed concerns in single file
**After refactor:**
- ebook.go: ~150 lines (SetupRoutes only)
- media.go: ~600 lines (CRUD + metadata)
- search.go: ~80 lines (search operations)
- matching.go: ~200 lines (sync operations)
- Clear single responsibilities per file
- All API endpoints identical
- All tests continue to work
**File count increase:** +3 new handler files
**Lines of code change:** ~0 (moved, not modified)
**API compatibility:** 100% maintained
**Risk level:** VERY LOW
---
## Final Notes
### Dependencies to verify:
1. **Worker access for MediaHandler:**
- Check where `worker` is created in current code
- Pass to NewMediaHandler properly
2. **AdminMiddleware import:**
- Ensure all router files import handlers package
- AdminMiddleware is accessible
3. **Import statements:**
- Verify all new handler files have correct imports
- No circular dependencies created
### Testing strategy:
1. **Compile after each file creation**
2. **Test endpoint accessibility after router changes**
3. **Run existing test suite before/after**
4. **Verify Bruno tests still pass**
5. **Check verification script passes**
This plan ensures zero API changes while completely reorganizing the codebase into maintainable, single-responsibility files.
---
**Ready for safe implementation by any AI following these exact phases.**
File diff suppressed because it is too large Load Diff