feat(router): cookie-aware SSR library resolution with resolveLibrary helper

- helpers.go: Promote getText() from a local closure in frontend.go
  to a package-level function so it can be used by resolveLibrary.
  Add resolveLibrary(c, cfg, user.ID) helper that:
    1. Reads library_id query param (explicit navigation wins)
    2. Falls back to selectedLibrary cookie — validates __all__
       sentinel or real UUID, rejects garbage values silently
    3. Falls back to user's first visible library
  Returns LibraryResolution struct with LibraryID, IsAll, LibUUID,
  Libraries, and FirstID — eliminating repeated boilerplate across
  all SSR routes.

- frontend.go: Replace manual library resolution boilerplate in 5
  SSR route handlers (series, tags/detail, bookshelf, dashboard,
  collections/:id) with resolveLibrary(). Each route now gets cookie-
  aware library selection for free. Collection detail correctly
  handles All Libraries mode for both system and user collections.
  Dashboard no longer makes a redundant second GetUserVisibleLibraries
  call.
This commit is contained in:
2026-05-18 17:53:42 -04:00
parent 19fc222fb3
commit 122038123c
2 changed files with 204 additions and 315 deletions
+128 -315
View File
@@ -111,14 +111,6 @@ func registerFrontendRoutes(cfg *Config) {
// Protected frontend routes (no /api prefix) // Protected frontend routes (no /api prefix)
frontendProtected := e.Group("", jwtMiddleware, ensureUserExistsMiddleware(cfg)) frontendProtected := e.Group("", jwtMiddleware, ensureUserExistsMiddleware(cfg))
// Helper to extract text from pgtype.Text
getText := func(t pgtype.Text) string {
if t.Valid {
return t.String
}
return ""
}
// Series browse page // Series browse page
frontendProtected.GET("/series", func(c *echo.Context) error { frontendProtected.GET("/series", func(c *echo.Context) error {
user, err := getTemplateUserWithTheme(c, cfg) user, err := getTemplateUserWithTheme(c, cfg)
@@ -128,36 +120,11 @@ func registerFrontendRoutes(cfg *Config) {
var errorMsg string var errorMsg string
libraryID := c.QueryParam("library_id") libRes := resolveLibrary(c, cfg, user.ID)
userUUID, _ := uuid.Parse(user.ID) libraryID := libRes.LibraryID
if libraryID == "" { libData := libRes.Libraries
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID)) if libRes.IsAll {
if err == nil && len(libraries) > 0 { errorMsg = ""
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
libraryID = libUUID.String()
} else {
errorMsg = "No libraries available"
}
}
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if err != nil {
log.Printf("GetUserVisibleLibraries failed: %v", err)
libraries = []database.GetUserVisibleLibrariesRow{}
if errorMsg == "" {
errorMsg = "Error loading libraries"
}
}
libData := make([]templates.LibraryData, len(libraries))
for i, lib := range libraries {
libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16])
libData[i] = templates.LibraryData{
ID: libUUID.String(),
Name: lib.Name,
Description: getText(lib.Description),
TypeName: lib.TypeName,
}
} }
perSeriesPage := 24 perSeriesPage := 24
@@ -172,31 +139,28 @@ func registerFrontendRoutes(cfg *Config) {
var seriesCards []templates.SeriesCardData var seriesCards []templates.SeriesCardData
totalPages := 1 totalPages := 1
if libraryID != "" && errorMsg == "" { if errorMsg == "" {
libUUID, err := uuid.Parse(libraryID) seriesList, total, err := handlers.GetSeriesCardsData(c.Request().Context(), cfg.Queries, libRes.LibUUID, perSeriesPage, offset)
if err == nil { if err != nil {
seriesList, total, err := handlers.GetSeriesCardsData(c.Request().Context(), cfg.Queries, libUUID, perSeriesPage, offset) log.Printf("GetSeriesCardsData failed: %v", err)
if err != nil { errorMsg = "Error loading series"
log.Printf("GetSeriesCardsData failed: %v", err) } else {
errorMsg = "Error loading series" totalPages = (total + perSeriesPage - 1) / perSeriesPage
} else { if totalPages < 1 {
totalPages = (total + perSeriesPage - 1) / perSeriesPage totalPages = 1
if totalPages < 1 { }
totalPages = 1 seriesCards = make([]templates.SeriesCardData, 0, len(seriesList))
} for _, s := range seriesList {
seriesCards = make([]templates.SeriesCardData, 0, len(seriesList)) covers := s.CoverPaths
for _, s := range seriesList { if covers == nil {
covers := s.CoverPaths covers = []string{}
if covers == nil {
covers = []string{}
}
seriesCards = append(seriesCards, templates.SeriesCardData{
Name: s.Name,
BookCount: s.BookCount,
TotalInSeries: s.TotalInSeries,
CoverPaths: covers,
})
} }
seriesCards = append(seriesCards, templates.SeriesCardData{
Name: s.Name,
BookCount: s.BookCount,
TotalInSeries: s.TotalInSeries,
CoverPaths: covers,
})
} }
} }
} }
@@ -227,40 +191,23 @@ func registerFrontendRoutes(cfg *Config) {
return renderErrorPage(c, "Series name required", "bad_request") return renderErrorPage(c, "Series name required", "bad_request")
} }
libraryID := c.QueryParam("library_id")
userUUID, _ := uuid.Parse(user.ID)
if libraryID == "" {
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if err == nil && len(libraries) > 0 {
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
libraryID = libUUID.String()
} else {
errorMsg = "No libraries available"
}
}
var bookInfoList []handlers.BookInfo var bookInfoList []handlers.BookInfo
if libraryID != "" && errorMsg == "" { svc := services.NewSeriesService(cfg.Queries)
libUUID, err := uuid.Parse(libraryID) books, err := svc.GetSeriesBooks(c.Request().Context(), seriesName)
if err == nil { if err != nil {
svc := services.NewSeriesService(cfg.Queries) log.Printf("GetSeriesBooks failed: %v", err)
books, err := svc.GetSeriesBooks(c.Request().Context(), libUUID, seriesName) errorMsg = "Error loading series books"
if err != nil { } else {
log.Printf("GetSeriesBooks failed: %v", err) bookInfoList = make([]handlers.BookInfo, 0, len(books))
errorMsg = "Error loading series books" for _, item := range books {
} else { itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16])
bookInfoList = make([]handlers.BookInfo, 0, len(books)) bookInfoList = append(bookInfoList, handlers.BookInfo{
for _, item := range books { MediaItemID: itemUUID.String(),
itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16]) Title: item.Title,
bookInfoList = append(bookInfoList, handlers.BookInfo{ Author: textToString(item.Author),
MediaItemID: itemUUID.String(), CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
Title: item.Title, })
Author: textToString(item.Author),
CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
})
}
}
} }
} }
@@ -269,7 +216,7 @@ func registerFrontendRoutes(cfg *Config) {
} }
var buf bytes.Buffer var buf bytes.Buffer
err = templates.BrowseDetail(user, "📚", "Series", seriesName, seriesName, "/series", "All Series", "📚", "This series doesn't have any books in this library yet", bookInfoList, errorMsg).Render(c.Request().Context(), &buf) err = templates.BrowseDetail(user, "📚", "Series", seriesName, seriesName, "/series", "All Series", "📚", "This series doesn't have any books yet", bookInfoList, errorMsg).Render(c.Request().Context(), &buf)
if err != nil { if err != nil {
return err return err
} }
@@ -289,41 +236,29 @@ func registerFrontendRoutes(cfg *Config) {
return renderErrorPage(c, "Tag name required", "bad_request") return renderErrorPage(c, "Tag name required", "bad_request")
} }
libraryID := c.QueryParam("library_id") libRes := resolveLibrary(c, cfg, user.ID)
userUUID, _ := uuid.Parse(user.ID) libraryID := libRes.LibraryID
if libraryID == "" {
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if err == nil && len(libraries) > 0 {
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
libraryID = libUUID.String()
} else {
errorMsg = "No libraries available"
}
}
var bookInfoList []handlers.BookInfo var bookInfoList []handlers.BookInfo
if libraryID != "" && errorMsg == "" { if libraryID != "" && errorMsg == "" {
libUUID, err := uuid.Parse(libraryID) books, err := cfg.Queries.GetBooksByTag(c.Request().Context(), database.GetBooksByTagParams{
if err == nil { LibraryID: libRes.LibUUID,
books, err := cfg.Queries.GetBooksByTag(c.Request().Context(), database.GetBooksByTagParams{ Column2: tagName,
LibraryID: uuidToPGType(libUUID), })
Column2: tagName, if err != nil {
}) log.Printf("GetBooksByTag failed: %v", err)
if err != nil { errorMsg = "Error loading tag books"
log.Printf("GetBooksByTag failed: %v", err) } else {
errorMsg = "Error loading tag books" bookInfoList = make([]handlers.BookInfo, 0, len(books))
} else { for _, item := range books {
bookInfoList = make([]handlers.BookInfo, 0, len(books)) itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16])
for _, item := range books { bookInfoList = append(bookInfoList, handlers.BookInfo{
itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16]) MediaItemID: itemUUID.String(),
bookInfoList = append(bookInfoList, handlers.BookInfo{ Title: item.Title,
MediaItemID: itemUUID.String(), Author: textToString(item.Author),
Title: item.Title, CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
Author: textToString(item.Author), })
CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
})
}
} }
} }
} }
@@ -348,52 +283,20 @@ func registerFrontendRoutes(cfg *Config) {
var errorMsg string var errorMsg string
// Get library_id from query param or user's first library libRes := resolveLibrary(c, cfg, user.ID)
libraryID := c.QueryParam("library_id") libraryID := libRes.LibraryID
if libraryID == "" { libData := libRes.Libraries
userUUID, _ := uuid.Parse(user.ID)
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if err == nil && len(libraries) > 0 {
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
libraryID = libUUID.String()
} else {
errorMsg = "No libraries available"
}
}
// Get libraries for dropdown // Fetch saved filters for SSR
userUUID, _ := uuid.Parse(user.ID) userUUID, _ := uuid.Parse(user.ID)
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if err != nil {
log.Printf("GetUserVisibleLibraries failed: %v", err)
libraries = []database.GetUserVisibleLibrariesRow{}
if errorMsg == "" {
errorMsg = "Error loading libraries"
}
}
libData := make([]templates.LibraryData, len(libraries))
for i, lib := range libraries {
libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16])
libData[i] = templates.LibraryData{
ID: libUUID.String(),
Name: lib.Name,
Description: getText(lib.Description),
TypeName: lib.TypeName,
}
}
// Fetch saved filters for SSR (using existing query)
var savedFilters []database.SavedFilters var savedFilters []database.SavedFilters
if libraryID != "" && errorMsg == "" { savedFilters, err = cfg.Queries.GetSavedFilters(c.Request().Context(), database.GetSavedFiltersParams{
savedFilters, err = cfg.Queries.GetSavedFilters(c.Request().Context(), database.GetSavedFiltersParams{ UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, ResourceType: "media-items",
ResourceType: "media-items", })
}) if err != nil {
if err != nil { log.Printf("GetSavedFilters failed: %v", err)
log.Printf("GetSavedFilters failed: %v", err) savedFilters = []database.SavedFilters{}
savedFilters = []database.SavedFilters{}
}
} }
// Fetch first page of books for SSR // Fetch first page of books for SSR
@@ -402,64 +305,51 @@ func registerFrontendRoutes(cfg *Config) {
limit := 50 limit := 50
offset := 0 offset := 0
if libraryID != "" && errorMsg == "" { if errorMsg == "" {
libUUID, err := uuid.Parse(libraryID) // Check URL params for pagination
if err == nil { if limitStr := c.QueryParam("limit"); limitStr != "" {
// Check URL params for pagination if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
if limitStr := c.QueryParam("limit"); limitStr != "" { limit = l
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 { }
limit = l }
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
offset = o
}
}
params := services.SearchParams{
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
LibraryID: libRes.LibUUID,
SearchQuery: "",
AuthorFilter: "",
SeriesFilter: "",
GenreFilter: "",
TagsFilter: "",
LanguageFilter: "",
YearMin: 0,
YearMax: 0,
HasCover: pgtype.Bool{Valid: false},
Sort: "created_at DESC",
Limit: limit,
Offset: offset,
}
var results []database.SearchMediaItemsUnifiedRow
results, totalCount, err = cfg.MediaHandler.ExecuteSearch(c.Request().Context(), params)
if err != nil {
log.Printf("ExecuteSearch failed: %v", err)
} else {
bookInfoList = make([]handlers.BookInfo, len(results))
for i, book := range results {
bookUUID, _ := uuid.FromBytes(book.ID.Bytes[0:16])
bookLibUUID, _ := uuid.FromBytes(book.LibraryID.Bytes[0:16])
bookInfoList[i] = handlers.BookInfo{
MediaItemID: bookUUID.String(),
Title: book.Title,
Author: textToString(book.Author),
CoverImagePath: utils.ResolveMediaURL(pgtype.UUID{Bytes: bookLibUUID, Valid: true}, book.CoverImagePath),
} }
} }
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
offset = o
}
}
// Convert user.ID (string) to pgtype.UUID for service layer
userUUID, err := uuid.Parse(user.ID)
if err != nil {
log.Printf("Failed to parse user ID: %v", err)
return renderErrorPage(c, "Error loading user", "user_id_error")
}
// Build search params (same as search.go:76-91)
params := services.SearchParams{
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
SearchQuery: "", // Empty for initial SSR load
AuthorFilter: "",
SeriesFilter: "",
GenreFilter: "",
TagsFilter: "",
LanguageFilter: "",
YearMin: 0,
YearMax: 0,
HasCover: pgtype.Bool{Valid: false},
Sort: "created_at DESC",
Limit: limit,
Offset: offset,
}
// Execute search using the same handler as API (search.go:93)
var results []database.SearchMediaItemsUnifiedRow
results, totalCount, err = cfg.MediaHandler.ExecuteSearch(c.Request().Context(), params)
if err != nil {
log.Printf("ExecuteSearch failed: %v", err)
// Continue without books - will show empty state
} else {
// Convert to BookInfo (same as search.go:99-109)
bookInfoList = make([]handlers.BookInfo, len(results))
for i, book := range results {
bookUUID, _ := uuid.FromBytes(book.ID.Bytes[0:16])
bookLibUUID, _ := uuid.FromBytes(book.LibraryID.Bytes[0:16])
bookInfoList[i] = handlers.BookInfo{
MediaItemID: bookUUID.String(),
Title: book.Title,
Author: textToString(book.Author),
CoverImagePath: utils.ResolveMediaURL(pgtype.UUID{Bytes: bookLibUUID, Valid: true}, book.CoverImagePath),
}
}
log.Printf("SSR: fetched %d books for library %s", len(bookInfoList), libraryID)
}
} }
} }
@@ -480,20 +370,13 @@ func registerFrontendRoutes(cfg *Config) {
var errorMsg string var errorMsg string
libraryID := c.QueryParam("library_id") libRes := resolveLibrary(c, cfg, user.ID)
if libraryID == "" { libraryID := libRes.LibraryID
userUUID, _ := uuid.Parse(user.ID)
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if err == nil && len(libraries) > 0 {
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
libraryID = libUUID.String()
}
}
libUUID, _ := uuid.Parse(libraryID)
userUUID, _ := uuid.Parse(user.ID) userUUID, _ := uuid.Parse(user.ID)
pgLibUUID := libRes.LibUUID
prefs, err := cfg.DashboardService.GetDashboardPreferences(c.Request().Context(), userUUID, libUUID) prefs, err := cfg.DashboardService.GetDashboardPreferences(c.Request().Context(), userUUID, pgLibUUID)
if err != nil { if err != nil {
log.Printf("GetDashboardPreferences failed: %v", err) log.Printf("GetDashboardPreferences failed: %v", err)
prefs = database.UserDashboardPreferences{ prefs = database.UserDashboardPreferences{
@@ -511,7 +394,7 @@ func registerFrontendRoutes(cfg *Config) {
allSections, err := cfg.DashboardService.GetDashboardSections( allSections, err := cfg.DashboardService.GetDashboardSections(
c.Request().Context(), c.Request().Context(),
userUUID, userUUID,
libUUID, pgLibUUID,
limit, limit,
prefs.CollectionOrder, prefs.CollectionOrder,
[]string{}, // No filtering - get all sections []string{}, // No filtering - get all sections
@@ -525,32 +408,11 @@ func registerFrontendRoutes(cfg *Config) {
// Get only visible sections for the dashboard display // Get only visible sections for the dashboard display
visibleSections := cfg.DashboardService.FilterHiddenCollections(allSections, prefs.HiddenCollections) visibleSections := cfg.DashboardService.FilterHiddenCollections(allSections, prefs.HiddenCollections)
userUUID2, _ := uuid.Parse(user.ID)
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID2))
if err != nil {
log.Printf("GetUserVisibleLibraries failed: %v", err)
libraries = []database.GetUserVisibleLibrariesRow{}
if errorMsg == "" {
errorMsg = "Error loading libraries"
}
}
libData := make([]templates.LibraryData, len(libraries))
for i, lib := range libraries {
libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16])
libData[i] = templates.LibraryData{
ID: libUUID.String(),
Name: lib.Name,
Description: getText(lib.Description),
TypeName: lib.TypeName,
}
}
sectionData := handlers.BuildSections(visibleSections, libraryID) sectionData := handlers.BuildSections(visibleSections, libraryID)
allSectionsData := handlers.BuildSections(allSections, libraryID) allSectionsData := handlers.BuildSections(allSections, libraryID)
var buf bytes.Buffer var buf bytes.Buffer
err = templates.Dashboard(user, sectionData, allSectionsData, libData, libraryID, prefs.HiddenCollections, limit, errorMsg).Render(c.Request().Context(), &buf) err = templates.Dashboard(user, sectionData, allSectionsData, libRes.Libraries, libraryID, prefs.HiddenCollections, limit, errorMsg).Render(c.Request().Context(), &buf)
if err != nil { if err != nil {
return err return err
} }
@@ -585,32 +447,8 @@ func registerFrontendRoutes(cfg *Config) {
} }
} }
userUUID, _ := uuid.Parse(user.ID)
libraries, libErr := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if libErr != nil {
log.Printf("GetUserVisibleLibraries failed: %v", libErr)
libraries = []database.GetUserVisibleLibrariesRow{}
}
currentLibraryID := c.QueryParam("library_id")
if currentLibraryID == "" && len(libraries) > 0 {
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
currentLibraryID = libUUID.String()
}
libData := make([]templates.LibraryData, len(libraries))
for i, lib := range libraries {
libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16])
libData[i] = templates.LibraryData{
ID: libUUID.String(),
Name: lib.Name,
Description: getText(lib.Description),
TypeName: lib.TypeName,
}
}
var buf bytes.Buffer var buf bytes.Buffer
err = templates.Collection(user, colData, libData, currentLibraryID, errorMsg).Render(c.Request().Context(), &buf) err = templates.Collection(user, colData, errorMsg).Render(c.Request().Context(), &buf)
if err != nil { if err != nil {
return err return err
} }
@@ -698,20 +536,12 @@ func registerFrontendRoutes(cfg *Config) {
userUUID, _ := uuid.Parse(user.ID) userUUID, _ := uuid.Parse(user.ID)
var books []handlers.BookInfo var books []handlers.BookInfo
libraryID := c.QueryParam("library_id") libRes := resolveLibrary(c, cfg, user.ID)
libraryID := libRes.LibraryID
if collection.QueryType.Valid && collection.QueryType.String != "" { if collection.QueryType.Valid && collection.QueryType.String != "" {
if libraryID == "" {
libraries, libErr := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if libErr == nil && len(libraries) > 0 {
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
libraryID = libUUID.String()
}
}
libUUID, _ := uuid.Parse(libraryID)
dashboardSvc := services.NewDashboardService(cfg.Queries) dashboardSvc := services.NewDashboardService(cfg.Queries)
sections, secErr := dashboardSvc.GetDashboardSections(c.Request().Context(), userUUID, libUUID, 1000, []string{}, []string{}) sections, secErr := dashboardSvc.GetDashboardSections(c.Request().Context(), userUUID, libRes.LibUUID, 1000, []string{}, []string{})
if secErr != nil { if secErr != nil {
return renderErrorPage(c, "Error loading books", "books_load_error") return renderErrorPage(c, "Error loading books", "books_load_error")
} }
@@ -733,7 +563,7 @@ func registerFrontendRoutes(cfg *Config) {
} }
} }
} else { } else {
if libraryID != "" { if libraryID != "" && !libRes.IsAll {
libUUID, parseErr := uuid.Parse(libraryID) libUUID, parseErr := uuid.Parse(libraryID)
if parseErr != nil { if parseErr != nil {
return renderErrorPage(c, "Invalid library ID", "invalid_library_id") return renderErrorPage(c, "Invalid library ID", "invalid_library_id")
@@ -743,7 +573,7 @@ func registerFrontendRoutes(cfg *Config) {
database.GetCollectionItemsForDashboardParams{ database.GetCollectionItemsForDashboardParams{
CollectionID: pgtype.UUID{Bytes: collUUID, Valid: true}, CollectionID: pgtype.UUID{Bytes: collUUID, Valid: true},
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true}, LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
Limit: 1000, Limit: pgtype.Int4{Int32: 1000, Valid: true},
}) })
if collErr != nil { if collErr != nil {
books = []handlers.BookInfo{} books = []handlers.BookInfo{}
@@ -795,25 +625,8 @@ func registerFrontendRoutes(cfg *Config) {
Icon: collection.Icon.String, Icon: collection.Icon.String,
} }
libraries, libErr := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if libErr != nil {
log.Printf("GetUserVisibleLibraries failed: %v", libErr)
libraries = []database.GetUserVisibleLibrariesRow{}
}
libData := make([]templates.LibraryData, len(libraries))
for i, lib := range libraries {
libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16])
libData[i] = templates.LibraryData{
ID: libUUID.String(),
Name: lib.Name,
Description: getText(lib.Description),
TypeName: lib.TypeName,
}
}
var buf bytes.Buffer var buf bytes.Buffer
err = templates.CollectionDetail(user, colData, books, libraryID, libData).Render(c.Request().Context(), &buf) err = templates.CollectionDetail(user, colData, books, libraryID, libRes.Libraries).Render(c.Request().Context(), &buf)
if err != nil { if err != nil {
return err return err
} }
+76
View File
@@ -3,7 +3,9 @@ package router
import ( import (
"context" "context"
"log" "log"
"net/url"
"bookhoard/internal/database"
"bookhoard/templates" "bookhoard/templates"
"github.com/google/uuid" "github.com/google/uuid"
@@ -86,3 +88,77 @@ func parseUUID(s string) (uuid.UUID, error) {
func uuidToPGType(u uuid.UUID) pgtype.UUID { func uuidToPGType(u uuid.UUID) pgtype.UUID {
return pgtype.UUID{Bytes: u, Valid: true} return pgtype.UUID{Bytes: u, Valid: true}
} }
const selectedLibraryCookie = "selectedLibrary"
const allLibrariesSentinel = "__all__"
type LibraryResolution struct {
LibraryID string
IsAll bool
LibUUID pgtype.UUID
Libraries []templates.LibraryData
FirstID string
}
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{}
userU, _ := uuid.Parse(userUUID)
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userU))
if err != nil {
log.Printf("GetUserVisibleLibraries failed: %v", err)
libraries = []database.GetUserVisibleLibrariesRow{}
}
res.Libraries = make([]templates.LibraryData, len(libraries))
for i, lib := range libraries {
libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16])
res.Libraries[i] = templates.LibraryData{
ID: libUUID.String(),
Name: lib.Name,
Description: getText(lib.Description),
TypeName: lib.TypeName,
}
}
if len(libraries) > 0 {
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
res.FirstID = libUUID.String()
}
libraryID := c.QueryParam("library_id")
if libraryID == "" {
if cookie, err := c.Cookie(selectedLibraryCookie); err == nil {
val, _ := url.QueryUnescape(cookie.Value)
if val == allLibrariesSentinel {
res.IsAll = true
res.LibraryID = ""
return res
}
if _, parseErr := uuid.Parse(val); parseErr == nil {
libraryID = val
}
}
}
if libraryID == "" {
res.LibraryID = res.FirstID
if res.LibraryID != "" {
parsed, _ := uuid.Parse(res.LibraryID)
res.LibUUID = pgtype.UUID{Bytes: parsed, Valid: true}
}
return res
}
res.LibraryID = libraryID
parsed, _ := uuid.Parse(libraryID)
res.LibUUID = pgtype.UUID{Bytes: parsed, Valid: true}
return res
}