Create dashboardHandler instance and add to router config: - Initialize dashboardHandler using handlers.NewDashboardHandler(queries) - Add dashboardHandler to router.Config for route registration - All dashboard routes are now available at /api/dashboard/*
209 lines
6.8 KiB
Go
209 lines
6.8 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"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
"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)
|
|
}
|
|
|
|
// TODO: templates package was removed during router refactor
|
|
// This function is unused and should be removed or updated
|
|
/*
|
|
func getTemplateUserWithTheme(c echo.Context, queries *database.Queries) (templates.User, error) {
|
|
// Get the user from context
|
|
userID := c.Get("user_id")
|
|
if userID == nil {
|
|
return templates.User{}, fmt.Errorf("user not authenticated")
|
|
}
|
|
|
|
// Convert to UUID
|
|
userUUID, err := uuid.Parse(userID.(string))
|
|
if err != nil {
|
|
return templates.User{}, fmt.Errorf("invalid user ID: %w", err)
|
|
}
|
|
|
|
// Fetch user from database
|
|
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)
|
|
|
|
// 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)
|
|
libraryHandler := handlers.NewLibraryHandler(queries)
|
|
deviceHandler := handlers.NewDeviceHandler(queries, cfg.JWTSecret, cfg)
|
|
deviceAuthMiddleware := middleware.NewDeviceAuthMiddleware(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)
|
|
|
|
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)
|
|
|
|
// NEW: Create refactored handlers
|
|
collectionHandler := handlers.NewCollectionHandler(queries, connManager)
|
|
dashboardHandler := handlers.NewDashboardHandler(queries)
|
|
mediaHandler := handlers.NewMediaHandler(queries, libraryService, worker)
|
|
matchingHandler := handlers.NewMatchingHandler(queries, connManager)
|
|
|
|
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,
|
|
MediaHandler: mediaHandler,
|
|
MatchingHandler: matchingHandler,
|
|
KOReaderHandler: koreaderHandler,
|
|
WSHandler: wsHandler,
|
|
ConflictHandler: conflictHandler,
|
|
AnalyticsHandler: analyticsHandler,
|
|
QueueHandler: queueHandler,
|
|
CollectionHandler: collectionHandler,
|
|
DashboardHandler: dashboardHandler,
|
|
OPDSHandler: opdsHandler,
|
|
SystemSettingsHandler: systemSettingsHandler,
|
|
ConnManager: connManager,
|
|
QueueProcessor: queueProcessor,
|
|
DeviceAuthMiddleware: deviceAuthMiddleware,
|
|
LoginTracker: loginAttemptTracker,
|
|
}
|
|
|
|
// Register all routes and get ebook handler
|
|
ebookHandler := router.RegisterRoutes(routerConfig)
|
|
|
|
// ========================================================================
|
|
// APPLICATION LIFECYCLE MANAGEMENT
|
|
// ========================================================================
|
|
|
|
// Create app with lifecycle management
|
|
application := app.New(e, ebookHandler)
|
|
|
|
// ========================================================================
|
|
// START SERVER (managed by app lifecycle)
|
|
// ========================================================================
|
|
|
|
log.Printf("Starting server on port %s", cfg.ServerPort)
|
|
|
|
// Start HTTP server in background
|
|
go func() {
|
|
if err := e.Start(":" + cfg.ServerPort); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("Server failed to start: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Start application (blocks until shutdown signal)
|
|
if err := application.Start(); err != nil {
|
|
log.Fatalf("Application error: %v", err)
|
|
}
|
|
}
|