feat(router): improve error handling with dedicated error pages

- Add renderErrorPage helper for consistent error rendering
- Add ensureUserExistsMiddleware to detect deleted users and redirect to login
- Add catch-all 404 handler for unknown routes
- Gracefully handle data loading failures with error messages instead of crashing
- Log errors for debugging while still rendering pages
This commit is contained in:
2026-02-20 13:25:41 -05:00
parent 79fcfa38f0
commit 7c652d5a3a
3 changed files with 186 additions and 43 deletions
+51
View File
@@ -8,6 +8,8 @@ import (
ratelimit "bookhoard/internal/middleware"
"bookhoard/internal/services"
"bookhoard/internal/sync"
"bookhoard/templates"
"bytes"
"log"
"net/http"
"strings"
@@ -105,6 +107,45 @@ 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 {
userIDStr, ok := c.Get("user_id").(string)
if !ok {
return next(c)
}
userUUID, err := uuid.Parse(userIDStr)
if err != nil {
log.Printf("Invalid UUID in user existence check: %v", err)
return next(c)
}
// Check if user exists in database
_, err = cfg.Queries.GetUser(c.Request().Context(), uuidToPGType(userUUID))
if err != nil {
log.Printf("User not found in database: %s", userIDStr)
// Clear invalid cookie
c.SetCookie(&http.Cookie{
Name: "token",
Value: "",
Expires: time.Now().Add(-24 * time.Hour),
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
// Redirect to login with session=invalid
return c.Redirect(http.StatusFound, "/login?session=invalid")
}
return next(c)
}
}
}
// wantsHTML determines if the request expects HTML response
func wantsHTML(header http.Header) bool {
// Check Accept header
@@ -180,6 +221,16 @@ func RegisterRoutes(cfg *Config) *handlers.Handler {
registerDocumentationRoutes(cfg)
e.Static("/static", "web/static")
// Catch-all 404 handler - must be last
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 {
return err
}
return c.HTML(http.StatusNotFound, buf.String())
})
// Start background tasks (queue processor and connection cleanup)
scannerHandler.StartBackgroundTasks()