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
@@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"bookhoard/internal/database"
|
||||
"bookhoard/internal/handlers"
|
||||
"bookhoard/internal/utils"
|
||||
"bookhoard/templates"
|
||||
"bytes"
|
||||
"context"
|
||||
@@ -213,7 +214,7 @@ func renderLibraryList(c *echo.Context, cfg *Config) error {
|
||||
libData[i] = templates.LibraryData{
|
||||
ID: libUUID.String(),
|
||||
Name: lib.Name,
|
||||
Description: getText(lib.Description),
|
||||
Description: utils.PgTextString(lib.Description),
|
||||
TypeName: lib.TypeName,
|
||||
TypeValue: lib.TypeName,
|
||||
FolderCount: folderCount,
|
||||
@@ -262,7 +263,7 @@ func renderLibraryPanelWithError(c *echo.Context, cfg *Config, libraryID pgtype.
|
||||
libData := templates.LibraryData{
|
||||
ID: libraryIDStr,
|
||||
Name: lib.Name,
|
||||
Description: getText(lib.Description),
|
||||
Description: utils.PgTextString(lib.Description),
|
||||
TypeName: lib.TypeName,
|
||||
TypeValue: lib.TypeName,
|
||||
}
|
||||
|
||||
+20
-20
@@ -552,9 +552,9 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
colData[i] = templates.CollectionData{
|
||||
ID: uuid.UUID(col.ID.Bytes).String(),
|
||||
Name: col.Name,
|
||||
Description: getText(col.Description),
|
||||
Color: getText(col.Color),
|
||||
Icon: getText(col.Icon),
|
||||
Description: utils.PgTextString(col.Description),
|
||||
Color: utils.PgTextString(col.Color),
|
||||
Icon: utils.PgTextString(col.Icon),
|
||||
IsSystem: col.IsSystemCollection.Bool,
|
||||
IsSmart: isSmart,
|
||||
BookCount: bookCount,
|
||||
@@ -598,9 +598,9 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
colData := templates.CollectionData{
|
||||
ID: uuid.UUID(collection.ID.Bytes).String(),
|
||||
Name: collection.Name,
|
||||
Description: getText(collection.Description),
|
||||
Color: getText(collection.Color),
|
||||
Icon: getText(collection.Icon),
|
||||
Description: utils.PgTextString(collection.Description),
|
||||
Color: utils.PgTextString(collection.Color),
|
||||
Icon: utils.PgTextString(collection.Icon),
|
||||
}
|
||||
|
||||
// Ensure default color if empty
|
||||
@@ -652,9 +652,9 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
colData := templates.CollectionData{
|
||||
ID: collectionID,
|
||||
Name: collection.Name,
|
||||
Description: getText(collection.Description),
|
||||
Color: getText(collection.Color),
|
||||
Icon: getText(collection.Icon),
|
||||
Description: utils.PgTextString(collection.Description),
|
||||
Color: utils.PgTextString(collection.Color),
|
||||
Icon: utils.PgTextString(collection.Icon),
|
||||
IsSystem: collection.IsSystemCollection.Bool,
|
||||
IsSmart: cfg.CollectionHandler.CollectionService().IsSmartCollection(collection.AutoAssignRules),
|
||||
}
|
||||
@@ -861,7 +861,7 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
libData[i] = templates.LibraryData{
|
||||
ID: libUUID.String(),
|
||||
Name: lib.Name,
|
||||
Description: getText(lib.Description),
|
||||
Description: utils.PgTextString(lib.Description),
|
||||
TypeName: lib.TypeName,
|
||||
}
|
||||
}
|
||||
@@ -1056,7 +1056,7 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
item := templates.ArchivedItem{
|
||||
ID: itemUUID.String(),
|
||||
Title: row.Title,
|
||||
Author: getText(row.Author),
|
||||
Author: utils.PgTextString(row.Author),
|
||||
LibraryName: row.LibraryName,
|
||||
FilePath: row.FilePath,
|
||||
MissingScans: row.MissingScanCount,
|
||||
@@ -1064,7 +1064,7 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
if row.MatchID.Valid {
|
||||
item.MatchID = uuid.UUID(row.MatchID.Bytes).String()
|
||||
item.MatchTitle = row.MatchTitle
|
||||
item.MatchLibraryName = getText(row.MatchLibraryName)
|
||||
item.MatchLibraryName = utils.PgTextString(row.MatchLibraryName)
|
||||
}
|
||||
if row.ArchivedAt.Valid {
|
||||
archived := row.ArchivedAt.Time
|
||||
@@ -1111,7 +1111,7 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
libData[i] = templates.LibraryData{
|
||||
ID: libUUID.String(),
|
||||
Name: lib.Name,
|
||||
Description: getText(lib.Description),
|
||||
Description: utils.PgTextString(lib.Description),
|
||||
TypeName: lib.TypeName,
|
||||
TypeValue: lib.TypeName,
|
||||
FolderCount: len(folders),
|
||||
@@ -1292,9 +1292,9 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
Username: u.Username,
|
||||
Email: u.Email,
|
||||
Role: u.Role,
|
||||
Theme: getText(u.Theme),
|
||||
FirstName: getText(u.FirstName),
|
||||
LastName: getText(u.LastName),
|
||||
Theme: utils.PgTextString(u.Theme),
|
||||
FirstName: utils.PgTextString(u.FirstName),
|
||||
LastName: utils.PgTextString(u.LastName),
|
||||
CreatedAt: u.CreatedAt.Time,
|
||||
}
|
||||
}
|
||||
@@ -1401,13 +1401,13 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
Username: targetUser.Username,
|
||||
Email: targetUser.Email,
|
||||
Role: targetUser.Role,
|
||||
Theme: getText(targetUser.Theme),
|
||||
FirstName: getText(targetUser.FirstName),
|
||||
LastName: getText(targetUser.LastName),
|
||||
Theme: utils.PgTextString(targetUser.Theme),
|
||||
FirstName: utils.PgTextString(targetUser.FirstName),
|
||||
LastName: utils.PgTextString(targetUser.LastName),
|
||||
CreatedAt: targetUser.CreatedAt.Time,
|
||||
// Without this the timezone select rendered UTC selected for
|
||||
// EVERY user — saves landed, the modal just never showed them.
|
||||
Timezone: getText(targetUser.Timezone),
|
||||
Timezone: utils.PgTextString(targetUser.Timezone),
|
||||
}
|
||||
|
||||
// Render modal
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"bookhoard/internal/database"
|
||||
"bookhoard/internal/utils"
|
||||
"bookhoard/templates"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -60,8 +61,8 @@ func getTemplateUserWithTheme(c *echo.Context, cfg *Config) (templates.User, err
|
||||
Theme: userTheme,
|
||||
Token: token,
|
||||
Timezone: userTimezone,
|
||||
FirstName: getText(userDB.FirstName),
|
||||
LastName: getText(userDB.LastName),
|
||||
FirstName: utils.PgTextString(userDB.FirstName),
|
||||
LastName: utils.PgTextString(userDB.LastName),
|
||||
CreatedAt: userDB.CreatedAt.Time,
|
||||
}, nil
|
||||
}
|
||||
@@ -135,13 +136,6 @@ func folderReachableCached(path string) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
func getText(t pgtype.Text) string {
|
||||
if t.Valid {
|
||||
return t.String
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func resolveLibrary(c *echo.Context, cfg *Config, userUUID string) LibraryResolution {
|
||||
res := LibraryResolution{}
|
||||
|
||||
@@ -195,7 +189,7 @@ func resolveLibrary(c *echo.Context, cfg *Config, userUUID string) LibraryResolu
|
||||
res.Libraries[i] = templates.LibraryData{
|
||||
ID: libUUID.String(),
|
||||
Name: lib.Name,
|
||||
Description: getText(lib.Description),
|
||||
Description: utils.PgTextString(lib.Description),
|
||||
TypeName: lib.TypeName,
|
||||
TypeValue: lib.TypeName,
|
||||
MediaCount: countMap[libUUID.String()],
|
||||
|
||||
@@ -80,21 +80,21 @@ func registerReaderRoutes(cfg *Config) {
|
||||
mediaUUID, _ := uuid.FromBytes(mediaItem.ID.Bytes[0:16])
|
||||
libUUID, _ := uuid.FromBytes(mediaItem.LibraryID.Bytes[0:16])
|
||||
metadata := templates.ReaderMetadata{
|
||||
MediaItemID: mediaUUID.String(),
|
||||
Title: mediaItem.Title,
|
||||
Author: textToString(mediaItem.Author),
|
||||
MediaItemID: mediaUUID.String(),
|
||||
Title: mediaItem.Title,
|
||||
Author: utils.PgTextString(mediaItem.Author),
|
||||
// Sidecar covers store a raw library-relative path; resolve to
|
||||
// the cookie-authed /uploads URL (idempotent for items whose
|
||||
// stored value is already a servable path).
|
||||
CoverImagePath: utils.ResolveMediaURL(mediaItem.LibraryID, mediaItem.CoverImagePath),
|
||||
LibraryType: mediaItem.FormatGroup,
|
||||
MimeType: textToString(mediaItem.MimeType),
|
||||
MimeType: utils.PgTextString(mediaItem.MimeType),
|
||||
FilePath: mediaItem.FilePath,
|
||||
TotalPages: int(mediaItem.PageCount.Int32),
|
||||
ChapterCount: int(mediaItem.ChapterCount.Int32),
|
||||
FormatGroup: mediaItem.FormatGroup,
|
||||
MangaType: textToString(mediaItem.MangaType),
|
||||
ReadingDirection: textToString(mediaItem.ReadingDirection),
|
||||
MangaType: utils.PgTextString(mediaItem.MangaType),
|
||||
ReadingDirection: utils.PgTextString(mediaItem.ReadingDirection),
|
||||
LibraryID: libUUID.String(),
|
||||
FileURL: utils.ResolveMediaURL(mediaItem.LibraryID, pgtype.Text{String: mediaItem.FilePath, Valid: true}),
|
||||
TotalCharacters: mediaItem.TotalCharacters.Int64,
|
||||
|
||||
@@ -116,11 +116,3 @@ func handleSearchHTML(c *echo.Context, cfg *Config) error {
|
||||
}
|
||||
return c.HTML(http.StatusOK, buf.String())
|
||||
}
|
||||
|
||||
// textToString converts pgtype.Text to string
|
||||
func textToString(t pgtype.Text) string {
|
||||
if t.Valid {
|
||||
return t.String
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in new issue
Block a user