From 1c5290317265587ad14c515ae5739c364af115a9 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 28 Mar 2026 23:54:33 -0400 Subject: [PATCH] feat: add template helper functions for book detail page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add utility functions for rendering book metadata: - renderStars(): Convert rating (1-10 scale) to star display - formatFileSize(): Convert bytes to human-readable format (KB, MB, GB) - getExternalURL(): Generate URLs for external book services with smart fallback: ID → ISBN → Title+Author search Supports Goodreads, Open Library, Google Books, Amazon These helpers make book detail template cleaner and follow DRY principle. --- templates/utils.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/templates/utils.go b/templates/utils.go index 8293546..150c36f 100644 --- a/templates/utils.go +++ b/templates/utils.go @@ -1,6 +1,8 @@ package templates import ( + "fmt" + "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) @@ -32,3 +34,91 @@ func uuidToString(id pgtype.UUID) string { } return u.String() } + +// renderStars converts rating (1-10 scale) to star display +// Rating scale: 1-10 where odd numbers = half stars (1=0.5★, 3=1.5★, etc.) +func renderStars(rating int32) string { + stars := "" + fullStars := rating / 2 + hasHalf := rating%2 != 0 + + for i := int32(0); i < fullStars; i++ { + stars += "★" + } + if hasHalf { + stars += "½" + } + + return stars +} + +// formatFileSize converts bytes to human-readable format +func formatFileSize(bytes int64) string { + const ( + KB = 1024 + MB = KB * 1024 + GB = MB * 1024 + ) + + switch { + case bytes >= GB: + return fmt.Sprintf("%.2f GB", float64(bytes)/float64(GB)) + case bytes >= MB: + return fmt.Sprintf("%.2f MB", float64(bytes)/float64(MB)) + case bytes >= KB: + return fmt.Sprintf("%.2f KB", float64(bytes)/float64(KB)) + default: + return fmt.Sprintf("%d B", bytes) + } +} + +// getExternalURL generates URL for external book services +// Priority: ID > ISBN > Title+Author search +func getExternalURL(service string, id string, isbn pgtype.Text, title string, author pgtype.Text) string { + baseURL := "" + searchTerm := "" + + // Determine search term: ID > ISBN > Title+Author + if id != "" { + searchTerm = id + } else if isbn.Valid && isbn.String != "" { + searchTerm = isbn.String + } else { + // Build title+author search query + if author.Valid && author.String != "" { + searchTerm = fmt.Sprintf("%s %s", title, author.String) + } else { + searchTerm = title + } + } + + // Build URL based on service + switch service { + case "goodreads": + if id != "" { + baseURL = "https://www.goodreads.com/book/show/" + } else { + baseURL = "https://www.goodreads.com/search?q=" + } + case "openlibrary": + if id != "" { + baseURL = "https://openlibrary.org/books/" + } else { + baseURL = "https://openlibrary.org/search?q=" + } + case "googlebooks": + if id != "" { + baseURL = "https://books.google.com/books?id=" + } else { + baseURL = "https://www.google.com/search?tbm=bks&q=" + } + case "amazon": + // Amazon doesn't have direct book IDs, always search + baseURL = "https://www.amazon.com/s?k=" + if isbn.Valid && isbn.String != "" { + searchTerm = isbn.String + } + } + + return baseURL + searchTerm +}