Add DashboardService and DashboardHandler to application configuration: Router Config Updates (internal/router/router.go): - Add services import for DashboardService type - Add DashboardService field to Config struct - DashboardService: Used by SSR routes in frontend.go for data fetching - DashboardHandler: Used by API routes in dashboard.go for JSON endpoints Server Initialization (cmd/server/main.go): - Create dashboardService instance using services.NewDashboardService(queries) - Keep dashboardHandler creation (already exists from Phase 4) - Add DashboardService to routerConfig - Both services now available for dependency injection Test Helpers (cmd/server/tests/test_helpers.go): - Create dashboardService instance for testing - Create dashboardHandler instance for testing - Add both DashboardService and DashboardHandler to routerConfig - Ensures test environment matches production setup Architecture Rationale: - DashboardService: Service layer with business logic (reusable by SSR, mobile) - DashboardHandler: HTTP handler layer (JSON API endpoints) - Separation allows SSR templates to call service directly - API routes use handler for proper HTTP response handling - Mobile apps can use API endpoints via DashboardHandler All three files updated consistently for complete integration.
211 lines
6.9 KiB
Go
211 lines
6.9 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)
|
|
dashboardService := services.NewDashboardService(queries)
|
|
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,
|
|
DashboardService: dashboardService,
|
|
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)
|
|
}
|
|
}
|