Files
bookhoard/internal/router/router.go
T
john-okeefe 296c45e870 refactor(router): Phase 5 - update router package with new handlers
- Update Config struct to replace EbookHandler with MediaHandler, SearchHandler, MatchingHandler
- Add CollectionHandler to Config struct
- Update RegisterRoutes to call new registration functions
- Create registerCollectionsRoutes for 15 collection endpoints
- Create registerSearchRoutes for search endpoints (uses MediaHandler.SearchMediaItems, MatchingHandler.QueryBooks)
- Create registerMatchingRoutes for 8 matching/linking endpoints
- Update registerMediaRoutes to use cfg.MediaHandler (adds 24 media endpoints)
- Create registerProgressRoutes for 3 universal progress endpoints

All 57 routes preserved and properly registered with correct handlers.
No functionality lost, all endpoints work identically.

This is Phase 5 of the ebook.go refactoring plan.
2026-02-07 19:50:19 -05:00

147 lines
4.6 KiB
Go

package router
import (
"bookhoard/internal/config"
"bookhoard/internal/database"
"bookhoard/internal/handlers"
"bookhoard/internal/middleware"
ratelimit "bookhoard/internal/middleware"
"bookhoard/internal/sync"
"log"
"net/http"
"time"
"github.com/go-playground/validator/v10"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
echomiddleware "github.com/labstack/echo/v4/middleware"
)
// CustomValidator wraps the go-playground validator
type CustomValidator struct {
validator *validator.Validate
}
func (cv *CustomValidator) Validate(i interface{}) error {
return cv.validator.Struct(i)
}
// Config holds all dependencies needed for route registration
type Config struct {
Echo *echo.Echo
Queries *database.Queries
Cfg *config.Config
DBPool interface{} // pgxpool.Pool interface
AuthHandler *handlers.AuthHandler
LibraryHandler *handlers.LibraryHandler
DeviceHandler *handlers.DeviceHandler
MediaHandler *handlers.MediaHandler
SearchHandler *handlers.SearchHandler
MatchingHandler *handlers.MatchingHandler
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
}
// createJWTMiddleware creates a JWT middleware with proper user context setup
func createJWTMiddleware(cfg *Config) echo.MiddlewareFunc {
return echojwt.WithConfig(echojwt.Config{
SigningKey: []byte(cfg.Cfg.JWTSecret),
ContextKey: "user",
SuccessHandler: func(c echo.Context) {
token := c.Get("user").(*jwt.Token)
claims := token.Claims.(jwt.MapClaims)
c.Set("user_id", claims["user_id"])
c.Set("user_role", claims["user_role"])
c.Set("user_email", claims["user_email"])
c.Set("user_username", claims["user_username"])
// Parse UUID from string claims
userIDStr, _ := claims["user_id"].(string)
userUUID, err := uuid.Parse(userIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user ID in token"})
return
}
c.Set("user", database.Users{
ID: pgtype.UUID{Bytes: [16]byte(userUUID), Valid: true},
Email: claims["user_email"].(string),
Username: claims["user_username"].(string),
Role: claims["user_role"].(string),
})
},
})
}
// RegisterRoutes registers all application routes
func RegisterRoutes(cfg *Config) *handlers.Handler {
e := cfg.Echo
// Set up validator
v := validator.New()
if err := ratelimit.RegisterPasswordValidation(v); err != nil {
log.Fatal("Failed to register password validator:", err)
}
e.Validator = &CustomValidator{validator: v}
// Global middleware
e.Use(echomiddleware.Logger())
e.Use(echomiddleware.Recover())
e.Use(echomiddleware.CORS())
e.Use(ratelimit.RequestTracingMiddleware(cfg.Cfg))
// Rate limiter
rateLimiterConfig := ratelimit.RateLimiterConfig{
Enabled: cfg.Cfg.RateLimitEnabled,
RequestsPerMinute: cfg.Cfg.RequestsPerMinute,
CleanupInterval: 5 * time.Minute,
}
rateLimiter := ratelimit.NewRateLimiter(rateLimiterConfig)
rateLimitMiddleware := ratelimit.RateLimiterMiddleware(rateLimiter)
// Register core application routes (collections, devices, media, etc.) - ONCE
jwtMiddleware := createJWTMiddleware(cfg)
protected := e.Group("/api", jwtMiddleware)
// Register route groups
registerAuthRoutes(cfg, rateLimitMiddleware)
registerLibraryRoutes(cfg)
registerDeviceRoutes(cfg)
registerSyncRoutes(cfg)
registerCollectionsRoutes(cfg)
registerMediaRoutes(cfg)
registerSearchRoutes(cfg)
registerMatchingRoutes(cfg)
registerConflictRoutes(cfg)
registerAnalyticsRoutes(cfg)
registerQueueRoutes(cfg)
registerOPDSRoutes(cfg)
registerWebSocketRoutes(cfg)
registerFrontendRoutes(cfg)
registerDocumentationRoutes(cfg)
// Create ebook handler for scanner routes and progress routes
ebookHandler := handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager)
// Register progress routes with actual handler
registerProgressRoutes(cfg, ebookHandler)
// Register scanner routes (admin only)
admin := protected.Group("", handlers.AdminMiddleware)
registerScannerRoutes(admin, ebookHandler)
return ebookHandler
}