From 122038123c8fdedea82d7f2337b8be91ca6bdbe2 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 18 May 2026 17:53:42 -0400 Subject: [PATCH] feat(router): cookie-aware SSR library resolution with resolveLibrary helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- internal/router/frontend.go | 443 +++++++++++------------------------- internal/router/helpers.go | 76 +++++++ 2 files changed, 204 insertions(+), 315 deletions(-) diff --git a/internal/router/frontend.go b/internal/router/frontend.go index a19fa28..1dd0b1e 100644 --- a/internal/router/frontend.go +++ b/internal/router/frontend.go @@ -111,14 +111,6 @@ func registerFrontendRoutes(cfg *Config) { // Protected frontend routes (no /api prefix) 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 frontendProtected.GET("/series", func(c *echo.Context) error { user, err := getTemplateUserWithTheme(c, cfg) @@ -128,36 +120,11 @@ func registerFrontendRoutes(cfg *Config) { var errorMsg string - 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" - } - } - - 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, - } + libRes := resolveLibrary(c, cfg, user.ID) + libraryID := libRes.LibraryID + libData := libRes.Libraries + if libRes.IsAll { + errorMsg = "" } perSeriesPage := 24 @@ -172,31 +139,28 @@ func registerFrontendRoutes(cfg *Config) { var seriesCards []templates.SeriesCardData totalPages := 1 - if libraryID != "" && errorMsg == "" { - libUUID, err := uuid.Parse(libraryID) - if err == nil { - seriesList, total, err := handlers.GetSeriesCardsData(c.Request().Context(), cfg.Queries, libUUID, perSeriesPage, offset) - if err != nil { - log.Printf("GetSeriesCardsData failed: %v", err) - errorMsg = "Error loading series" - } else { - totalPages = (total + perSeriesPage - 1) / perSeriesPage - if totalPages < 1 { - totalPages = 1 - } - seriesCards = make([]templates.SeriesCardData, 0, len(seriesList)) - for _, s := range seriesList { - covers := s.CoverPaths - if covers == nil { - covers = []string{} - } - seriesCards = append(seriesCards, templates.SeriesCardData{ - Name: s.Name, - BookCount: s.BookCount, - TotalInSeries: s.TotalInSeries, - CoverPaths: covers, - }) + if errorMsg == "" { + seriesList, total, err := handlers.GetSeriesCardsData(c.Request().Context(), cfg.Queries, libRes.LibUUID, perSeriesPage, offset) + if err != nil { + log.Printf("GetSeriesCardsData failed: %v", err) + errorMsg = "Error loading series" + } else { + totalPages = (total + perSeriesPage - 1) / perSeriesPage + if totalPages < 1 { + totalPages = 1 + } + seriesCards = make([]templates.SeriesCardData, 0, len(seriesList)) + for _, s := range seriesList { + covers := s.CoverPaths + if covers == nil { + covers = []string{} } + 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") } - 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 - if libraryID != "" && errorMsg == "" { - libUUID, err := uuid.Parse(libraryID) - if err == nil { - svc := services.NewSeriesService(cfg.Queries) - books, err := svc.GetSeriesBooks(c.Request().Context(), libUUID, seriesName) - if err != nil { - log.Printf("GetSeriesBooks failed: %v", err) - errorMsg = "Error loading series books" - } else { - bookInfoList = make([]handlers.BookInfo, 0, len(books)) - for _, item := range books { - itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16]) - bookInfoList = append(bookInfoList, handlers.BookInfo{ - MediaItemID: itemUUID.String(), - Title: item.Title, - Author: textToString(item.Author), - CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath), - }) - } - } + svc := services.NewSeriesService(cfg.Queries) + books, err := svc.GetSeriesBooks(c.Request().Context(), seriesName) + if err != nil { + log.Printf("GetSeriesBooks failed: %v", err) + errorMsg = "Error loading series books" + } else { + bookInfoList = make([]handlers.BookInfo, 0, len(books)) + for _, item := range books { + itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16]) + bookInfoList = append(bookInfoList, handlers.BookInfo{ + MediaItemID: itemUUID.String(), + 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 - 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 { return err } @@ -289,41 +236,29 @@ func registerFrontendRoutes(cfg *Config) { return renderErrorPage(c, "Tag 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" - } - } + libRes := resolveLibrary(c, cfg, user.ID) + libraryID := libRes.LibraryID var bookInfoList []handlers.BookInfo if libraryID != "" && errorMsg == "" { - libUUID, err := uuid.Parse(libraryID) - if err == nil { - books, err := cfg.Queries.GetBooksByTag(c.Request().Context(), database.GetBooksByTagParams{ - LibraryID: uuidToPGType(libUUID), - Column2: tagName, - }) - if err != nil { - log.Printf("GetBooksByTag failed: %v", err) - errorMsg = "Error loading tag books" - } else { - bookInfoList = make([]handlers.BookInfo, 0, len(books)) - for _, item := range books { - itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16]) - bookInfoList = append(bookInfoList, handlers.BookInfo{ - MediaItemID: itemUUID.String(), - Title: item.Title, - Author: textToString(item.Author), - CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath), - }) - } + books, err := cfg.Queries.GetBooksByTag(c.Request().Context(), database.GetBooksByTagParams{ + LibraryID: libRes.LibUUID, + Column2: tagName, + }) + if err != nil { + log.Printf("GetBooksByTag failed: %v", err) + errorMsg = "Error loading tag books" + } else { + bookInfoList = make([]handlers.BookInfo, 0, len(books)) + for _, item := range books { + itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16]) + bookInfoList = append(bookInfoList, handlers.BookInfo{ + MediaItemID: itemUUID.String(), + Title: item.Title, + Author: textToString(item.Author), + CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath), + }) } } } @@ -348,52 +283,20 @@ func registerFrontendRoutes(cfg *Config) { var errorMsg string - // Get library_id from query param or user's first library - libraryID := c.QueryParam("library_id") - if 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() - } else { - errorMsg = "No libraries available" - } - } + libRes := resolveLibrary(c, cfg, user.ID) + libraryID := libRes.LibraryID + libData := libRes.Libraries - // Get libraries for dropdown + // Fetch saved filters for SSR 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 - if libraryID != "" && errorMsg == "" { - savedFilters, err = cfg.Queries.GetSavedFilters(c.Request().Context(), database.GetSavedFiltersParams{ - UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, - ResourceType: "media-items", - }) - if err != nil { - log.Printf("GetSavedFilters failed: %v", err) - savedFilters = []database.SavedFilters{} - } + savedFilters, err = cfg.Queries.GetSavedFilters(c.Request().Context(), database.GetSavedFiltersParams{ + UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, + ResourceType: "media-items", + }) + if err != nil { + log.Printf("GetSavedFilters failed: %v", err) + savedFilters = []database.SavedFilters{} } // Fetch first page of books for SSR @@ -402,64 +305,51 @@ func registerFrontendRoutes(cfg *Config) { limit := 50 offset := 0 - if libraryID != "" && errorMsg == "" { - libUUID, err := uuid.Parse(libraryID) - if err == nil { - // Check URL params for pagination - if limitStr := c.QueryParam("limit"); limitStr != "" { - if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 { - limit = l + if errorMsg == "" { + // Check URL params for pagination + if limitStr := c.QueryParam("limit"); limitStr != "" { + 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 - libraryID := c.QueryParam("library_id") - if 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() - } - } + libRes := resolveLibrary(c, cfg, user.ID) + libraryID := libRes.LibraryID - libUUID, _ := uuid.Parse(libraryID) 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 { log.Printf("GetDashboardPreferences failed: %v", err) prefs = database.UserDashboardPreferences{ @@ -511,7 +394,7 @@ func registerFrontendRoutes(cfg *Config) { allSections, err := cfg.DashboardService.GetDashboardSections( c.Request().Context(), userUUID, - libUUID, + pgLibUUID, limit, prefs.CollectionOrder, []string{}, // No filtering - get all sections @@ -525,32 +408,11 @@ func registerFrontendRoutes(cfg *Config) { // Get only visible sections for the dashboard display 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) allSectionsData := handlers.BuildSections(allSections, libraryID) 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 { 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 - 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 { return err } @@ -698,20 +536,12 @@ func registerFrontendRoutes(cfg *Config) { userUUID, _ := uuid.Parse(user.ID) 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 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) - 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 { return renderErrorPage(c, "Error loading books", "books_load_error") } @@ -733,7 +563,7 @@ func registerFrontendRoutes(cfg *Config) { } } } else { - if libraryID != "" { + if libraryID != "" && !libRes.IsAll { libUUID, parseErr := uuid.Parse(libraryID) if parseErr != nil { return renderErrorPage(c, "Invalid library ID", "invalid_library_id") @@ -743,7 +573,7 @@ func registerFrontendRoutes(cfg *Config) { database.GetCollectionItemsForDashboardParams{ CollectionID: pgtype.UUID{Bytes: collUUID, Valid: true}, LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true}, - Limit: 1000, + Limit: pgtype.Int4{Int32: 1000, Valid: true}, }) if collErr != nil { books = []handlers.BookInfo{} @@ -795,25 +625,8 @@ func registerFrontendRoutes(cfg *Config) { 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 - 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 { return err } diff --git a/internal/router/helpers.go b/internal/router/helpers.go index 449efee..f087e6e 100644 --- a/internal/router/helpers.go +++ b/internal/router/helpers.go @@ -3,7 +3,9 @@ package router import ( "context" "log" + "net/url" + "bookhoard/internal/database" "bookhoard/templates" "github.com/google/uuid" @@ -86,3 +88,77 @@ func parseUUID(s string) (uuid.UUID, error) { func uuidToPGType(u uuid.UUID) pgtype.UUID { 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 +}