feat(schema): subtitle/explicit columns, authors+publishers arrays, float series numbers
Phase 1 of the series/collections/metadata overhaul
(~/.opencode/plan/series-collections-metadata-overhaul.md).
Schema (in place, idempotent):
- media_items.series_number INTEGER -> DOUBLE PRECISION: real-world
ordinals are decimals (0.5, 1.5 novellas); INTEGER silently truncated
them (calibre 2.5 pinned as 2 in tests).
- + subtitle TEXT, explicit BOOLEAN (sidecar ingest; ABS explicit /
Audible isAdult).
- + authors/authors_search/publishers/publishers_search TEXT[] NOT NULL
(narrators A2a precedent). Arrays are write truth; author/publisher
varchars become trigger-regenerated display strings
(sync_author_publisher_display) so every varchar consumer (trgm
search, facets, API, OPDS/Kobo) keeps working unchanged.
- Row-level NULL normalization for the new arrays in
set_library_type_name (A2 lesson: Go nil -> explicit NULL -> 23502).
- Backfills ARRAY[author]/ARRAY[publisher] — deliberately no
comma-splitting ('Tolkien, J.R.R.' would corrupt; scanner kept only
authors[0] historically).
- collection_items.position INTEGER + (collection_id, position) index
for playlist ordering.
Go: sqlc regen; MediaMetadata/handler params gain Subtitle/Explicit/
Authors/Publishers (legacy singles still accepted, arrays win);
SeriesNumber int32 -> float64 end to end; metadata_overrides adapted
with legacy override keys ('author'/'publisher') preserved so existing
user overrides survive; bulk author edit writes the array.
Pins widened (docs/contract_pins.md): raw sqlc row key set + the six
new columns; changelog entry added.
Verified: unit suite green; integration sweep (bulk/smart collections/
scanner/stats/sync/queue/calibre/pins/audiobooks) green against the
shared dev DB after in-place migration. Pre-existing unrelated red:
TestOwnershipAndAdminTestSuite/TestAdminCreateUser timezone inheritance
fails identically at the pre-change commit (live shared state).
This commit is contained in:
1 parent
f7dc8189a1
commit
646477afad
30 files changed
+790
-247
No files matched your search
+32
-11
@@ -39,10 +39,14 @@ type CreateMediaItemRequest struct {
|
||||
MimeType string `json:"mime_type" validate:"required"`
|
||||
CoverImagePath string `json:"cover_image_path"`
|
||||
Series string `json:"series"`
|
||||
SeriesNumber int32 `json:"series_number"`
|
||||
SeriesNumber float64 `json:"series_number"`
|
||||
Tags []string `json:"tags"`
|
||||
ASIN string `json:"asin"`
|
||||
DatePublished string `json:"date_published"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
Explicit bool `json:"explicit"`
|
||||
Authors []string `json:"authors"` // write truth; legacy `author` still accepted
|
||||
Publishers []string `json:"publishers"` // write truth; legacy `publisher` still accepted
|
||||
Publisher string `json:"publisher"`
|
||||
Contributors []string `json:"contributors"`
|
||||
// NEW: Reading direction and comic metadata fields
|
||||
@@ -69,16 +73,20 @@ type CreateMediaItemRequest struct {
|
||||
type UpdateMediaItemRequest struct {
|
||||
Title string `form:"title" json:"title" validate:"required,min=1,max=500"`
|
||||
Author string `form:"author" json:"author"`
|
||||
Authors []string `form:"authors" json:"authors"` // write truth; legacy `author` still accepted
|
||||
Subtitle string `form:"subtitle" json:"subtitle"`
|
||||
Explicit bool `form:"explicit" json:"explicit"`
|
||||
ISBN string `form:"isbn" json:"isbn"`
|
||||
Description string `form:"description" json:"description"`
|
||||
CoverImagePath string `form:"cover_image_path" json:"cover_image_path"`
|
||||
CoverAction string `form:"cover_action" json:"cover_action"`
|
||||
Series string `form:"series" json:"series"`
|
||||
SeriesNumber int32 `form:"series_number" json:"series_number"`
|
||||
SeriesNumber float64 `form:"series_number" json:"series_number"`
|
||||
Tags []string `form:"tags" json:"tags"`
|
||||
ASIN string `form:"asin" json:"asin"`
|
||||
DatePublished string `form:"date_published" json:"date_published"`
|
||||
Publisher string `form:"publisher" json:"publisher"`
|
||||
Publishers []string `form:"publishers" json:"publishers"` // write truth; legacy `publisher` still accepted
|
||||
Contributors []string `form:"contributors" json:"contributors"`
|
||||
Language string `form:"language" json:"language"`
|
||||
Edition string `form:"edition" json:"edition"`
|
||||
@@ -588,13 +596,16 @@ func (h *MediaHandler) HandleBulkUpdate(c *echo.Context) error {
|
||||
updateParams := database.UpdateMediaItemParams{
|
||||
ID: mediaUUID,
|
||||
Title: existingMedia.Title,
|
||||
Author: existingMedia.Author,
|
||||
Subtitle: existingMedia.Subtitle,
|
||||
Authors: existingMedia.Authors,
|
||||
AuthorsSearch: existingMedia.AuthorsSearch,
|
||||
Genre: existingMedia.Genre,
|
||||
Language: existingMedia.Language,
|
||||
Tags: existingMedia.Tags,
|
||||
TagsSearch: existingMedia.TagsSearch,
|
||||
Description: existingMedia.Description,
|
||||
Publisher: existingMedia.Publisher,
|
||||
Publishers: existingMedia.Publishers,
|
||||
PublishersSearch: existingMedia.PublishersSearch,
|
||||
CopyrightYear: existingMedia.CopyrightYear,
|
||||
Isbn: existingMedia.Isbn,
|
||||
Series: existingMedia.Series,
|
||||
@@ -623,6 +634,7 @@ func (h *MediaHandler) HandleBulkUpdate(c *echo.Context) error {
|
||||
AlternateInfo: existingMedia.AlternateInfo,
|
||||
ScanInformation: existingMedia.ScanInformation,
|
||||
Summary: existingMedia.Summary,
|
||||
Explicit: existingMedia.Explicit,
|
||||
}
|
||||
|
||||
// Bulk edits are user customizations: keep existing overrides and mark
|
||||
@@ -633,7 +645,8 @@ func (h *MediaHandler) HandleBulkUpdate(c *echo.Context) error {
|
||||
overrides = utils.MergeOverrides(overrides, utils.OverrideTitle)
|
||||
}
|
||||
if update.Updates.Author != nil {
|
||||
updateParams.Author = pgtype.Text{String: *update.Updates.Author, Valid: true}
|
||||
updateParams.Authors = []string{*update.Updates.Author}
|
||||
updateParams.AuthorsSearch = utils.NormalizeTagsSearch(updateParams.Authors)
|
||||
overrides = utils.MergeOverrides(overrides, utils.OverrideAuthor)
|
||||
}
|
||||
if update.Updates.Genre != nil {
|
||||
@@ -1415,7 +1428,9 @@ func (mh *MediaHandler) CreateMediaItem(c *echo.Context) error {
|
||||
created, err := mh.db.CreateMediaItemIfNew(c.Request().Context(), database.CreateMediaItemIfNewParams{
|
||||
LibraryID: pgtype.UUID{Bytes: req.LibraryID, Valid: true},
|
||||
Title: req.Title,
|
||||
Author: pgtype.Text{String: req.Author, Valid: req.Author != ""},
|
||||
Subtitle: pgtype.Text{String: req.Subtitle, Valid: req.Subtitle != ""},
|
||||
Authors: utils.SingleOrSlice(req.Author, req.Authors),
|
||||
AuthorsSearch: utils.NormalizeTagsSearch(utils.SingleOrSlice(req.Author, req.Authors)),
|
||||
Isbn: pgtype.Text{String: isbnValue, Valid: req.ISBN != ""},
|
||||
Description: pgtype.Text{String: req.Description, Valid: req.Description != ""},
|
||||
FilePath: req.FilePath,
|
||||
@@ -1423,15 +1438,17 @@ func (mh *MediaHandler) CreateMediaItem(c *echo.Context) error {
|
||||
MimeType: pgtype.Text{String: req.MimeType, Valid: req.MimeType != ""},
|
||||
CoverImagePath: pgtype.Text{String: req.CoverImagePath, Valid: req.CoverImagePath != ""},
|
||||
Series: pgtype.Text{String: req.Series, Valid: req.Series != ""},
|
||||
SeriesNumber: pgtype.Int4{Int32: req.SeriesNumber, Valid: req.SeriesNumber > 0},
|
||||
SeriesNumber: pgtype.Float8{Float64: req.SeriesNumber, Valid: req.SeriesNumber > 0},
|
||||
Tags: req.Tags,
|
||||
TagsSearch: tagsSearch,
|
||||
Narrators: narrators,
|
||||
NarratorsSearch: narratorsSearch,
|
||||
Abridged: req.Abridged,
|
||||
Explicit: req.Explicit,
|
||||
Asin: pgtype.Text{String: req.ASIN, Valid: req.ASIN != ""},
|
||||
DatePublished: pgtype.Date{Time: parseDate(req.DatePublished), Valid: req.DatePublished != ""},
|
||||
Publisher: pgtype.Text{String: req.Publisher, Valid: req.Publisher != ""},
|
||||
Publishers: utils.SingleOrSlice(req.Publisher, req.Publishers),
|
||||
PublishersSearch: utils.NormalizeTagsSearch(utils.SingleOrSlice(req.Publisher, req.Publishers)),
|
||||
Contributors: req.Contributors,
|
||||
ContributorsSearch: contributorsSearch,
|
||||
AddedByAdminID: user.ID,
|
||||
@@ -1541,20 +1558,24 @@ func (mh *MediaHandler) UpdateMediaItem(c *echo.Context) error {
|
||||
params := database.UpdateMediaItemParams{
|
||||
ID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
Title: req.Title,
|
||||
Author: pgtype.Text{String: req.Author, Valid: req.Author != ""},
|
||||
Subtitle: pgtype.Text{String: req.Subtitle, Valid: req.Subtitle != ""},
|
||||
Authors: utils.SingleOrSlice(req.Author, req.Authors),
|
||||
AuthorsSearch: utils.NormalizeTagsSearch(utils.SingleOrSlice(req.Author, req.Authors)),
|
||||
Isbn: pgtype.Text{String: isbnValue, Valid: req.ISBN != ""},
|
||||
Description: pgtype.Text{String: req.Description, Valid: req.Description != ""},
|
||||
CoverImagePath: pgtype.Text{String: coverPath, Valid: coverPath != ""},
|
||||
Series: pgtype.Text{String: req.Series, Valid: req.Series != ""},
|
||||
SeriesNumber: pgtype.Int4{Int32: req.SeriesNumber, Valid: req.SeriesNumber > 0},
|
||||
SeriesNumber: pgtype.Float8{Float64: req.SeriesNumber, Valid: req.SeriesNumber > 0},
|
||||
Tags: req.Tags,
|
||||
TagsSearch: tagsSearch,
|
||||
Narrators: narrators,
|
||||
NarratorsSearch: narratorsSearch,
|
||||
Abridged: req.Abridged,
|
||||
Explicit: req.Explicit,
|
||||
Asin: pgtype.Text{String: req.ASIN, Valid: req.ASIN != ""},
|
||||
DatePublished: pgtype.Date{Time: parseDate(req.DatePublished), Valid: req.DatePublished != ""},
|
||||
Publisher: pgtype.Text{String: req.Publisher, Valid: req.Publisher != ""},
|
||||
Publishers: utils.SingleOrSlice(req.Publisher, req.Publishers),
|
||||
PublishersSearch: utils.NormalizeTagsSearch(utils.SingleOrSlice(req.Publisher, req.Publishers)),
|
||||
Contributors: req.Contributors,
|
||||
ContributorsSearch: contributorsSearch,
|
||||
Language: pgtype.Text{String: req.Language, Valid: req.Language != ""},
|
||||
|
||||
Reference in new issue
Block a user