Wire up the ProcessingIssuesHandler throughout the application: cmd/server/main.go: - Remove obsolete commented-out getTemplateUserWithTheme function - Instantiate ProcessingIssuesHandler with database queries - Add handler to router Config (with field alignment cleanup) internal/router/router.go: - Add ProcessingIssuesHandler field to router Config struct - Reformat Config struct for better field alignment This enables the processing issues API endpoints for listing and getting statistics about issues within libraries, integrated with the admin UI.
186 lines
6.7 KiB
Go
186 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bookhoard/internal/app"
|
|
"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"
|
|
"context"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"github.com/labstack/echo/v5"
|
|
echomiddleware "github.com/labstack/echo/v5/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 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)
|
|
|
|
// Initialize database schema
|
|
log.Println("🔧 Ensuring database schema is initialized...")
|
|
ctx := context.Background()
|
|
if err := database.Initialize(ctx, dbPool); err != nil {
|
|
log.Fatal("❌ Database schema initialization failed:", err)
|
|
}
|
|
log.Println("✅ Database schema initialized and verified, starting server...")
|
|
|
|
// 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)
|
|
systemSettingsHandler := handlers.NewSystemSettingsHandler(queries)
|
|
sidecarHandler := handlers.NewSidecarHandler(queries, cfg)
|
|
libraryHandler := handlers.NewLibraryHandler(queries)
|
|
deviceHandler := handlers.NewDeviceHandler(queries, cfg.JWTSecret, cfg)
|
|
deviceAuthMiddleware := middleware.NewDeviceAuthMiddleware(queries)
|
|
processingIssuesHandler := handlers.NewProcessingIssuesHandler(queries)
|
|
|
|
// Create WebSocket connection manager
|
|
connManager := sync.NewConnectionManager()
|
|
|
|
// Create sync queue processor
|
|
queueProcessor := sync.NewSyncQueueProcessor(queries)
|
|
|
|
// Create library service
|
|
libraryService := services.NewLibraryService(queries)
|
|
|
|
// Create worker for background tasks
|
|
worker := services.NewWorker(3, connManager)
|
|
services.WorkerInstance = worker
|
|
|
|
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, libraryService, conversionService)
|
|
|
|
// NEW: Create refactored handlers
|
|
collectionHandler := handlers.NewCollectionHandler(queries, libraryService, connManager)
|
|
dashboardService := services.NewDashboardService(queries)
|
|
dashboardHandler := handlers.NewDashboardHandler(queries)
|
|
filtersHandler := handlers.NewFiltersHandler(queries)
|
|
mediaHandler := handlers.NewMediaHandler(queries, libraryService, worker)
|
|
matchingHandler := handlers.NewMatchingHandler(queries, connManager)
|
|
jobsHandler := handlers.NewJobsHandler(queries, worker)
|
|
|
|
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.RequestLogger())
|
|
e.Use(echomiddleware.Recover())
|
|
e.Use(echomiddleware.CORSWithConfig(echomiddleware.CORSConfig{
|
|
AllowOrigins: []string{"*"},
|
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
|
|
ExposeHeaders: []string{"Content-Length"},
|
|
AllowCredentials: false,
|
|
}))
|
|
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,
|
|
MediaHandler: mediaHandler,
|
|
MatchingHandler: matchingHandler,
|
|
ProcessingIssuesHandler: processingIssuesHandler,
|
|
KOReaderHandler: koreaderHandler,
|
|
WSHandler: wsHandler,
|
|
ConflictHandler: conflictHandler,
|
|
AnalyticsHandler: analyticsHandler,
|
|
QueueHandler: queueHandler,
|
|
CollectionHandler: collectionHandler,
|
|
FiltersHandler: filtersHandler,
|
|
DashboardHandler: dashboardHandler,
|
|
DashboardService: dashboardService,
|
|
OPDSHandler: opdsHandler,
|
|
Worker: worker,
|
|
SystemSettingsHandler: systemSettingsHandler,
|
|
SidecarHandler: sidecarHandler,
|
|
ConnManager: connManager,
|
|
QueueProcessor: queueProcessor,
|
|
DeviceAuthMiddleware: deviceAuthMiddleware,
|
|
JobsHandler: jobsHandler,
|
|
LoginTracker: loginAttemptTracker,
|
|
}
|
|
|
|
// Register all routes and get ebook handler
|
|
_ = router.RegisterRoutes(routerConfig)
|
|
|
|
// ========================================================================
|
|
// APPLICATION LIFECYCLE MANAGEMENT
|
|
// ========================================================================
|
|
|
|
// Create app with lifecycle management
|
|
application := app.New(e)
|
|
|
|
// ========================================================================
|
|
// START SERVER (managed by app lifecycle)
|
|
// ========================================================================
|
|
log.Printf("Starting server on port %s", cfg.ServerPort)
|
|
|
|
// Start HTTP server
|
|
if err := application.StartServer(":" + cfg.ServerPort); err != nil {
|
|
log.Fatalf("Failed to start server: %v", err)
|
|
}
|
|
|
|
// Start application lifecycle (blocks until shutdown signal)
|
|
if err := application.Start(); err != nil {
|
|
log.Fatalf("Application error: %v", err)
|
|
}
|
|
}
|