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:
+132
-43
@@ -3,10 +3,13 @@ package router
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"bookhoard/internal/database"
|
||||||
"bookhoard/internal/handlers"
|
"bookhoard/internal/handlers"
|
||||||
|
"bookhoard/internal/services"
|
||||||
"bookhoard/templates"
|
"bookhoard/templates"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
@@ -16,6 +19,16 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
log.Printf("renderErrorPage failed to render template: %v", err)
|
||||||
|
return c.HTML(http.StatusInternalServerError, "Internal server error")
|
||||||
|
}
|
||||||
|
return c.HTML(http.StatusInternalServerError, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
func registerFrontendRoutes(cfg *Config) {
|
func registerFrontendRoutes(cfg *Config) {
|
||||||
e := cfg.Echo
|
e := cfg.Echo
|
||||||
|
|
||||||
@@ -91,7 +104,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Protected frontend routes (no /api prefix)
|
// Protected frontend routes (no /api prefix)
|
||||||
frontendProtected := e.Group("", jwtMiddleware)
|
frontendProtected := e.Group("", jwtMiddleware, ensureUserExistsMiddleware(cfg))
|
||||||
|
|
||||||
// Helper to extract text from pgtype.Text
|
// Helper to extract text from pgtype.Text
|
||||||
getText := func(t pgtype.Text) string {
|
getText := func(t pgtype.Text) string {
|
||||||
@@ -105,9 +118,11 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
frontendProtected.GET("/dashboard", func(c echo.Context) error {
|
frontendProtected.GET("/dashboard", func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errorMsg string
|
||||||
|
|
||||||
libraryID := c.QueryParam("library_id")
|
libraryID := c.QueryParam("library_id")
|
||||||
if libraryID == "" {
|
if libraryID == "" {
|
||||||
userUUID, _ := uuid.Parse(user.ID)
|
userUUID, _ := uuid.Parse(user.ID)
|
||||||
@@ -123,7 +138,8 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
|
|
||||||
prefs, _ := cfg.DashboardService.GetDashboardPreferences(c.Request().Context(), userUUID, libUUID)
|
prefs, _ := cfg.DashboardService.GetDashboardPreferences(c.Request().Context(), userUUID, libUUID)
|
||||||
|
|
||||||
sections, err := cfg.DashboardService.GetDashboardSections(
|
var sections []services.DashboardSection
|
||||||
|
sections, err = cfg.DashboardService.GetDashboardSections(
|
||||||
c.Request().Context(),
|
c.Request().Context(),
|
||||||
userUUID,
|
userUUID,
|
||||||
libUUID,
|
libUUID,
|
||||||
@@ -132,13 +148,19 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
prefs.HiddenCollections,
|
prefs.HiddenCollections,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading dashboard")
|
log.Printf("Dashboard sections query failed: %v", err)
|
||||||
|
sections = []services.DashboardSection{}
|
||||||
|
errorMsg = "Error loading dashboard"
|
||||||
}
|
}
|
||||||
|
|
||||||
userUUID2, _ := uuid.Parse(user.ID)
|
userUUID2, _ := uuid.Parse(user.ID)
|
||||||
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID2))
|
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID2))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading libraries")
|
log.Printf("GetUserVisibleLibraries failed: %v", err)
|
||||||
|
libraries = []database.GetUserVisibleLibrariesRow{}
|
||||||
|
if errorMsg == "" {
|
||||||
|
errorMsg = "Error loading libraries"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
libData := make([]templates.LibraryData, len(libraries))
|
libData := make([]templates.LibraryData, len(libraries))
|
||||||
@@ -155,7 +177,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
sectionData := handlers.BuildSections(sections)
|
sectionData := handlers.BuildSections(sections)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Dashboard(user, sectionData, libData, libraryID).Render(c.Request().Context(), &buf)
|
err = templates.Dashboard(user, sectionData, libData, libraryID, errorMsg).Render(c.Request().Context(), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -166,12 +188,19 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
frontendProtected.GET("/collections", func(c echo.Context) error {
|
frontendProtected.GET("/collections", func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
collections, err := cfg.CollectionHandler.GetCollectionsData(c)
|
|
||||||
|
var errorMsg string
|
||||||
|
var collections []database.Collections
|
||||||
|
|
||||||
|
collections, err = cfg.CollectionHandler.GetCollectionsData(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading collections")
|
log.Printf("GetCollectionsData failed: %v", err)
|
||||||
|
collections = []database.Collections{}
|
||||||
|
errorMsg = "Error loading collections"
|
||||||
}
|
}
|
||||||
|
|
||||||
colData := make([]templates.CollectionData, len(collections))
|
colData := make([]templates.CollectionData, len(collections))
|
||||||
for i, col := range collections {
|
for i, col := range collections {
|
||||||
colData[i] = templates.CollectionData{
|
colData[i] = templates.CollectionData{
|
||||||
@@ -182,8 +211,9 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
Icon: getText(col.Icon),
|
Icon: getText(col.Icon),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Collection(user, colData).Render(c.Request().Context(), &buf)
|
err = templates.Collection(user, colData, errorMsg).Render(c.Request().Context(), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -194,13 +224,18 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
frontendProtected.GET("/custom-section", func(c echo.Context) error {
|
frontendProtected.GET("/custom-section", func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errorMsg string
|
||||||
|
var libraries []database.GetUserVisibleLibrariesRow
|
||||||
|
|
||||||
userUUID, _ := uuid.Parse(user.ID)
|
userUUID, _ := uuid.Parse(user.ID)
|
||||||
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
|
libraries, err = cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading libraries")
|
log.Printf("GetUserVisibleLibraries failed: %v", err)
|
||||||
|
libraries = []database.GetUserVisibleLibrariesRow{}
|
||||||
|
errorMsg = "Error loading libraries"
|
||||||
}
|
}
|
||||||
|
|
||||||
libData := make([]templates.LibraryData, len(libraries))
|
libData := make([]templates.LibraryData, len(libraries))
|
||||||
@@ -215,7 +250,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.CustomSectionBuilder(user, libData).Render(c.Request().Context(), &buf)
|
err = templates.CustomSectionBuilder(user, libData, errorMsg).Render(c.Request().Context(), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -226,14 +261,21 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
frontendProtected.GET("/progress", func(c echo.Context) error {
|
frontendProtected.GET("/progress", func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
progressData, err := cfg.ScannerHandler.GetAllProgressData(c)
|
|
||||||
|
var errorMsg string
|
||||||
|
var progressData []handlers.ProgressWithMedia
|
||||||
|
|
||||||
|
progressData, err = cfg.ScannerHandler.GetAllProgressData(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading progress")
|
log.Printf("GetAllProgressData failed: %v", err)
|
||||||
|
progressData = []handlers.ProgressWithMedia{}
|
||||||
|
errorMsg = "Error loading progress"
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Progress(user, progressData).Render(c.Request().Context(), &buf)
|
err = templates.Progress(user, progressData, errorMsg).Render(c.Request().Context(), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -244,19 +286,33 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
frontendProtected.GET("/devices", func(c echo.Context) error {
|
frontendProtected.GET("/devices", func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
devices, err := cfg.DeviceHandler.GetDevicesData(c)
|
|
||||||
|
var errorMsg string
|
||||||
|
var devices []handlers.DeviceInfo
|
||||||
|
var pendingList []templates.PendingRegistrationData
|
||||||
|
|
||||||
|
devices, err = cfg.DeviceHandler.GetDevicesData(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading devices")
|
log.Printf("GetDevicesData failed: %v", err)
|
||||||
|
devices = []handlers.DeviceInfo{}
|
||||||
|
errorMsg = "Error loading devices"
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingMaps, err := cfg.DeviceHandler.GetPendingRegistrationsData(c)
|
pendingMaps, err := cfg.DeviceHandler.GetPendingRegistrationsData(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading pending")
|
log.Printf("GetPendingRegistrationsData failed: %v", err)
|
||||||
|
pendingList = []templates.PendingRegistrationData{}
|
||||||
|
if errorMsg == "" {
|
||||||
|
errorMsg = "Error loading pending registrations"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pendingList = convertPending(pendingMaps)
|
||||||
}
|
}
|
||||||
pendingList := convertPending(pendingMaps)
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Devices(user, devices, pendingList, cfg.Cfg.BaseURL).Render(c.Request().Context(), &buf)
|
err = templates.Devices(user, devices, pendingList, errorMsg, cfg.Cfg.BaseURL).Render(c.Request().Context(), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -267,14 +323,24 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
frontendProtected.GET("/conflicts", func(c echo.Context) error {
|
frontendProtected.GET("/conflicts", func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
conflictsData, total, unresolved, err := cfg.ConflictHandler.GetConflictsData(c)
|
|
||||||
|
var errorMsg string
|
||||||
|
var conflictsData []handlers.ConflictDetailResponse
|
||||||
|
var total, unresolved int
|
||||||
|
|
||||||
|
conflictsData, total, unresolved, err = cfg.ConflictHandler.GetConflictsData(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading conflicts")
|
log.Printf("GetConflictsData failed: %v", err)
|
||||||
|
conflictsData = []handlers.ConflictDetailResponse{}
|
||||||
|
total = 0
|
||||||
|
unresolved = 0
|
||||||
|
errorMsg = "Error loading conflicts"
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Conflicts(user, conflictsData, total, unresolved).Render(c.Request().Context(), &buf)
|
err = templates.Conflicts(user, conflictsData, total, unresolved, errorMsg).Render(c.Request().Context(), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -285,7 +351,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
frontendProtected.GET("/analytics", func(c echo.Context) error {
|
frontendProtected.GET("/analytics", func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Analytics(user).Render(c.Request().Context(), &buf)
|
err = templates.Analytics(user).Render(c.Request().Context(), &buf)
|
||||||
@@ -303,7 +369,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
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)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Admin(user).Render(c.Request().Context(), &buf)
|
err = templates.Admin(user).Render(c.Request().Context(), &buf)
|
||||||
@@ -316,7 +382,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
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)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Admin(user).Render(c.Request().Context(), &buf)
|
err = templates.Admin(user).Render(c.Request().Context(), &buf)
|
||||||
@@ -329,7 +395,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
frontendProtected.GET("/admin/profile", handlers.AdminMiddleware(func(c echo.Context) error {
|
frontendProtected.GET("/admin/profile", handlers.AdminMiddleware(func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.AdminProfile(user).Render(c.Request().Context(), &buf)
|
err = templates.AdminProfile(user).Render(c.Request().Context(), &buf)
|
||||||
@@ -342,7 +408,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
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)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.AdminLibrary(user).Render(c.Request().Context(), &buf)
|
err = templates.AdminLibrary(user).Render(c.Request().Context(), &buf)
|
||||||
@@ -360,19 +426,32 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
protected.GET("/devices-page", func(c echo.Context) error {
|
protected.GET("/devices-page", func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
devices, err := cfg.DeviceHandler.GetDevicesData(c)
|
var errorMsg string
|
||||||
|
var devices []handlers.DeviceInfo
|
||||||
|
var pendingList []templates.PendingRegistrationData
|
||||||
|
|
||||||
|
devices, err = cfg.DeviceHandler.GetDevicesData(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading devices")
|
log.Printf("GetDevicesData failed: %v", err)
|
||||||
|
devices = []handlers.DeviceInfo{}
|
||||||
|
errorMsg = "Error loading devices"
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingMaps, err := cfg.DeviceHandler.GetPendingRegistrationsData(c)
|
pendingMaps, err := cfg.DeviceHandler.GetPendingRegistrationsData(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading pending")
|
log.Printf("GetPendingRegistrationsData failed: %v", err)
|
||||||
|
pendingList = []templates.PendingRegistrationData{}
|
||||||
|
if errorMsg == "" {
|
||||||
|
errorMsg = "Error loading pending registrations"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pendingList = convertPending(pendingMaps)
|
||||||
}
|
}
|
||||||
pendingList := convertPending(pendingMaps)
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Devices(user, devices, pendingList, cfg.Cfg.BaseURL).Render(c.Request().Context(), &buf)
|
err = templates.Devices(user, devices, pendingList, errorMsg, cfg.Cfg.BaseURL).Render(c.Request().Context(), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -382,14 +461,24 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
protected.GET("/conflicts-page", func(c echo.Context) error {
|
protected.GET("/conflicts-page", func(c echo.Context) error {
|
||||||
user, err := getTemplateUserWithTheme(c, cfg)
|
user, err := getTemplateUserWithTheme(c, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading user")
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
||||||
}
|
}
|
||||||
conflictsData, total, unresolved, err := cfg.ConflictHandler.GetConflictsData(c)
|
|
||||||
|
var errorMsg string
|
||||||
|
var conflictsData []handlers.ConflictDetailResponse
|
||||||
|
var total, unresolved int
|
||||||
|
|
||||||
|
conflictsData, total, unresolved, err = cfg.ConflictHandler.GetConflictsData(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.HTML(http.StatusInternalServerError, "Error loading conflicts")
|
log.Printf("GetConflictsData failed: %v", err)
|
||||||
|
conflictsData = []handlers.ConflictDetailResponse{}
|
||||||
|
total = 0
|
||||||
|
unresolved = 0
|
||||||
|
errorMsg = "Error loading conflicts"
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = templates.Conflicts(user, conflictsData, total, unresolved).Render(c.Request().Context(), &buf)
|
err = templates.Conflicts(user, conflictsData, total, unresolved, errorMsg).Render(c.Request().Context(), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
"bookhoard/templates"
|
"bookhoard/templates"
|
||||||
|
|
||||||
@@ -19,11 +20,13 @@ func getTemplateUserWithTheme(c echo.Context, cfg *Config) (templates.User, erro
|
|||||||
|
|
||||||
userUUID, err := uuid.Parse(userID)
|
userUUID, err := uuid.Parse(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("getTemplateUserWithTheme failed: invalid UUID '%s': %v", userID, err)
|
||||||
return templates.User{}, err
|
return templates.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userDB, err := cfg.Queries.GetUser(c.Request().Context(), uuidToPGType(userUUID))
|
userDB, err := cfg.Queries.GetUser(c.Request().Context(), uuidToPGType(userUUID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("getTemplateUserWithTheme failed: database query error for user ID %s: %v", userID, err)
|
||||||
return templates.User{}, err
|
return templates.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
ratelimit "bookhoard/internal/middleware"
|
ratelimit "bookhoard/internal/middleware"
|
||||||
"bookhoard/internal/services"
|
"bookhoard/internal/services"
|
||||||
"bookhoard/internal/sync"
|
"bookhoard/internal/sync"
|
||||||
|
"bookhoard/templates"
|
||||||
|
"bytes"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"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
|
// wantsHTML determines if the request expects HTML response
|
||||||
func wantsHTML(header http.Header) bool {
|
func wantsHTML(header http.Header) bool {
|
||||||
// Check Accept header
|
// Check Accept header
|
||||||
@@ -180,6 +221,16 @@ func RegisterRoutes(cfg *Config) *handlers.Handler {
|
|||||||
registerDocumentationRoutes(cfg)
|
registerDocumentationRoutes(cfg)
|
||||||
e.Static("/static", "web/static")
|
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)
|
// Start background tasks (queue processor and connection cleanup)
|
||||||
scannerHandler.StartBackgroundTasks()
|
scannerHandler.StartBackgroundTasks()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user