feat: add router package structure for route organization
Create internal/router/ package to organize route registration: - router.go: Main router setup and configuration - auth.go: Authentication routes (login, register, profile, etc.) - docs.go: Documentation routes - frontend.go: Frontend SSR routes (/, /login, /admin, etc.) - helpers.go: Helper functions for template rendering This is the first step in refactoring 858-line main.go into a more maintainable structure following Go best practices. Routes themselves have NOT changed - only organization.
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
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")
|
||||
}
|
||||
deviceData, err := cfg.DeviceHandler.GetDevicesData(c)
|
||||
if err != nil {
|
||||
return c.HTML(http.StatusInternalServerError, "Error loading devices")
|
||||
}
|
||||
pendingData, err := cfg.DeviceHandler.GetPendingRegistrationsData(c)
|
||||
if err != nil {
|
||||
return c.HTML(http.StatusInternalServerError, "Error loading pending")
|
||||
}
|
||||
devicesList := convertDevices(deviceData)
|
||||
pendingList := convertPending(pendingData)
|
||||
var buf bytes.Buffer
|
||||
err = templates.Devices(user, devicesList, 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",
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user