refactor(db): GetCollectionItemsForDashboard embeds the media row; excluded filter moves into SQL; third converter dies

SELECT mi.*, ci.excluded forced every caller to filter tombstone
exclusions in Go over a 69-field row, then a 45-field hand converter
(getCollectionItemsRowToMediaItems) copied it field-by-field into
MediaItems for BuildSections. The query now filters
(ci.excluded IS NOT TRUE) itself and returns sqlc.embed(mi) — callers
consume .MediaItems directly, the Go filter loops die in both
collection-detail ladders (JSON + SSR), and the converter is deleted.

Pins, H1/H3 tests, unit suites green.
This commit is contained in:
John O'Keefe committed 2026-10-17 16:51:30 -04:00
1 parent 18727fef93
commit 77cf0b827b
7 files changed
+102 -218

No files matched your search

+4 -54
View File
@@ -2,7 +2,6 @@ package services
import (
"bookhoard/internal/database"
"bookhoard/internal/utils"
"context"
"encoding/json"
"errors"
@@ -13,55 +12,6 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
func getCollectionItemsRowToMediaItems(item database.GetCollectionItemsForDashboardRow) database.MediaItems {
return database.MediaItems{
ID: item.ID,
LibraryID: item.LibraryID,
Title: item.Title,
Author: item.Author,
Isbn: item.Isbn,
Description: item.Description,
FilePath: utils.ResolveMediaURL(item.LibraryID, pgtype.Text{String: item.FilePath, Valid: true}),
FileSize: item.FileSize,
MimeType: item.MimeType,
CoverImagePath: pgtype.Text{String: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath), Valid: true},
Series: item.Series,
SeriesNumber: item.SeriesNumber,
Tags: item.Tags,
Asin: item.Asin,
DatePublished: item.DatePublished,
Publisher: item.Publisher,
Contributors: item.Contributors,
Language: item.Language,
Edition: item.Edition,
PageCount: item.PageCount,
Genre: item.Genre,
CopyrightYear: item.CopyrightYear,
GoodreadsID: item.GoodreadsID,
OpenlibraryID: item.OpenlibraryID,
GoogleBooksID: item.GoogleBooksID,
AddedByAdminID: item.AddedByAdminID,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
FormatGroup: item.FormatGroup,
FormatMimetype: item.FormatMimetype,
IsReflowable: item.IsReflowable,
HasFixedLayout: item.HasFixedLayout,
TotalCharacters: item.TotalCharacters,
ChapterCount: item.ChapterCount,
EntitlementID: item.EntitlementID,
RevisionNumber: item.RevisionNumber,
KoboContentID: item.KoboContentID,
KoboMetadata: item.KoboMetadata,
TagsSearch: item.TagsSearch,
ContributorsSearch: item.ContributorsSearch,
FileSha256: item.FileSha256,
OpfIdentifier: item.OpfIdentifier,
OpfUuid: item.OpfUuid,
HashConfidence: item.HashConfidence,
}
}
type DashboardService struct {
db *database.Queries
collectionService *CollectionService
@@ -277,11 +227,11 @@ func (s *DashboardService) getUserCollectionItems(ctx context.Context, coll data
return nil, err
}
// The query filters excluded rows in SQL and embeds the media row,
// so the items are MediaItems as-is.
var manualNonExcluded []database.MediaItems
for _, item := range manualItems {
if !item.Excluded.Valid || !item.Excluded.Bool {
manualNonExcluded = append(manualNonExcluded, getCollectionItemsRowToMediaItems(item))
}
for _, row := range manualItems {
manualNonExcluded = append(manualNonExcluded, row.MediaItems)
}
var autoItems []database.MediaItems