Skip to content

Conversation

@H2OKing89
Copy link

@H2OKing89 H2OKing89 commented Jan 4, 2026

Brief summary

Adds first-class support for Audible Series ASINs by introducing a new audibleSeriesAsin field on the Series entity. This enables stronger integration with Audible metadata and allows series to be reliably linked to official Audible catalog entries.

Which issue is fixed?

Fixes #4937

In-depth Description

What this enables

  • Store a 10-character Audible Series ASIN directly on a Series (audibleSeriesAsin)
  • Accept either:
    • Raw ASIN (e.g. B08G9PRS1K)
    • Full Audible series URL (ASIN auto-extracted)
  • Prevent accidental ASIN loss during common edit flows (blank updates won’t clear an existing ASIN)
  • Search library by series name OR series ASIN

Key behaviors

✅ Normalization + validation

  • Extracts ASINs from pasted Audible URLs
  • Validates format: ^[A-Z0-9]{10}$ (uppercase alphanumeric, length 10)

✅ Data integrity safeguards (no accidental nuking)

  • Frontend: real-time validation + visual feedback
  • Model layer: validation + normalization
  • Sequelize hook: beforeValidate ensures validation runs for all update paths
  • Controller safeguard: blank/empty updates won’t overwrite an existing ASIN

Why this approach?

  • Low-friction: ASINs are captured automatically from Audible metadata when available, but can still be entered manually.
  • URL-friendly: Pasting a full Audible series URL "just works" via ASIN extraction.
  • Safe by default: Safeguards prevent accidental clearing/overwriting of an existing ASIN during normal edit flows.

Implementation overview

Database & model layer

  • Migration v2.33.0 adds audibleSeriesAsin + index
  • Series model:
    • Normalization helper (normalizeAudibleSeriesAsin())
    • Validation rules for the 10-char format
  • beforeValidate hook guarantees consistent validation across CREATE / PATCH / PUT
  • Backend guard prevents empty updates from clearing existing ASIN data

UI

  • New AsinInput.vue component:
    • URL detection + ASIN extraction on paste
    • Real-time validation state + feedback
  • Integrated into Series edit flow
  • Protects against clearing when selecting an existing series with a blank ASIN field

Metadata + scanning

  • Audible provider returns series ASINs in results when available
  • Quick match + interactive match capture and persist series ASINs automatically

Search

  • Library search now supports lookup via series ASIN in addition to the name

ASIN flow architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                          ASIN ENTRY POINTS                                  │
└─────────────────────────────────────────────────────────────────────────────┘
                                     │
         ┌───────────────────────────┼───────────────────────────┐
         │                           │                           │
         ▼                           ▼                           ▼
┌─────────────────┐       ┌─────────────────┐       ┌─────────────────┐
│  Manual Entry   │       │ Metadata Match  │       │  Library Scan   │
│                 │       │                 │       │                 │
│ • Series Modal  │       │ • Interactive   │       │ • Quick Match   │
│ • AsinInput.vue │       │ • Match.vue     │       │ • Scanner.js    │
│ • Paste URL     │       │ • Audible API   │       │ • Auto-populate │
│   or raw ASIN   │       │   Response      │       │                 │
└────────┬────────┘       └────────┬────────┘       └────────┬────────┘
         │                         │                         │
         └─────────────────────────┼─────────────────────────┘
                                   │
                                   ▼
                   ┌───────────────────────────────┐
                   │   URL/ASIN Normalization      │
                   │                               │
                   │  normalizeAudibleSeriesAsin() │
                   └───────────────┬───────────────┘
                                   │
                                   ▼
                   ┌─────────────────────────────┐
                   │  Sequelize beforeValidate   │
                   │         Hook (Series.js)    │
                   └──────────────┬──────────────┘
                                  │
                                  ▼
                   ┌─────────────────────────────┐
                   │   Backend Safeguard Check   │
                   │  (SeriesController.js)      │
                   └──────────────┬──────────────┘
                                  │
                                  ▼
                   ┌─────────────────────────────┐
                   │    Database Persistence     │
                   │  Series Table (SQLite)      │
                   │  audibleSeriesAsin + INDEX  │
                   └──────────────┬──────────────┘
                                  │
                                  ▼
                   ┌─────────────────────────────┐
                   │          ASIN USAGE         │
                   │ • Search by ASIN            │
                   │ • UI display/edit           │
                   │ • Future: deep linking      │
                   └─────────────────────────────┘

How have you tested this?

Manual testing

  1. ✅ Create new series with ASIN in edit modal (raw ASIN + URL paste)
  2. ✅ Update existing series with ASIN (persists correctly)
  3. ✅ Interactive match captures series ASIN from provider
  4. ✅ Quick match during scan auto-populates series ASIN
  5. ✅ Search library by ASIN returns correct books
  6. ✅ Editing a book/series with blank ASIN does not clear an existing ASIN
  7. ✅ Invalid ASIN formats are rejected with clear feedback

Automated testing

  • ✅ All existing tests pass (325)

  • ✅ Added 55 new unit tests covering:

    • Validation
    • Normalization
    • URL extraction

