feat(filters+nav): author navigation end-to-end — arrays on every media response, deep-linkable bookshelf filters, clickable authors
Completes the A2c acceptance scenario: click an author on a book detail page -> land on the bookshelf filtered to everything that author wrote (including their share of co-authored books). - Media detail + unscoped list resolved maps expose subtitle, explicit, authors, publishers (the library-scoped raw branch and the search endpoint already did — raw rows follow the SELECT). - SearchMediaItemsUnified SELECT carries the new columns; the search endpoint serializes the row directly, so clients get them there too. - book_detail.templ renders each author as a link to /bookshelf?author_filter=<name>. - Bookshelf honors filter params in the URL on load (author_filter, series_filter, genre_filter, publisher_filter, tags_filter, narrator_filter, language_filter): pre-fills the form, applies it to the first grid load. Comma-joined values are multi-select via the server-side string_to_array split. Filtered views are shareable. Pins widened: search rows (+7 keys), resolved keys (+4); changelog entries in docs/contract_pins.md. Android follow-up plan written to ~/.opencode/plan/bookhoard-app-v2-compat.md.
This commit is contained in:
1 parent
f94e7472b4
commit
7537063cb7
8 files changed
+851
-728
No files matched your search
@@ -13201,7 +13201,14 @@ SELECT
|
||||
mi.tags_search,
|
||||
mi.contributors_search,
|
||||
mi.narrators,
|
||||
mi.narrators_search,
|
||||
mi.abridged,
|
||||
mi.subtitle,
|
||||
mi.explicit,
|
||||
mi.authors,
|
||||
mi.authors_search,
|
||||
mi.publishers,
|
||||
mi.publishers_search,
|
||||
mi.audio_metadata,
|
||||
mi.file_sha256,
|
||||
mi.opf_identifier,
|
||||
@@ -13429,7 +13436,14 @@ type SearchMediaItemsUnifiedRow struct {
|
||||
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
||||
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
|
||||
Narrators []string `db:"narrators" json:"narrators"`
|
||||
NarratorsSearch []string `db:"narrators_search" json:"narrators_search"`
|
||||
Abridged bool `db:"abridged" json:"abridged"`
|
||||
Subtitle pgtype.Text `db:"subtitle" json:"subtitle"`
|
||||
Explicit bool `db:"explicit" json:"explicit"`
|
||||
Authors []string `db:"authors" json:"authors"`
|
||||
AuthorsSearch []string `db:"authors_search" json:"authors_search"`
|
||||
Publishers []string `db:"publishers" json:"publishers"`
|
||||
PublishersSearch []string `db:"publishers_search" json:"publishers_search"`
|
||||
AudioMetadata []byte `db:"audio_metadata" json:"audio_metadata"`
|
||||
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
||||
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
||||
@@ -13508,7 +13522,14 @@ func (q *Queries) SearchMediaItemsUnified(ctx context.Context, arg SearchMediaIt
|
||||
&i.TagsSearch,
|
||||
&i.ContributorsSearch,
|
||||
&i.Narrators,
|
||||
&i.NarratorsSearch,
|
||||
&i.Abridged,
|
||||
&i.Subtitle,
|
||||
&i.Explicit,
|
||||
&i.Authors,
|
||||
&i.AuthorsSearch,
|
||||
&i.Publishers,
|
||||
&i.PublishersSearch,
|
||||
&i.AudioMetadata,
|
||||
&i.FileSha256,
|
||||
&i.OpfIdentifier,
|
||||
|
||||
@@ -662,7 +662,14 @@ SELECT
|
||||
mi.tags_search,
|
||||
mi.contributors_search,
|
||||
mi.narrators,
|
||||
mi.narrators_search,
|
||||
mi.abridged,
|
||||
mi.subtitle,
|
||||
mi.explicit,
|
||||
mi.authors,
|
||||
mi.authors_search,
|
||||
mi.publishers,
|
||||
mi.publishers_search,
|
||||
mi.audio_metadata,
|
||||
mi.file_sha256,
|
||||
mi.opf_identifier,
|
||||
|
||||
@@ -844,7 +844,9 @@ 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,
|
||||
"subtitle": utils.PgTextString(item.Subtitle),
|
||||
"author": utils.PgTextString(item.Author),
|
||||
"authors": item.Authors,
|
||||
"isbn": utils.PgTextString(item.Isbn),
|
||||
"description": utils.PgTextString(item.Description),
|
||||
"file_path": utils.ResolveMediaURL(item.LibraryID, pgtype.Text{String: item.FilePath, Valid: item.FilePath != ""}),
|
||||
@@ -857,6 +859,8 @@ func (mh *MediaHandler) ListMediaItems(c *echo.Context) error {
|
||||
"asin": utils.PgTextString(item.Asin),
|
||||
"date_published": item.DatePublished.Time.Format("2006-01-02"),
|
||||
"publisher": utils.PgTextString(item.Publisher),
|
||||
"publishers": item.Publishers,
|
||||
"explicit": item.Explicit,
|
||||
"contributors": item.Contributors,
|
||||
"language": utils.PgTextString(item.Language),
|
||||
"edition": utils.PgTextString(item.Edition),
|
||||
@@ -916,7 +920,9 @@ 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,
|
||||
"subtitle": utils.PgTextString(item.Subtitle),
|
||||
"author": utils.PgTextString(item.Author),
|
||||
"authors": item.Authors,
|
||||
"isbn": utils.PgTextString(item.Isbn),
|
||||
"description": utils.PgTextString(item.Description),
|
||||
"file_path": utils.ResolveMediaURL(item.LibraryID, pgtype.Text{String: item.FilePath, Valid: item.FilePath != ""}),
|
||||
@@ -929,6 +935,8 @@ func (mh *MediaHandler) GetMediaItem(c *echo.Context) error {
|
||||
"asin": utils.PgTextString(item.Asin),
|
||||
"date_published": item.DatePublished.Time.Format("2006-01-02"),
|
||||
"publisher": utils.PgTextString(item.Publisher),
|
||||
"publishers": item.Publishers,
|
||||
"explicit": item.Explicit,
|
||||
"contributors": item.Contributors,
|
||||
"language": utils.PgTextString(item.Language),
|
||||
"edition": utils.PgTextString(item.Edition),
|
||||
|
||||
Reference in new issue
Block a user