diff --git a/internal/router/auth.go b/internal/router/auth.go index 129ead2..a02a870 100644 --- a/internal/router/auth.go +++ b/internal/router/auth.go @@ -3,7 +3,7 @@ package router import ( "bookhoard/internal/handlers" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) func registerAuthRoutes(cfg *Config, rateLimitMiddleware echo.MiddlewareFunc) { diff --git a/internal/router/frontend.go b/internal/router/frontend.go index a4a88be..2f19b97 100644 --- a/internal/router/frontend.go +++ b/internal/router/frontend.go @@ -15,12 +15,12 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/jackc/pgx/v5/pgtype" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" "github.com/google/uuid" ) -func renderErrorPage(c echo.Context, message string, errorType string) error { +func renderErrorPage(c *echo.Context, message string, errorType string) error { var buf bytes.Buffer err := templates.ErrorPage(message, errorType).Render(c.Request().Context(), &buf) if err != nil { @@ -51,7 +51,7 @@ func registerFrontendRoutes(cfg *Config) { // ============================================================================ // Public routes for login and registration pages - e.GET("/login", func(c echo.Context) error { + e.GET("/login", func(c *echo.Context) error { var buf bytes.Buffer sessionExpired := c.QueryParam("session") == "expired" deleted := c.QueryParam("deleted") == "true" @@ -62,7 +62,7 @@ func registerFrontendRoutes(cfg *Config) { return c.HTML(http.StatusOK, buf.String()) }) - e.GET("/register", func(c echo.Context) error { + e.GET("/register", func(c *echo.Context) error { var buf bytes.Buffer err := templates.Register().Render(c.Request().Context(), &buf) if err != nil { @@ -72,7 +72,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Root route - landing page with smart login detection - e.GET("/", func(c echo.Context) error { + e.GET("/", func(c *echo.Context) error { var buf bytes.Buffer var err error @@ -117,7 +117,7 @@ func registerFrontendRoutes(cfg *Config) { } // Dashboard page - frontendProtected.GET("/dashboard", func(c echo.Context) error { + frontendProtected.GET("/dashboard", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -203,7 +203,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Collections page - frontendProtected.GET("/collections", func(c echo.Context) error { + frontendProtected.GET("/collections", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -239,7 +239,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Collection create modal - frontendProtected.GET("/collections/create-modal", func(c echo.Context) error { + frontendProtected.GET("/collections/create-modal", func(c *echo.Context) error { var buf bytes.Buffer err := templates.CollectionModal(templates.CollectionData{}).Render(c.Request().Context(), &buf) if err != nil { @@ -249,7 +249,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Collection edit modal - frontendProtected.GET("/collections/:id/edit-modal", func(c echo.Context) error { + frontendProtected.GET("/collections/:id/edit-modal", func(c *echo.Context) error { // Parse collection ID collectionID := c.Param("id") collUUID, err := uuid.Parse(collectionID) @@ -286,7 +286,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Restore system collection modal - frontendProtected.GET("/collections/restore-modal", func(c echo.Context) error { + frontendProtected.GET("/collections/restore-modal", func(c *echo.Context) error { var buf bytes.Buffer err := templates.RestoreSystemCollectionModal().Render(c.Request().Context(), &buf) if err != nil { @@ -296,7 +296,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Collection detail page (works for both system and user collections) - frontendProtected.GET("/collections/:id", func(c echo.Context) error { + frontendProtected.GET("/collections/:id", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -440,7 +440,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Custom Section Builder page - frontendProtected.GET("/custom-section", func(c echo.Context) error { + frontendProtected.GET("/custom-section", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -477,7 +477,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Progress page - frontendProtected.GET("/progress", func(c echo.Context) error { + frontendProtected.GET("/progress", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -502,7 +502,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Devices page - frontendProtected.GET("/devices", func(c echo.Context) error { + frontendProtected.GET("/devices", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -539,7 +539,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Conflicts page - frontendProtected.GET("/conflicts", func(c echo.Context) error { + frontendProtected.GET("/conflicts", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -567,7 +567,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Analytics page - frontendProtected.GET("/analytics", func(c echo.Context) error { + frontendProtected.GET("/analytics", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -581,7 +581,7 @@ func registerFrontendRoutes(cfg *Config) { }) // Profile page (all users) - frontendProtected.GET("/profile", func(c echo.Context) error { + frontendProtected.GET("/profile", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -600,7 +600,7 @@ func registerFrontendRoutes(cfg *Config) { // ============================================================================ // Admin routes - frontendProtected.GET("/admin", handlers.AdminMiddleware(func(c echo.Context) error { + frontendProtected.GET("/admin", handlers.AdminMiddleware(func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -613,7 +613,7 @@ func registerFrontendRoutes(cfg *Config) { return c.HTML(http.StatusOK, buf.String()) })) - frontendProtected.GET("/admin/", handlers.AdminMiddleware(func(c echo.Context) error { + frontendProtected.GET("/admin/", handlers.AdminMiddleware(func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -626,7 +626,7 @@ func registerFrontendRoutes(cfg *Config) { return c.HTML(http.StatusOK, buf.String()) })) - frontendProtected.GET("/admin/library", handlers.AdminMiddleware(func(c echo.Context) error { + frontendProtected.GET("/admin/library", handlers.AdminMiddleware(func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -679,7 +679,7 @@ func registerFrontendRoutes(cfg *Config) { })) // Admin users page - frontendProtected.GET("/admin/users", handlers.AdminMiddleware(func(c echo.Context) error { + frontendProtected.GET("/admin/users", handlers.AdminMiddleware(func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -723,7 +723,7 @@ func registerFrontendRoutes(cfg *Config) { })) // Admin: Get profile modal for editing user - frontendProtected.GET("/admin/users/:id/profile-modal", handlers.AdminMiddleware(func(c echo.Context) error { + frontendProtected.GET("/admin/users/:id/profile-modal", handlers.AdminMiddleware(func(c *echo.Context) error { // Get target user ID from URL targetUserID := c.Param("id") parsedUUID, err := uuid.Parse(targetUserID) @@ -763,7 +763,7 @@ func registerFrontendRoutes(cfg *Config) { // ============================================================================ // Keep legacy routes under /api for existing API consumers - protected.GET("/devices-page", func(c echo.Context) error { + protected.GET("/devices-page", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -798,7 +798,7 @@ func registerFrontendRoutes(cfg *Config) { return c.HTML(http.StatusOK, buf.String()) }) - protected.GET("/conflicts-page", func(c echo.Context) error { + protected.GET("/conflicts-page", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return renderErrorPage(c, "Error loading user", "user_load_error") @@ -833,7 +833,7 @@ func registerFrontendRoutes(cfg *Config) { // ============================================================================ // Health check -func (cfg *Config) GetHealth(c echo.Context) error { +func (cfg *Config) GetHealth(c *echo.Context) error { ctx, cancel := context.WithTimeout(c.Request().Context(), 2*time.Second) defer cancel() if err := pingDB(cfg, ctx); err != nil { diff --git a/internal/router/helpers.go b/internal/router/helpers.go index 9167f81..4c68734 100644 --- a/internal/router/helpers.go +++ b/internal/router/helpers.go @@ -9,10 +9,10 @@ import ( "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) -func getTemplateUserWithTheme(c echo.Context, cfg *Config) (templates.User, error) { +func getTemplateUserWithTheme(c *echo.Context, cfg *Config) (templates.User, error) { userID := c.Get("user_id").(string) userEmail := c.Get("user_email").(string) userUsername := c.Get("user_username").(string) diff --git a/internal/router/library.go b/internal/router/library.go index 177afad..14cc81a 100644 --- a/internal/router/library.go +++ b/internal/router/library.go @@ -3,7 +3,7 @@ package router import ( "bookhoard/internal/handlers" - "github.com/labstack/echo/v4" + "github.com/labstack/echo/v5" ) func registerLibraryRoutes(cfg *Config) { @@ -36,7 +36,7 @@ func registerLibraryRoutes(cfg *Config) { adminLibrary.GET("/:id/folders", cfg.LibraryHandler.GetLibraryFolders) adminLibrary.DELETE("/:id/folders", cfg.LibraryHandler.DeleteLibraryFolder) adminLibrary.GET("/:id/stats", cfg.LibraryHandler.GetLibraryStats) - adminLibrary.POST("/:id/scan", func(c echo.Context) error { + adminLibrary.POST("/:id/scan", func(c *echo.Context) error { libraryID := c.Param("id") scanReq := map[string]interface{}{ "library_id": libraryID, @@ -44,7 +44,7 @@ func registerLibraryRoutes(cfg *Config) { c.Set("scan_request", scanReq) return h.ScanLibrary(c) }) - adminLibrary.GET("/:id/media-items", func(c echo.Context) error { + adminLibrary.GET("/:id/media-items", func(c *echo.Context) error { libraryID := c.Param("id") c.QueryParams().Set("library_id", libraryID) return cfg.MediaHandler.ListMediaItems(c) diff --git a/internal/router/router.go b/internal/router/router.go index d70a195..120e2aa 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -21,9 +21,9 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" - echojwt "github.com/labstack/echo-jwt/v4" - "github.com/labstack/echo/v4" - echomiddleware "github.com/labstack/echo/v4/middleware" + echojwt "github.com/labstack/echo-jwt/v5" + "github.com/labstack/echo/v5" + echomiddleware "github.com/labstack/echo/v5/middleware" ) // CustomValidator wraps the go-playground validator @@ -71,7 +71,7 @@ func createJWTMiddleware(cfg *Config) echo.MiddlewareFunc { SigningKey: []byte(cfg.Cfg.JWTSecret), ContextKey: "user", TokenLookup: "cookie:token,header:Authorization:Bearer ", - SuccessHandler: func(c echo.Context) { + SuccessHandler: func(c *echo.Context) error { token := c.Get("user").(*jwt.Token) claims := token.Claims.(jwt.MapClaims) c.Set("user_id", claims["user_id"]) @@ -83,8 +83,7 @@ func createJWTMiddleware(cfg *Config) echo.MiddlewareFunc { userIDStr, _ := claims["user_id"].(string) userUUID, err := uuid.Parse(userIDStr) if err != nil { - c.Error(echo.NewHTTPError(http.StatusBadRequest, "invalid user ID in token")) - return + return echo.NewHTTPError(http.StatusBadRequest, "invalid user ID in token") } c.Set("user", database.Users{ @@ -93,8 +92,9 @@ func createJWTMiddleware(cfg *Config) echo.MiddlewareFunc { Username: claims["user_username"].(string), Role: claims["user_role"].(string), }) + return nil }, - ErrorHandler: func(c echo.Context, err error) error { + ErrorHandler: func(c *echo.Context, err error) error { // Check if this is a page request (browser navigation) if wantsHTML(c.Request().Header) { // Page request → Redirect to login with message @@ -114,7 +114,7 @@ func createJWTMiddleware(cfg *Config) echo.MiddlewareFunc { // ensureUserExistsMiddleware checks if the authenticated user still exists in the database func ensureUserExistsMiddleware(cfg *Config) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { + return func(c *echo.Context) error { userIDStr, ok := c.Get("user_id").(string) if !ok { return next(c) @@ -184,7 +184,7 @@ func RegisterRoutes(cfg *Config) *handlers.Handler { e.Validator = &CustomValidator{validator: v} // Global middleware - e.Use(echomiddleware.Logger()) + e.Use(echomiddleware.RequestLogger()) e.Use(echomiddleware.Recover()) e.Use(echomiddleware.CORS()) e.Use(ratelimit.RequestTracingMiddleware(cfg.Cfg)) @@ -227,7 +227,7 @@ func RegisterRoutes(cfg *Config) *handlers.Handler { e.Static("/static", "web/static") // Catch-all 404 handler - must be last - e.GET("/*", func(c echo.Context) error { + e.GET("/*", func(c *echo.Context) error { var buf bytes.Buffer err := templates.ErrorPage("Page not found", "404").Render(c.Request().Context(), &buf) if err != nil { diff --git a/internal/router/scanner.go b/internal/router/scanner.go index 147d597..bf75e00 100644 --- a/internal/router/scanner.go +++ b/internal/router/scanner.go @@ -2,7 +2,8 @@ package router import ( "bookhoard/internal/handlers" - "github.com/labstack/echo/v4" + + "github.com/labstack/echo/v5" ) // registerScannerRoutes registers all scanner-related endpoints diff --git a/internal/router/sync.go b/internal/router/sync.go index 6b8dc0f..da5c315 100644 --- a/internal/router/sync.go +++ b/internal/router/sync.go @@ -43,6 +43,6 @@ func registerSyncRoutes(cfg *Config) { func registerWebSocketRoutes(cfg *Config) { e := cfg.Echo - // WebSocket endpoint for real-time sync - e.GET("/ws/sync", cfg.WSHandler.HandleWebSocket) + wsGroup := e.Group("/ws/sync") + wsGroup.GET("", cfg.WSHandler.HandleWebSocket) }