diff --git a/internal/app/app.go b/internal/app/app.go index bfa7376..644379c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,6 +2,7 @@ package app import ( "context" + "errors" "log" "net/http" "os" @@ -40,7 +41,7 @@ func (a *App) StartServer(addr string) error { // Start HTTP server in background go func() { - if err := a.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + if err := a.server.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) { log.Fatalf("Server failed to start: %v", err) } }() diff --git a/internal/handlers/analytics.go b/internal/handlers/analytics.go index 7be958c..9df4a66 100644 --- a/internal/handlers/analytics.go +++ b/internal/handlers/analytics.go @@ -3,6 +3,7 @@ package handlers import ( "bookhoard/internal/database" "context" + "errors" "fmt" "net/http" "time" @@ -98,7 +99,7 @@ func (h *AnalyticsHandler) GetReadingStats(c *echo.Context) error { CreatedAt_2: pgtype.Timestamptz{Time: endTime, Valid: true}, }) - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusInternalServerError, "failed to get reading history") } @@ -211,7 +212,7 @@ func (h *AnalyticsHandler) GetDeviceUsage(c *echo.Context) error { ctx := context.Background() usage, err := h.db.GetUserDeviceUsage(ctx, user.ID) - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusInternalServerError, "failed to get device usage") } @@ -263,7 +264,7 @@ func (h *AnalyticsHandler) GetPopularBooks(c *echo.Context) error { Limit: limitInt, }) - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusInternalServerError, "failed to get popular books") } diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index b0eb38b..41cb079 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -7,6 +7,7 @@ import ( "bookhoard/internal/database" "bookhoard/internal/middleware" "context" + "errors" "fmt" "net/http" "os" @@ -887,7 +888,7 @@ func (h *AuthHandler) DeleteUser(c *echo.Context) error { // Delete user (this will cascade to delete all related data) err = h.db.DeleteUser(c.Request().Context(), targetUserUUID) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { if c.Request().Header.Get("HX-Request") == "true" { return c.HTML(http.StatusNotFound, `
User not found
`) } @@ -940,7 +941,7 @@ func (h *AuthHandler) UpdateUserMaxDevices(c *echo.Context) error { MaxDevices: pgtype.Int4{Int32: req.MaxDevices, Valid: true}, }) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) diff --git a/internal/handlers/collections.go b/internal/handlers/collections.go index 52acf57..f1f99e4 100644 --- a/internal/handlers/collections.go +++ b/internal/handlers/collections.go @@ -6,6 +6,7 @@ import ( wsync "bookhoard/internal/sync" "bookhoard/internal/utils" "encoding/json" + "errors" "fmt" "net/http" "strconv" @@ -891,7 +892,7 @@ func (h *CollectionHandler) PreviewCollection(c *echo.Context) error { _, err = h.db.GetLibrary(c.Request().Context(), pgtype.UUID{Bytes: libUUID, Valid: true}) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusBadRequest, map[string]string{"error": "library not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) diff --git a/internal/handlers/conflicts.go b/internal/handlers/conflicts.go index 446ca46..82f253b 100644 --- a/internal/handlers/conflicts.go +++ b/internal/handlers/conflicts.go @@ -5,6 +5,7 @@ import ( wsync "bookhoard/internal/sync" "context" "encoding/json" + "errors" "net/http" "time" @@ -83,7 +84,7 @@ func (h *ConflictHandler) GetConflictsData(c *echo.Context) ([]ConflictDetailRes conflicts, err = h.db.ListSyncConflictsByUser(ctx, user.ID) } - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { return nil, 0, 0, err } @@ -150,7 +151,7 @@ func (h *ConflictHandler) GetConflict(c *echo.Context) error { conflictUUID := pgtype.UUID{Bytes: [16]byte(conflictID), Valid: true} conflict, err := h.db.GetSyncConflict(context.Background(), conflictUUID) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "conflict not found") } return echo.NewHTTPError(http.StatusInternalServerError, "failed to get conflict") @@ -214,7 +215,7 @@ func (h *ConflictHandler) ResolveConflict(c *echo.Context) error { conflictUUID := pgtype.UUID{Bytes: [16]byte(conflictID), Valid: true} conflict, err := h.db.GetSyncConflict(context.Background(), conflictUUID) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "conflict not found") } return echo.NewHTTPError(http.StatusInternalServerError, "failed to get conflict") @@ -292,7 +293,7 @@ func (h *ConflictHandler) applyProgressResolution(mediaItemID pgtype.UUID, userI MediaItemID: mediaItemID, UserID: userID, }) - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { return err } @@ -380,7 +381,7 @@ func (h *ConflictHandler) DeleteConflict(c *echo.Context) error { conflictUUID := pgtype.UUID{Bytes: [16]byte(conflictID), Valid: true} conflict, err := h.db.GetSyncConflict(context.Background(), conflictUUID) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "conflict not found") } return echo.NewHTTPError(http.StatusInternalServerError, "failed to get conflict") @@ -641,7 +642,7 @@ func (h *ConflictHandler) applyResolution(mediaItemID pgtype.UUID, userID pgtype MediaItemID: mediaItemID, UserID: userID, }) - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { return err } diff --git a/internal/handlers/media.go b/internal/handlers/media.go index 653c062..f2cf439 100644 --- a/internal/handlers/media.go +++ b/internal/handlers/media.go @@ -6,6 +6,7 @@ import ( "bookhoard/internal/utils" "context" "encoding/json" + "errors" "fmt" "io" "log" @@ -728,7 +729,7 @@ func (mh *MediaHandler) GetMediaItem(c *echo.Context) error { item, err := mh.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaUUID, Valid: true}) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusNotFound, map[string]string{"error": "media item not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) @@ -836,7 +837,7 @@ func (mh *MediaHandler) GetMediaRating(c *echo.Context) error { UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, }) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusOK, map[string]interface{}{"rating": nil}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) @@ -894,7 +895,7 @@ func (mh *MediaHandler) GetMediaReadingProgress(c *echo.Context) error { UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, }) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusOK, map[string]interface{}{ "current_page": 0, "total_pages": nil, @@ -1026,7 +1027,7 @@ func (mh *MediaHandler) CreateMediaItem(c *echo.Context) error { _, err = mh.db.GetLibrary(c.Request().Context(), pgtype.UUID{Bytes: req.LibraryID, Valid: true}) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusBadRequest, map[string]string{"error": "library not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) @@ -1237,7 +1238,7 @@ func (mh *MediaHandler) GetMediaNote(c *echo.Context) error { note, err := mh.db.GetMediaNote(c.Request().Context(), pgtype.UUID{Bytes: noteUUID, Valid: true}) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusNotFound, map[string]string{"error": "note not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) @@ -1377,7 +1378,7 @@ func (mh *MediaHandler) GetMediaHighlight(c *echo.Context) error { highlight, err := mh.db.GetMediaHighlight(c.Request().Context(), pgtype.UUID{Bytes: highlightUUID, Valid: true}) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusNotFound, map[string]string{"error": "highlight not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) diff --git a/internal/handlers/progress.go b/internal/handlers/progress.go index 612f711..d2af600 100644 --- a/internal/handlers/progress.go +++ b/internal/handlers/progress.go @@ -5,6 +5,7 @@ import ( wsync "bookhoard/internal/sync" "bookhoard/internal/utils" "context" + "errors" "net/http" "strconv" "time" @@ -43,7 +44,7 @@ func (h *Handler) GetUniversalProgress(c *echo.Context) error { UserID: pgtype.UUID{Bytes: user.ID.Bytes, Valid: true}, }) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusOK, map[string]interface{}{ "media_item_id": mediaItemID, "progress": nil, diff --git a/internal/handlers/reader.go b/internal/handlers/reader.go index 84a9faa..2f665da 100644 --- a/internal/handlers/reader.go +++ b/internal/handlers/reader.go @@ -4,6 +4,7 @@ import ( "bookhoard/internal/database" "bookhoard/internal/services" "context" + "errors" "fmt" "net/http" "os" @@ -63,7 +64,7 @@ func (h *ReaderHandler) ShowReader(c *echo.Context) error { // Get media item mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: parsedUUID, Valid: true}) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusNotFound, map[string]string{"error": "Media item not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch media item"}) @@ -108,7 +109,7 @@ func (h *ReaderHandler) ShowReader(c *echo.Context) error { MediaItemID: pgtype.UUID{Bytes: parsedUUID, Valid: true}, UserID: userData.ID, }) - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { progress = database.ReadingProgress{} } @@ -316,7 +317,7 @@ func (h *ReaderHandler) GetReadingSpeed(c *echo.Context) error { }) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { // Return zero values if no reading has occurred return c.JSON(http.StatusOK, map[string]interface{}{ "words_per_minute": 0, @@ -597,7 +598,7 @@ func (h *ReaderHandler) ParseEbook(c *echo.Context) error { // Fetch media item mediaItem, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: parsedUUID, Valid: true}) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(404, map[string]string{"error": "Media item not found"}) } return c.JSON(500, map[string]string{"error": "Failed to fetch media item"}) diff --git a/internal/handlers/refresh_token.go b/internal/handlers/refresh_token.go index 45db111..04f650e 100644 --- a/internal/handlers/refresh_token.go +++ b/internal/handlers/refresh_token.go @@ -3,6 +3,7 @@ package handlers import ( "bookhoard/internal/database" "context" + "errors" "fmt" "net/http" "time" @@ -52,7 +53,7 @@ func (h *AuthHandler) RefreshAccessToken(c *echo.Context) error { tokenInfo, err := h.db.GetRefreshToken(c.Request().Context(), tokenUUID) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusUnauthorized, map[string]string{"error": "invalid or expired refresh token"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to validate refresh token"}) @@ -88,7 +89,7 @@ func (h *AuthHandler) Logout(c *echo.Context) error { } err = h.db.RevokeRefreshToken(c.Request().Context(), tokenUUID) - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to revoke refresh token"}) } diff --git a/internal/handlers/sidecar.go b/internal/handlers/sidecar.go index 0da9d9a..92c3b6a 100644 --- a/internal/handlers/sidecar.go +++ b/internal/handlers/sidecar.go @@ -171,7 +171,7 @@ func (h *SidecarHandler) GetSidecarConfig(c *echo.Context) error { } // Build sidecar config - config := SidecarConfig{ + sidecarConfig := SidecarConfig{ Version: "1.0", Bookhoard: SidecarBookhoardConfig{ OPDSCatalog: opdsCatalogURL, @@ -188,7 +188,7 @@ func (h *SidecarHandler) GetSidecarConfig(c *echo.Context) error { LastUpdated: time.Now().Format(time.RFC3339), } - return c.JSON(http.StatusOK, config) + return c.JSON(http.StatusOK, sidecarConfig) } // DownloadSidecarConfig generates a .bookhoard.json file for device setup @@ -352,8 +352,8 @@ func (h *SidecarHandler) GetSystemConfiguration(c *echo.Context) error { // Build config map result := make(map[string]string) - for _, config := range configs { - result[config.Key] = config.Value + for _, systemConfig := range configs { + result[systemConfig.Key] = systemConfig.Value } return c.JSON(http.StatusOK, result) diff --git a/internal/handlers/system_settings.go b/internal/handlers/system_settings.go index c7fad0b..0acde00 100644 --- a/internal/handlers/system_settings.go +++ b/internal/handlers/system_settings.go @@ -2,6 +2,7 @@ package handlers import ( "bookhoard/internal/database" + "errors" "net/http" "strconv" @@ -47,7 +48,7 @@ func (h *SystemSettingsHandler) UpdateScanSettings(c *echo.Context) error { SettingValue: scanFrequencyValue, }) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusNotFound, map[string]string{"error": "system setting not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) @@ -58,7 +59,7 @@ func (h *SystemSettingsHandler) UpdateScanSettings(c *echo.Context) error { SettingValue: autoScanValue, }) if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusNotFound, map[string]string{"error": "system setting not found"}) } return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) @@ -74,7 +75,7 @@ func (h *SystemSettingsHandler) UpdateScanSettings(c *echo.Context) error { func (h *SystemSettingsHandler) GetScanSettings(c *echo.Context) error { scanFrequencySetting, err := h.db.GetSystemSetting(c.Request().Context(), "scan_poll_interval_seconds") if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusOK, ScanSettingsResponse{ ScanPollIntervalSeconds: 60, AutoScanEnabled: true, @@ -85,7 +86,7 @@ func (h *SystemSettingsHandler) GetScanSettings(c *echo.Context) error { autoScanSetting, err := h.db.GetSystemSetting(c.Request().Context(), "auto_scan_enabled") if err != nil { - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { return c.JSON(http.StatusOK, ScanSettingsResponse{ ScanPollIntervalSeconds: 60, AutoScanEnabled: true, diff --git a/internal/middleware/error_handler.go b/internal/middleware/error_handler.go index 422fe9e..788d1e2 100644 --- a/internal/middleware/error_handler.go +++ b/internal/middleware/error_handler.go @@ -1,6 +1,7 @@ package middleware import ( + "errors" "fmt" "net/http" @@ -83,7 +84,7 @@ func WrapHandler(fn func(*echo.Context) error) echo.HandlerFunc { return func(c *echo.Context) error { err := fn(c) if err != nil { - if httpErr, ok := err.(*HTTPError); ok { + if httpErr, ok := errors.AsType[*HTTPError](err); ok { return RespondWithHTTPError(c, httpErr) } return RespondWithError(c, http.StatusInternalServerError, "Internal server error", err) diff --git a/internal/router/reader.go b/internal/router/reader.go index d71aa07..48cd442 100644 --- a/internal/router/reader.go +++ b/internal/router/reader.go @@ -6,6 +6,7 @@ import ( "bookhoard/internal/services" "bookhoard/templates" "bytes" + "errors" "fmt" "net/http" @@ -73,7 +74,7 @@ func registerReaderRoutes(cfg *Config) { MediaItemID: pgtype.UUID{Bytes: parsedUUID, Valid: true}, UserID: uuidToPGType(userUUID), }) - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { progress = database.ReadingProgress{} } // Get bookmarks diff --git a/internal/services/media_scanner.go b/internal/services/media_scanner.go index d31bbb6..7aee7b0 100644 --- a/internal/services/media_scanner.go +++ b/internal/services/media_scanner.go @@ -15,6 +15,7 @@ import ( "encoding/hex" "encoding/json" "encoding/xml" + "errors" "fmt" "image" _ "image/jpeg" @@ -590,7 +591,7 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool, fmt.Printf("Media item already exists with same size, skipping: %s\n", path) return false, nil } - } else if err != pgx.ErrNoRows { + } else if !errors.Is(err, pgx.ErrNoRows) { fmt.Printf("Database error checking media item existence: %v\n", err) return false, fmt.Errorf("failed to check if media item exists: %v", err) } @@ -2605,7 +2606,7 @@ func (s *MediaScanner) scanDirectory(ctx context.Context, dirPath string) { LibraryID: libraryID, }) - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { if _, err := s.processMediaFile(ctx, path); err != nil { s.errors++ } else { @@ -2780,7 +2781,7 @@ func (s *MediaScanner) SyncFilesystemWithDatabase(ctx context.Context) error { FilePath: relPath, LibraryID: libraryID, }) - if err == pgx.ErrNoRows { + if errors.Is(err, pgx.ErrNoRows) { // New file found - scan it absPath := folder + "/" + relPath if _, err := os.Stat(absPath); err == nil { @@ -2848,19 +2849,19 @@ func (s *MediaScanner) extractOPFIdentifiers(epubPath string) (opfIdentifier, op return "", "", "low", nil } - var identifier, uuid string + var identifier, uuidString string for _, id := range identifiers { id = strings.TrimSpace(id) // Check for UUID format (urn:uuid:) if trimmed, found := strings.CutPrefix(strings.ToLower(id), "urn:uuid:"); found { - uuid = trimmed + uuidString = trimmed continue } // Check if it's a plain UUID (8-4-4-4-12 format) if isValidUUID(id) { - uuid = id + uuidString = id continue } @@ -2879,9 +2880,9 @@ func (s *MediaScanner) extractOPFIdentifiers(epubPath string) (opfIdentifier, op } } - confidence = s.determineHashConfidence(uuid, identifier) + confidence = s.determineHashConfidence(uuidString, identifier) - return identifier, uuid, confidence, nil + return identifier, uuidString, confidence, nil } // isValidUUID checks if string is a valid UUID (8-4-4-4-12 format) diff --git a/internal/services/search.go b/internal/services/search.go index eea0afd..ced6f60 100644 --- a/internal/services/search.go +++ b/internal/services/search.go @@ -3,6 +3,7 @@ package services import ( "bookhoard/internal/database" "context" + "errors" "strings" "github.com/jackc/pgx/v5" @@ -214,7 +215,7 @@ func (s *SearchService) SearchFieldValues(ctx context.Context, params FieldSearc // Shared between JSON endpoint and HTML rendering func (s *SearchService) ExecuteSearch(ctx context.Context, params SearchParams) ([]database.SearchMediaItemsUnifiedRow, int, error) { results, err := s.SearchMediaItemsUnified(ctx, params) - if err != nil && err != pgx.ErrNoRows { + if err != nil && !errors.Is(err, pgx.ErrNoRows) { return nil, 0, err }