- 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.
26 KiB
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:
media.go- Media items, ratings, progress, notes, highlights (CRUD + metadata)search.go- Query, search, filtered operationsmatching.go- Book linking, file aliases, matching algorithmsebook.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)
// 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:
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:
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:
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
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:
// 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.dbreference) - All imports are correct
Phase 3: Create Search Handler (QUERY OPERATIONS)
File: internal/handlers/search.go (NEW)
Step 3.1: Create SearchHandler struct
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
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):
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):
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):
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:
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)
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)
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
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):
authHandler := handlers.NewAuthHandler(queries, cfg.JWTSecret, loginAttemptTracker)
libraryHandler := handlers.NewLibraryHandler(queries)
deviceHandler := handlers.NewDeviceHandler(queries, cfg.JWTSecret, cfg)
// ... other handlers
Add new handler instances:
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):
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:
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):
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:
// 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)
- Create
internal/handlers/media.gowith struct and empty methods - Create
internal/handlers/search.gowith struct and empty methods - Create
internal/handlers/matching.gowith struct and empty methods - Verify compilation
Phase 2: Move method bodies (LOW RISK)
- Copy media methods from ebook.go to media.go
- Copy search methods from ebook.go to search.go
- Copy matching methods from ebook.go to matching.go
- Test compilation after each file
Phase 3: Update router registration (MEDIUM RISK)
- Update router.go Config struct
- Create registerSearchRoutes and registerMatchingRoutes
- Update registerMediaRoutes to use MediaHandler
- Test compilation
Phase 4: Update main.go initialization (LOW RISK)
- Add new handler instance creation
- Update routerConfig
- Test compilation and runtime
Phase 5: Clean up ebook.go (LOW RISK)
- Remove moved methods
- Simplify/Remove Handler struct if no longer needed
- 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:
# 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:
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:
# 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:
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:
-
Worker access for MediaHandler:
- Check where
workeris created in current code - Pass to NewMediaHandler properly
- Check where
-
AdminMiddleware import:
- Ensure all router files import handlers package
- AdminMiddleware is accessible
-
Import statements:
- Verify all new handler files have correct imports
- No circular dependencies created
Testing strategy:
- Compile after each file creation
- Test endpoint accessibility after router changes
- Run existing test suite before/after
- Verify Bruno tests still pass
- 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.