Migration testing

  • ✅ Up: column + index added
  • ✅ Down: column + index removed cleanly
  • ✅ Version bumped to 2.33.0 to match migration

Screenshots

Click to expand screenshots

Audiobook Editor

Audiobook Editor



Search by Series ASIN (results returned)

Search by Series ASIN returning results



Match flow: series populated with Series ASIN

Match flow showing series ASIN populated

Quentin King added 4 commits January 3, 2026 10:33
- Add audibleSeriesAsin column to Series table via migration v2.33.0
- Update Series model to include the new field
- Add API endpoint for updating series ASIN (PATCH /api/series/:id)
- Add unit tests for Series model
- Create AsinInput.vue component for ASIN input with URL extraction
- Update EditSeriesInputInnerModal to include ASIN field
- Update SeriesInputWidget to fetch and save ASIN data
- Add SeriesController PATCH endpoint for updating ASIN
- Add localization strings for ASIN-related messages

The AsinInput component automatically extracts ASINs from pasted
Audible URLs and provides validation feedback.
- Update libraryItemsBookFilters to search by audibleSeriesAsin
- Searching for an ASIN in the library now finds books in that series
- Update Audible provider to return series ASIN in search results
- Pass series ASIN through Match.vue when selecting metadata
- Update Book.updateSeriesFromRequest to forward ASIN to Series model
- Update Scanner to use series ASIN during quick match

When using the Audible metadata provider, the series ASIN is now
automatically captured and stored with the series.
@nichwall
Copy link
Contributor

nichwall commented Jan 4, 2026

What is the final intention of adding the ASIN to the series in ABS? We do not support having library items that don't have a file associated entry (i.e. no ghost entries or placeholders, everything needs to be a local file), so I'm struggling to understand why we would want to add this information to the series.

@H2OKing89
Copy link
Author

nichwall
What is the final intention of adding the ASIN to the series in ABS? We do not support having library items that don't have a file associated entry (i.e. no ghost entries or placeholders, everything needs to be a local file), so I'm struggling to understand why we would want to add this information to the series.


@nichwall

Good question! The intention is metadata enrichment + reliable identity — this doesn't change ABS's "no ghost entries" philosophy.

This PR only adds an optional Audible series ASIN to a Series that already exists because it’s linked to local books. It does not create placeholder series/books, track unowned volumes, or imply “missing books”.

Why store it at the series level?

  • Canonical identifier / disambiguation: series names collide a lot (“Chronicles”, “Book 1”, regional variants, subtitle differences). The ASIN gives us a stable ID to match/merge the same series across multiple books.
  • More reliable matching on subsequent scans: when the Audible provider returns series.asin, we can persist it once and use it to consistently attach future books to the correct series entry (even if the title formatting differs).
  • Consistency with book-level IDs: similar to storing ISBN/ASIN on a Book — just applied to the Series entity for fidelity.

So the “final intention” is: make series identification deterministic when metadata providers supply a stable ID, without introducing any library items that aren’t backed by local files.

@nichwall
Copy link
Contributor

nichwall commented Jan 4, 2026

nichwall
What is the final intention of adding the ASIN to the series in ABS? We do not support having library items that don't have a file associated entry (i.e. no ghost entries or placeholders, everything needs to be a local file), so I'm struggling to understand why we would want to add this information to the series.

@nichwall

Good question! The intention is metadata enrichment + reliable identity — this doesn't change ABS's "no ghost entries" philosophy.

This PR only adds an optional Audible series ASIN to a Series that already exists because it’s linked to local books. It does not create placeholder series/books, track unowned volumes, or imply “missing books”.

Why store it at the series level?

* **Canonical identifier / disambiguation:** series names collide _a lot_ (“Chronicles”, “Book 1”, regional variants, subtitle differences). The ASIN gives us a stable ID to match/merge the _same_ series across multiple books.

* **More reliable matching on subsequent scans:** when the Audible provider returns `series.asin`, we can persist it once and use it to consistently attach future books to the correct series entry (even if the title formatting differs).

* **Consistency with book-level IDs:** similar to storing ISBN/ASIN on a Book — just applied to the Series entity for fidelity.

So the “final intention” is: make series identification deterministic when metadata providers supply a stable ID, without introducing any library items that aren’t backed by local files.

We are not accepting or reviewing PRs that are fully AI generated due to an increasing number of AI generated PRs of varying quality levels. Reviewing PRs takes up already limited development time, and seeing a response that seems to be copied directly to and from a chat prompt erodes confidence that the rest of the PR (or the additional issues and PRs you opened over the past few days) are fully thought out and ready to review. This is not to say AI is bad and cannot be used since it can help to describe things better, but they also add a lot of unnecessary information that makes it harder to understand what is going on.

With that said, I can understand wanting to add metadata to a series, but still not sure how it fully applies here for matching against an online provider. Once you have reviewed and simplified the descriptions I can look at it again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] Add “Audible Series ID/ASIN” field to Series metadata

2 participants