package router import ( "bytes" "context" "net/http" "time" "bookhoard/internal/handlers" "bookhoard/templates" "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo-jwt/v4" "github.com/labstack/echo/v4" ) func registerFrontendRoutes(cfg *Config) { e := cfg.Echo // ============================================================================ // FRONTEND ROUTES - DO NOT DELETE // These routes serve Server-Side Rendered (SSR) HTML pages for the web UI. // They are NOT API endpoints and should NOT be removed during refactors. // All authenticated frontend routes use the jwtMiddleware to validate tokens. // ============================================================================ // JWT middleware for protected routes jwtMiddleware := echojwt.WithConfig(echojwt.Config{ SigningKey: []byte(cfg.Cfg.JWTSecret), ContextKey: "user", SuccessHandler: func(c echo.Context) { token := c.Get("user").(*jwt.Token) claims := token.Claims.(jwt.MapClaims) c.Set("user_id", claims["user_id"]) c.Set("user_role", claims["user_role"]) c.Set("user_email", claims["user_email"]) c.Set("user_username", claims["user_username"]) }, }) // Protected route group protected := e.Group("/api", jwtMiddleware) // Public routes for login and registration pages e.GET("/login", func(c echo.Context) error { var buf bytes.Buffer err := templates.Login().Render(c.Request().Context(), &buf) if err != nil { return err } return c.HTML(http.StatusOK, buf.String()) }) e.GET("/register", func(c echo.Context) error { var buf bytes.Buffer err := templates.Register().Render(c.Request().Context(), &buf) if err != nil { return err } return c.HTML(http.StatusOK, buf.String()) }) // Root route - landing page with smart login detection e.GET("/", func(c echo.Context) error { var buf bytes.Buffer var err error tokenString := c.Request().Header.Get("Authorization") if tokenString != "" && len(tokenString) > 7 && tokenString[:7] == "Bearer " { tokenString = tokenString[7:] } else { cookie, err := c.Cookie("token") if err == nil { tokenString = cookie.Value } } loggedIn := false if tokenString != "" { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return []byte(cfg.Cfg.JWTSecret), nil }) loggedIn = err == nil && token.Valid } err = templates.Index(loggedIn).Render(c.Request().Context(), &buf) if err != nil { return err } return c.HTML(http.StatusOK, buf.String()) }) // Public redirect routes e.GET("/bookshelf", func(c echo.Context) error { return c.Redirect(http.StatusTemporaryRedirect, "/api/bookshelf") }) e.GET("/dashboard", func(c echo.Context) error { return c.Redirect(http.StatusTemporaryRedirect, "/api/bookshelf") }) // Admin routes e.GET("/admin", handlers.AdminMiddleware(func(c echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return c.HTML(http.StatusInternalServerError, "Error loading user") } var buf bytes.Buffer err = templates.Admin(user).Render(c.Request().Context(), &buf) if err != nil { return err } return c.HTML(http.StatusOK, buf.String()) })) e.GET("/admin/", handlers.AdminMiddleware(func(c echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return c.HTML(http.StatusInternalServerError, "Error loading user") } var buf bytes.Buffer err = templates.Admin(user).Render(c.Request().Context(), &buf) if err != nil { return err } return c.HTML(http.StatusOK, buf.String()) })) e.GET("/admin/profile", handlers.AdminMiddleware(func(c echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return c.HTML(http.StatusInternalServerError, "Error loading user") } var buf bytes.Buffer err = templates.AdminProfile(user).Render(c.Request().Context(), &buf) if err != nil { return err } return c.HTML(http.StatusOK, buf.String()) })) e.GET("/admin/library", handlers.AdminMiddleware(func(c echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return c.HTML(http.StatusInternalServerError, "Error loading user") } var buf bytes.Buffer err = templates.AdminLibrary(user).Render(c.Request().Context(), &buf) if err != nil { return err } return c.HTML(http.StatusOK, buf.String()) })) // Devices page protected.GET("/devices-page", func(c echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return c.HTML(http.StatusInternalServerError, "Error loading user") } devices, err := cfg.DeviceHandler.GetDevicesData(c) if err != nil { return c.HTML(http.StatusInternalServerError, "Error loading devices") } pendingMaps, err := cfg.DeviceHandler.GetPendingRegistrationsData(c) if err != nil { return c.HTML(http.StatusInternalServerError, "Error loading pending") } pendingList := convertPending(pendingMaps) var buf bytes.Buffer err = templates.Devices(user, devices, pendingList).Render(c.Request().Context(), &buf) if err != nil { return err } return c.HTML(http.StatusOK, buf.String()) }) // Conflicts page protected.GET("/conflicts-page", func(c echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) if err != nil { return c.HTML(http.StatusInternalServerError, "Error loading user") } conflictsData, total, unresolved, err := cfg.ConflictHandler.GetConflictsData(c) if err != nil { return c.HTML(http.StatusInternalServerError, "Error loading conflicts") } var buf bytes.Buffer err = templates.Conflicts(user, conflictsData, total, unresolved).Render(c.Request().Context(), &buf) if err != nil { return err } return c.HTML(http.StatusOK, buf.String()) }) // Health check e.GET("/health", func(c echo.Context) error { ctx, cancel := context.WithTimeout(c.Request().Context(), 2*time.Second) defer cancel() if err := pingDB(cfg, ctx); err != nil { return c.JSON(http.StatusServiceUnavailable, map[string]string{ "status": "unhealthy", "error": "database unavailable", }) } return c.JSON(http.StatusOK, map[string]string{ "status": "healthy", "database": "connected", }) }) }