From 35c8ffe33e14be76374368a9aa8a4f33815a3028 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Thu, 23 Apr 2026 20:39:36 -0400 Subject: [PATCH] fix(reader): URL-encode file paths and JSON-encode init config to fix comics/manga loading The reader failed to load comics and manga (and any file with special characters in its path) for two reasons: 1. FileURL was built with raw fmt.Sprintf instead of ResolveMediaURL, so characters like '#' in paths (e.g. 'Annual #2') were interpreted as URL fragments, truncating the path and causing 404s. 2. The Alpine x-init expression used raw string interpolation for config values, so apostrophes in paths (e.g. "I'll Use My Appraisal Skill") broke JavaScript parsing with 'Unexpected identifier'. Fix by using utils.ResolveMediaURL for proper URL path encoding and json.Marshal for the initReader config to safely escape all special characters. --- internal/router/reader.go | 10 ++++++---- templates/reader.templ | 19 +++++++++++++++++-- templates/reader_templ.go | 35 +++++++++++++++++++++++++---------- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/internal/router/reader.go b/internal/router/reader.go index 48cd442..952085c 100644 --- a/internal/router/reader.go +++ b/internal/router/reader.go @@ -4,10 +4,10 @@ import ( "bookhoard/internal/database" "bookhoard/internal/handlers" "bookhoard/internal/services" + "bookhoard/internal/utils" "bookhoard/templates" "bytes" "errors" - "fmt" "net/http" "github.com/google/uuid" @@ -99,7 +99,7 @@ func registerReaderRoutes(cfg *Config) { MangaType: textToString(mediaItem.MangaType), ReadingDirection: textToString(mediaItem.ReadingDirection), LibraryID: libUUID.String(), - FileURL: fmt.Sprintf("/uploads/library-%s/%s", libUUID.String(), mediaItem.FilePath), + FileURL: utils.ResolveMediaURL(mediaItem.LibraryID, pgtype.Text{String: mediaItem.FilePath, Valid: true}), } // Progress conversion (inline) progressUUID, _ := uuid.FromBytes(progress.ID.Bytes[0:16]) @@ -124,12 +124,14 @@ func registerReaderRoutes(cfg *Config) { var pageNumber *int if b.PageNumber.Valid { - pageNumber = new(int(b.PageNumber.Int32)) + val := int(b.PageNumber.Int32) + pageNumber = &val } var chapterNumber *int if b.ChapterNumber.Valid { - chapterNumber = new(int(b.ChapterNumber.Int32)) + val := int(b.ChapterNumber.Int32) + chapterNumber = &val } templateBookmarks[i] = templates.Bookmark{ diff --git a/templates/reader.templ b/templates/reader.templ index ee4f369..9f4f9b0 100644 --- a/templates/reader.templ +++ b/templates/reader.templ @@ -1,6 +1,21 @@ package templates -import "fmt" +import ( + "encoding/json" + "fmt" +) + +func readerInitExpr(metadata ReaderMetadata) string { + config := map[string]string{ + "mediaItemId": metadata.MediaItemID, + "fileUrl": metadata.FileURL, + "formatGroup": metadata.FormatGroup, + "readingDirection": metadata.ReadingDirection, + "mangaType": metadata.MangaType, + } + jsonBytes, _ := json.Marshal(config) + return fmt.Sprintf("initReader(%s)", string(jsonBytes)) +} templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookmarks []Bookmark) { @@ -17,7 +32,7 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm @ReaderChrome(user, metadata, progress) diff --git a/templates/reader_templ.go b/templates/reader_templ.go index 1aa6215..78c3c8d 100644 --- a/templates/reader_templ.go +++ b/templates/reader_templ.go @@ -8,7 +8,22 @@ package templates import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -import "fmt" +import ( + "encoding/json" + "fmt" +) + +func readerInitExpr(metadata ReaderMetadata) string { + config := map[string]string{ + "mediaItemId": metadata.MediaItemID, + "fileUrl": metadata.FileURL, + "formatGroup": metadata.FormatGroup, + "readingDirection": metadata.ReadingDirection, + "mangaType": metadata.MangaType, + } + jsonBytes, _ := json.Marshal(config) + return fmt.Sprintf("initReader(%s)", string(jsonBytes)) +} func Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookmarks []Bookmark) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { @@ -38,7 +53,7 @@ func Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookma var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 11, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 26, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -49,9 +64,9 @@ func Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookma return templ_7745c5c3_Err } var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`initReader({mediaItemId:'%s',fileUrl:'%s',formatGroup:'%s',readingDirection:'%s',mangaType:'%s'})`, metadata.MediaItemID, metadata.FileURL, metadata.FormatGroup, metadata.ReadingDirection, metadata.MangaType)) + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(readerInitExpr(metadata)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 20, Col: 233} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 35, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -141,7 +156,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress) var templ_7745c5c3_Var5 templ.SafeURL templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs("/media/" + metadata.MediaItemID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 58, Col: 46} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 73, Col: 46} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -154,7 +169,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress) var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 61, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 76, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -167,7 +182,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress) var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 137, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 152, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -306,7 +321,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.CfiPosition) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 353, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 368, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -319,7 +334,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 356, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 371, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -332,7 +347,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Position) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 358, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 373, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil {