Files
bookhoard/cmd/server/main.go
T
john-okeefe 2fc44e6d9c feat(scanner): restore auto-start functionality for scheduler and watch mode
Phase 1 of scanner restoration plan

Changes:
- cmd/server/main.go: Capture ebookHandler from router.RegisterRoutes
- cmd/server/main.go: Start scheduler in background goroutine
- cmd/server/main.go: Defer StopScheduler() for graceful shutdown
- cmd/server/main.go: Start watch mode for all libraries after 2-second delay
- internal/router/router.go: Return ebookHandler from RegisterRoutes

This restores critical functionality that was removed during router refactor:
- Auto-scanning now works again
- Watch mode starts automatically for all libraries
- Graceful shutdown properly stops scheduler

Fixes issue where scheduler and watch mode were not starting on server boot.
2026-02-07 17:07:25 -05:00

182 lines
5.9 KiB
Go

package main
import (
"bookhoard/internal/config"
"bookhoard/internal/database"
"bookhoard/internal/handlers"
"bookhoard/internal/middleware"
ratelimit "bookhoard/internal/middleware"
"bookhoard/internal/router"
"bookhoard/internal/services"
"bookhoard/internal/sync"
"bookhoard/templates"
"context"
"log"
"time"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
"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)
}
func getTemplateUserWithTheme(c echo.Context, queries *database.Queries) (templates.User, error) {
userID := c.Get("user_id").(string)
userEmail := c.Get("user_email").(string)
userUsername := c.Get("user_username").(string)
userRole := c.Get("user_role").(string)
userUUID, err := uuid.Parse(userID)
if err != nil {
return templates.User{}, err
}
userDB, err := queries.GetUser(c.Request().Context(), pgtype.UUID{Bytes: userUUID, Valid: true})
if err != nil {
return templates.User{}, err
}
userTheme := "tokyo-night"
if userDB.Theme.Valid {
userTheme = userDB.Theme.String
}
return templates.User{
ID: userID,
Email: userEmail,
Username: userUsername,
Role: userRole,
Theme: userTheme,
}, nil
}
func main() {
cfg := config.LoadConfig()
dbPool, err := pgxpool.New(context.Background(), cfg.DatabaseURL())
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer dbPool.Close()
queries := database.New(dbPool)
// Create login attempt tracker: 5 failed attempts = 15 minute lockout
loginAttemptTracker := ratelimit.NewLoginAttemptTracker(5, 15*time.Minute, 5*time.Minute)
authHandler := handlers.NewAuthHandler(queries, cfg.JWTSecret, loginAttemptTracker)
libraryHandler := handlers.NewLibraryHandler(queries)
deviceHandler := handlers.NewDeviceHandler(queries, cfg.JWTSecret, cfg)
deviceAuthMiddleware := middleware.NewDeviceAuthMiddleware(queries)
// Create WebSocket connection manager
connManager := sync.NewConnectionManager()
connManager.StartCleanupTask()
// Create sync queue processor
queueProcessor := sync.NewSyncQueueProcessor(queries)
go queueProcessor.Start(context.Background())
koreaderHandler := handlers.NewKOReaderHandler(queries, connManager, queueProcessor)
wsHandler := handlers.NewWSHandler(queries, connManager, cfg.JWTSecret, deviceAuthMiddleware)
conflictHandler := handlers.NewConflictHandler(queries, connManager)
analyticsHandler := handlers.NewAnalyticsHandler(queries)
queueHandler := handlers.NewQueueHandler(queries, queueProcessor)
// Create conversion service for EPUB→KEPUB conversion
conversionService := services.NewConversionService(queries, "/var/bookhoard/cache/kepub")
opdsHandler := handlers.NewOPDSHandler(queries, conversionService)
e := echo.New()
// Set up validator
v := validator.New()
// Register custom password complexity validator
if err := ratelimit.RegisterPasswordValidation(v); err != nil {
log.Fatal("Failed to register password validator:", err)
}
e.Validator = &CustomValidator{validator: v}
// Middleware
e.Use(echomiddleware.Logger())
e.Use(echomiddleware.Recover())
e.Use(echomiddleware.CORS())
e.Use(ratelimit.RequestTracingMiddleware(cfg))
// Rate limiter for auth endpoints
// rateLimiterConfig := ratelimit.RateLimiterConfig{
// Enabled: cfg.RateLimitEnabled,
// RequestsPerMinute: cfg.RequestsPerMinute,
// CleanupInterval: 5 * time.Minute,
// }
// rateLimiter := ratelimit.NewRateLimiter(rateLimiterConfig)
// rateLimitMiddleware := ratelimit.RateLimiterMiddleware(rateLimiter) // Now in router/auth.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: nil, // TODO: Initialize collection handler
OPDSHandler: opdsHandler,
ConnManager: connManager,
QueueProcessor: queueProcessor,
DeviceAuthMiddleware: deviceAuthMiddleware,
LoginTracker: loginAttemptTracker,
}
// 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)
}
}()
// Public library types endpoint (no authentication required)
e.GET("/api/libraries/types", libraryHandler.GetLibraryTypes)
// ========================================================================
// FRONTEND ROUTES, HEALTH CHECK, DOCS (all now in router package)
// ========================================================================
// Start server
log.Printf("Starting server on port %s", cfg.ServerPort)
e.Logger.Fatal(e.Start(":" + cfg.ServerPort))
}