[PR #4960] feat: Add Audible series ASIN field to Series entity #4384

Open
opened 2026-04-25 00:19:32 +02:00 by adam · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/advplyr/audiobookshelf/pull/4960
Author: @H2OKing89
Created: 1/4/2026
Status: 🔄 Open

Base: masterHead: feat/series-audible-asin


📝 Commits (4)

  • 40606eb feat: add audibleSeriesAsin field to Series model
  • b8b3a20 feat: add UI for editing series Audible ASIN
  • edbd49c feat: add series search by Audible ASIN
  • d5a2ea9 feat: auto-populate series ASIN from Audible metadata

📊 Changes

17 files changed (+592 additions, -29 deletions)

View changed files

📝 .gitignore (+3 -0)
📝 client/components/modals/EditSeriesInputInnerModal.vue (+58 -2)
📝 client/components/modals/item/tabs/Match.vue (+5 -2)
client/components/ui/AsinInput.vue (+155 -0)
📝 client/components/widgets/SeriesInputWidget.vue (+55 -6)
📝 client/strings/en-us.json (+3 -0)
📝 docs/objects/entities/Series.yaml (+12 -0)
📝 package-lock.json (+2 -2)
📝 package.json (+1 -1)
📝 server/controllers/SeriesController.js (+33 -5)
server/migrations/v2.33.0-series-audible-asin.js (+107 -0)
📝 server/models/Book.js (+10 -2)
📝 server/models/Series.js (+59 -4)
📝 server/providers/Audible.js (+4 -2)
📝 server/scanner/Scanner.js (+19 -1)
📝 server/utils/queries/libraryItemsBookFilters.js (+11 -2)
test/server/models/Series.test.js (+55 -0)

📄 Description

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

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/advplyr/audiobookshelf/pull/4960 **Author:** [@H2OKing89](https://github.com/H2OKing89) **Created:** 1/4/2026 **Status:** 🔄 Open **Base:** `master` ← **Head:** `feat/series-audible-asin` --- ### 📝 Commits (4) - [`40606eb`](https://github.com/advplyr/audiobookshelf/commit/40606eb1af28852880522147c9a95f849d496592) feat: add audibleSeriesAsin field to Series model - [`b8b3a20`](https://github.com/advplyr/audiobookshelf/commit/b8b3a2049822a65c2445726d332c55dcb1bc00fa) feat: add UI for editing series Audible ASIN - [`edbd49c`](https://github.com/advplyr/audiobookshelf/commit/edbd49c4c180ab7524e27d638756f40d689fad6d) feat: add series search by Audible ASIN - [`d5a2ea9`](https://github.com/advplyr/audiobookshelf/commit/d5a2ea9feba67f2da7806afb29a34337d4ee8aee) feat: auto-populate series ASIN from Audible metadata ### 📊 Changes **17 files changed** (+592 additions, -29 deletions) <details> <summary>View changed files</summary> 📝 `.gitignore` (+3 -0) 📝 `client/components/modals/EditSeriesInputInnerModal.vue` (+58 -2) 📝 `client/components/modals/item/tabs/Match.vue` (+5 -2) ➕ `client/components/ui/AsinInput.vue` (+155 -0) 📝 `client/components/widgets/SeriesInputWidget.vue` (+55 -6) 📝 `client/strings/en-us.json` (+3 -0) 📝 `docs/objects/entities/Series.yaml` (+12 -0) 📝 `package-lock.json` (+2 -2) 📝 `package.json` (+1 -1) 📝 `server/controllers/SeriesController.js` (+33 -5) ➕ `server/migrations/v2.33.0-series-audible-asin.js` (+107 -0) 📝 `server/models/Book.js` (+10 -2) 📝 `server/models/Series.js` (+59 -4) 📝 `server/providers/Audible.js` (+4 -2) 📝 `server/scanner/Scanner.js` (+19 -1) 📝 `server/utils/queries/libraryItemsBookFilters.js` (+11 -2) ➕ `test/server/models/Series.test.js` (+55 -0) </details> ### 📄 Description ## 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 ```text ┌─────────────────────────────────────────────────────────────────────────────┐ │ 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 <details> <summary><strong>Click to expand screenshots</strong></summary> <br/> <p><strong>Audiobook Editor</strong></p> <img width="1217" height="1048" alt="Audiobook Editor" src="https://github.com/user-attachments/assets/72a9ddb3-9756-4b87-9a6c-28b388cf8168" /> <br/><br/> <p><strong>Search by Series ASIN (results returned)</strong></p> <img width="964" height="642" alt="Search by Series ASIN returning results" src="https://github.com/user-attachments/assets/5a8b20ac-731c-4f99-95f5-70c4a7622d73" /> <br/><br/> <p><strong>Match flow: series populated with Series ASIN</strong></p> <img width="1243" height="1051" alt="Match flow showing series ASIN populated" src="https://github.com/user-attachments/assets/5d76a07a-c948-4927-8db1-54ea72d74829" /> <br/> </details> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
adam added the pull-request label 2026-04-25 00:19:32 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf#4384