refactor(utils): one PgTextString for the four pgtype.Text unwrappers
handlers textToString, router textToString, router getText, and utils normText were four copies of the same NULL-to-empty unwrap (99 call sites). One exported utils.PgTextString replaces them all; the private normText (comparison normalizer) stays but is now the odd one out. Harness fix found on the way: setupTestServer built a full wiring literal for route registration and then RETURNED a second, sparser literal that never copied AnnotationService — setup.AnnotationService was always nil on the struct every test receives. Nobody noticed until cross_library_move_test became the first caller (it previously used the now-retired plain CreateMediaHighlight query); it now exercises the real SaveHighlight path through the wired service.
This commit is contained in:
1 parent
00305ffc7b
commit
188ae076ab
15 files changed
+140
-139
No files matched your search
@@ -114,7 +114,7 @@ func BookCardsFromMediaItems(rows []database.MediaItems, libTypes map[string]str
|
||||
cards = append(cards, buildBookCard(cardSource{
|
||||
ID: uuid.UUID(r.ID.Bytes),
|
||||
Title: r.Title,
|
||||
Author: textToString(r.Author),
|
||||
Author: utils.PgTextString(r.Author),
|
||||
LibraryID: r.LibraryID,
|
||||
CoverPath: r.CoverImagePath,
|
||||
FormatGroup: r.FormatGroup,
|
||||
@@ -134,7 +134,7 @@ func BookCardsFromCollectionDashRows(rows []database.GetCollectionItemsForDashbo
|
||||
cards = append(cards, buildBookCard(cardSource{
|
||||
ID: uuid.UUID(r.ID.Bytes),
|
||||
Title: r.Title,
|
||||
Author: textToString(r.Author),
|
||||
Author: utils.PgTextString(r.Author),
|
||||
LibraryID: r.LibraryID,
|
||||
CoverPath: r.CoverImagePath,
|
||||
FormatGroup: r.FormatGroup,
|
||||
@@ -154,7 +154,7 @@ func BookCardsFromCollectionItemRows(rows []database.GetCollectionItemsRow, libT
|
||||
cards = append(cards, buildBookCard(cardSource{
|
||||
ID: uuid.UUID(r.MediaItemID.Bytes),
|
||||
Title: r.Title,
|
||||
Author: textToString(r.Author),
|
||||
Author: utils.PgTextString(r.Author),
|
||||
LibraryID: r.LibraryID,
|
||||
CoverPath: r.CoverImagePath,
|
||||
FormatGroup: r.FormatGroup,
|
||||
@@ -173,7 +173,7 @@ func BookCardsFromUnified(rows []database.SearchMediaItemsUnifiedRow) []BookInfo
|
||||
cards = append(cards, buildBookCard(cardSource{
|
||||
ID: uuid.UUID(r.ID.Bytes),
|
||||
Title: r.Title,
|
||||
Author: textToString(r.Author),
|
||||
Author: utils.PgTextString(r.Author),
|
||||
LibraryID: r.LibraryID,
|
||||
CoverPath: r.CoverImagePath,
|
||||
FormatGroup: r.FormatGroup,
|
||||
|
||||
@@ -116,9 +116,9 @@ func (h *CollectionHandler) CreateCollection(c *echo.Context) error {
|
||||
"id": uuid.UUID(collection.ID.Bytes).String(),
|
||||
"user_id": uuid.UUID(collection.UserID.Bytes).String(),
|
||||
"name": collection.Name,
|
||||
"description": textToString(collection.Description),
|
||||
"color": textToString(collection.Color),
|
||||
"icon": textToString(collection.Icon),
|
||||
"description": utils.PgTextString(collection.Description),
|
||||
"color": utils.PgTextString(collection.Color),
|
||||
"icon": utils.PgTextString(collection.Icon),
|
||||
"collection_type": map[bool]string{true: "smart", false: "static"}[isSmart],
|
||||
"auto_assign_rules": collection.AutoAssignRules,
|
||||
"view_settings": collection.ViewSettings,
|
||||
@@ -204,9 +204,9 @@ func (h *CollectionHandler) GetCollections(c *echo.Context) error {
|
||||
response = append(response, CollectionResponse{
|
||||
ID: col.ID.Bytes,
|
||||
Name: col.Name,
|
||||
Description: textToString(col.Description),
|
||||
Color: textToString(col.Color),
|
||||
Icon: textToString(col.Icon),
|
||||
Description: utils.PgTextString(col.Description),
|
||||
Color: utils.PgTextString(col.Color),
|
||||
Icon: utils.PgTextString(col.Icon),
|
||||
AutoAssignRules: col.AutoAssignRules,
|
||||
CreatedAt: col.CreatedAt.Time.String(),
|
||||
BookCount: bookCount,
|
||||
@@ -325,9 +325,9 @@ func (h *CollectionHandler) GetCollection(c *echo.Context) error {
|
||||
"id": uuid.UUID(collection.ID.Bytes).String(),
|
||||
"user_id": uuid.UUID(collection.UserID.Bytes).String(),
|
||||
"name": collection.Name,
|
||||
"description": textToString(collection.Description),
|
||||
"color": textToString(collection.Color),
|
||||
"icon": textToString(collection.Icon),
|
||||
"description": utils.PgTextString(collection.Description),
|
||||
"color": utils.PgTextString(collection.Color),
|
||||
"icon": utils.PgTextString(collection.Icon),
|
||||
"auto_assign_rules": json.RawMessage(collection.AutoAssignRules),
|
||||
"view_settings": viewSettings,
|
||||
"books": bookList,
|
||||
@@ -369,9 +369,9 @@ func (h *CollectionHandler) UpdateCollection(c *echo.Context) error {
|
||||
"id": uuid.UUID(collection.ID.Bytes).String(),
|
||||
"user_id": uuid.UUID(collection.UserID.Bytes).String(),
|
||||
"name": collection.Name,
|
||||
"description": textToString(collection.Description),
|
||||
"color": textToString(collection.Color),
|
||||
"icon": textToString(collection.Icon),
|
||||
"description": utils.PgTextString(collection.Description),
|
||||
"color": utils.PgTextString(collection.Color),
|
||||
"icon": utils.PgTextString(collection.Icon),
|
||||
"auto_assign_rules": json.RawMessage(collection.AutoAssignRules),
|
||||
"view_settings": json.RawMessage(collection.ViewSettings),
|
||||
"created_at": collection.CreatedAt.Time.String(),
|
||||
@@ -580,8 +580,8 @@ func (h *CollectionHandler) GetDeviceMappings(c *echo.Context) error {
|
||||
ID: m.ID.Bytes,
|
||||
CollectionID: m.CollectionID.Bytes,
|
||||
CollectionName: m.CollectionName,
|
||||
DeviceShelfName: textToString(m.DeviceShelfName),
|
||||
SyncDirection: textToString(m.SyncDirection),
|
||||
DeviceShelfName: utils.PgTextString(m.DeviceShelfName),
|
||||
SyncDirection: utils.PgTextString(m.SyncDirection),
|
||||
CreatedAt: m.CreatedAt.Time.String(),
|
||||
})
|
||||
}
|
||||
@@ -627,8 +627,8 @@ func (h *CollectionHandler) CreateDeviceMapping(c *echo.Context) error {
|
||||
"id": uuid.UUID(mapping.ID.Bytes).String(),
|
||||
"collection_id": uuid.UUID(mapping.CollectionID.Bytes).String(),
|
||||
"device_id": uuid.UUID(mapping.DeviceID.Bytes).String(),
|
||||
"device_shelf_name": textToString(mapping.DeviceShelfName),
|
||||
"sync_direction": textToString(mapping.SyncDirection),
|
||||
"device_shelf_name": utils.PgTextString(mapping.DeviceShelfName),
|
||||
"sync_direction": utils.PgTextString(mapping.SyncDirection),
|
||||
"created_at": mapping.CreatedAt.Time.String(),
|
||||
})
|
||||
}
|
||||
@@ -661,8 +661,8 @@ func (h *CollectionHandler) UpdateDeviceMapping(c *echo.Context) error {
|
||||
"id": uuid.UUID(mapping.ID.Bytes).String(),
|
||||
"collection_id": uuid.UUID(mapping.CollectionID.Bytes).String(),
|
||||
"device_id": uuid.UUID(mapping.DeviceID.Bytes).String(),
|
||||
"device_shelf_name": textToString(mapping.DeviceShelfName),
|
||||
"sync_direction": textToString(mapping.SyncDirection),
|
||||
"device_shelf_name": utils.PgTextString(mapping.DeviceShelfName),
|
||||
"sync_direction": utils.PgTextString(mapping.SyncDirection),
|
||||
"created_at": mapping.CreatedAt.Time.String(),
|
||||
})
|
||||
}
|
||||
@@ -705,9 +705,9 @@ func (h *CollectionHandler) GetBookCollections(c *echo.Context) error {
|
||||
response = append(response, CollectionResponse{
|
||||
ID: col.ID.Bytes,
|
||||
Name: col.Name,
|
||||
Description: textToString(col.Description),
|
||||
Color: textToString(col.Color),
|
||||
Icon: textToString(col.Icon),
|
||||
Description: utils.PgTextString(col.Description),
|
||||
Color: utils.PgTextString(col.Color),
|
||||
Icon: utils.PgTextString(col.Icon),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -718,13 +718,6 @@ func (h *CollectionHandler) GetBookCollections(c *echo.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
func textToString(t pgtype.Text) string {
|
||||
if t.Valid {
|
||||
return t.String
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *CollectionHandler) GetCollectionsData(c *echo.Context) ([]database.Collections, error) {
|
||||
user := c.Get("user").(database.Users)
|
||||
userUUID := uuid.UUID(user.ID.Bytes)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"bookhoard/internal/database"
|
||||
"bookhoard/internal/utils"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -103,7 +104,7 @@ func TestTextToString(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := textToString(tt.input)
|
||||
result := utils.PgTextString(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"bookhoard/internal/database"
|
||||
"bookhoard/internal/services"
|
||||
"bookhoard/internal/utils"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
@@ -128,7 +129,7 @@ func TestTextToString_Valid(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := textToString(tt.input)
|
||||
result := utils.PgTextString(tt.input)
|
||||
assert.Equal(t, tt.expected, result, "textToString result mismatch")
|
||||
})
|
||||
}
|
||||
|
||||
+39
-39
@@ -815,44 +815,44 @@ func (mh *MediaHandler) ListMediaItems(c *echo.Context) error {
|
||||
"id": uuid.UUID(item.ID.Bytes).String(),
|
||||
"library_id": uuid.UUID(item.LibraryID.Bytes).String(),
|
||||
"title": item.Title,
|
||||
"author": textToString(item.Author),
|
||||
"isbn": textToString(item.Isbn),
|
||||
"description": textToString(item.Description),
|
||||
"author": utils.PgTextString(item.Author),
|
||||
"isbn": utils.PgTextString(item.Isbn),
|
||||
"description": utils.PgTextString(item.Description),
|
||||
"file_path": utils.ResolveMediaURL(item.LibraryID, pgtype.Text{String: item.FilePath, Valid: item.FilePath != ""}),
|
||||
"file_size": item.FileSize,
|
||||
"mime_type": textToString(item.MimeType),
|
||||
"mime_type": utils.PgTextString(item.MimeType),
|
||||
"cover_image_path": utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
|
||||
"series": textToString(item.Series),
|
||||
"series": utils.PgTextString(item.Series),
|
||||
"series_number": item.SeriesNumber,
|
||||
"tags": item.Tags,
|
||||
"asin": textToString(item.Asin),
|
||||
"asin": utils.PgTextString(item.Asin),
|
||||
"date_published": item.DatePublished.Time.Format("2006-01-02"),
|
||||
"publisher": textToString(item.Publisher),
|
||||
"publisher": utils.PgTextString(item.Publisher),
|
||||
"contributors": item.Contributors,
|
||||
"language": textToString(item.Language),
|
||||
"edition": textToString(item.Edition),
|
||||
"language": utils.PgTextString(item.Language),
|
||||
"edition": utils.PgTextString(item.Edition),
|
||||
"page_count": item.PageCount,
|
||||
"genre": textToString(item.Genre),
|
||||
"genre": utils.PgTextString(item.Genre),
|
||||
"created_at": item.CreatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
|
||||
"updated_at": item.UpdatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
|
||||
"format_group": item.FormatGroup, // already string
|
||||
// NEW: Reading direction fields
|
||||
"manga_type": textToString(item.MangaType),
|
||||
"reading_direction": textToString(item.ReadingDirection),
|
||||
"manga_type": utils.PgTextString(item.MangaType),
|
||||
"reading_direction": utils.PgTextString(item.ReadingDirection),
|
||||
// NEW: Universal metadata fields
|
||||
"series_count": item.SeriesCount,
|
||||
"volume": item.Volume,
|
||||
"imprint": textToString(item.Imprint),
|
||||
"age_rating": textToString(item.AgeRating),
|
||||
"web_url": textToString(item.WebUrl),
|
||||
"metadata_notes": textToString(item.MetadataNotes),
|
||||
"imprint": utils.PgTextString(item.Imprint),
|
||||
"age_rating": utils.PgTextString(item.AgeRating),
|
||||
"web_url": utils.PgTextString(item.WebUrl),
|
||||
"metadata_notes": utils.PgTextString(item.MetadataNotes),
|
||||
"community_rating": numericToFloat(item.CommunityRating),
|
||||
// NEW: Comic-specific fields
|
||||
"story_arc": textToString(item.StoryArc),
|
||||
"story_arc": utils.PgTextString(item.StoryArc),
|
||||
"is_black_and_white": item.IsBlackAndWhite,
|
||||
"alternate_info": jsonBytesToMap(item.AlternateInfo),
|
||||
"scan_information": textToString(item.ScanInformation),
|
||||
"summary": textToString(item.Summary),
|
||||
"scan_information": utils.PgTextString(item.ScanInformation),
|
||||
"summary": utils.PgTextString(item.Summary),
|
||||
}
|
||||
}
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": resolvedItems})
|
||||
@@ -887,47 +887,47 @@ func (mh *MediaHandler) GetMediaItem(c *echo.Context) error {
|
||||
"id": uuid.UUID(item.ID.Bytes).String(),
|
||||
"library_id": uuid.UUID(item.LibraryID.Bytes).String(),
|
||||
"title": item.Title,
|
||||
"author": textToString(item.Author),
|
||||
"isbn": textToString(item.Isbn),
|
||||
"description": textToString(item.Description),
|
||||
"author": utils.PgTextString(item.Author),
|
||||
"isbn": utils.PgTextString(item.Isbn),
|
||||
"description": utils.PgTextString(item.Description),
|
||||
"file_path": utils.ResolveMediaURL(item.LibraryID, pgtype.Text{String: item.FilePath, Valid: item.FilePath != ""}),
|
||||
"file_size": item.FileSize,
|
||||
"mime_type": textToString(item.MimeType),
|
||||
"mime_type": utils.PgTextString(item.MimeType),
|
||||
"cover_image_path": utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
|
||||
"series": textToString(item.Series),
|
||||
"series": utils.PgTextString(item.Series),
|
||||
"series_number": item.SeriesNumber,
|
||||
"tags": item.Tags,
|
||||
"asin": textToString(item.Asin),
|
||||
"asin": utils.PgTextString(item.Asin),
|
||||
"date_published": item.DatePublished.Time.Format("2006-01-02"),
|
||||
"publisher": textToString(item.Publisher),
|
||||
"publisher": utils.PgTextString(item.Publisher),
|
||||
"contributors": item.Contributors,
|
||||
"language": textToString(item.Language),
|
||||
"edition": textToString(item.Edition),
|
||||
"language": utils.PgTextString(item.Language),
|
||||
"edition": utils.PgTextString(item.Edition),
|
||||
"page_count": item.PageCount,
|
||||
"genre": textToString(item.Genre),
|
||||
"genre": utils.PgTextString(item.Genre),
|
||||
"created_at": item.CreatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
|
||||
"updated_at": item.UpdatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
|
||||
"format_group": item.FormatGroup,
|
||||
"format_mimetype": textToString(item.FormatMimetype),
|
||||
"format_mimetype": utils.PgTextString(item.FormatMimetype),
|
||||
"narrators": item.Narrators,
|
||||
"abridged": item.Abridged,
|
||||
// NEW: Reading direction fields
|
||||
"manga_type": textToString(item.MangaType),
|
||||
"reading_direction": textToString(item.ReadingDirection),
|
||||
"manga_type": utils.PgTextString(item.MangaType),
|
||||
"reading_direction": utils.PgTextString(item.ReadingDirection),
|
||||
// NEW: Universal metadata fields
|
||||
"series_count": item.SeriesCount,
|
||||
"volume": item.Volume,
|
||||
"imprint": textToString(item.Imprint),
|
||||
"age_rating": textToString(item.AgeRating),
|
||||
"web_url": textToString(item.WebUrl),
|
||||
"metadata_notes": textToString(item.MetadataNotes),
|
||||
"imprint": utils.PgTextString(item.Imprint),
|
||||
"age_rating": utils.PgTextString(item.AgeRating),
|
||||
"web_url": utils.PgTextString(item.WebUrl),
|
||||
"metadata_notes": utils.PgTextString(item.MetadataNotes),
|
||||
"community_rating": numericToFloat(item.CommunityRating),
|
||||
// NEW: Comic-specific fields
|
||||
"story_arc": textToString(item.StoryArc),
|
||||
"story_arc": utils.PgTextString(item.StoryArc),
|
||||
"is_black_and_white": item.IsBlackAndWhite,
|
||||
"alternate_info": jsonBytesToMap(item.AlternateInfo),
|
||||
"scan_information": textToString(item.ScanInformation),
|
||||
"summary": textToString(item.Summary),
|
||||
"scan_information": utils.PgTextString(item.ScanInformation),
|
||||
"summary": utils.PgTextString(item.Summary),
|
||||
"library_type_name": textPtrToString(item.LibraryTypeName),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package handlers
|
||||
import (
|
||||
"bookhoard/internal/database"
|
||||
"bookhoard/internal/services"
|
||||
"bookhoard/internal/utils"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -124,10 +125,10 @@ func (h *ReaderHandler) ShowReader(c *echo.Context) error {
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"media_item_id": mediaItemID,
|
||||
"title": mediaItem.Title,
|
||||
"author": textToString(mediaItem.Author),
|
||||
"cover_image_path": textToString(mediaItem.CoverImagePath),
|
||||
"author": utils.PgTextString(mediaItem.Author),
|
||||
"cover_image_path": utils.PgTextString(mediaItem.CoverImagePath),
|
||||
"library_type": mediaItem.FormatGroup,
|
||||
"mime_type": textToString(mediaItem.MimeType),
|
||||
"mime_type": utils.PgTextString(mediaItem.MimeType),
|
||||
"file_path": mediaItem.FilePath,
|
||||
"total_pages": mediaItem.PageCount,
|
||||
"chapter_count": mediaItem.ChapterCount,
|
||||
|
||||
@@ -3,6 +3,8 @@ package handlers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"bookhoard/internal/utils"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -38,7 +40,7 @@ func TestSeriesHandler_TextToStringConversion(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := textToString(tt.input)
|
||||
result := utils.PgTextString(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user