Admin library export for meta.audiosilo.app - #42
Merged
Merged
Conversation
An admin can now download a library's book list as a JSON file and import it
on meta.audiosilo.app's Watching page to mark which entries of a series they
own.
GET /api/v1/admin/libraries/{id}/export (requireAdmin) serves the
{"format":"audiosilo-books","version":1,...} envelope the metadata site
already parses, as an attachment named
audiosilo-<library-slug>-<YYYY-MM-DD>.json.
The file leaves the server, so it carries bibliographic facts only - title,
authors, narrators, series + position, asin/isbn, runtime_min, chapters - and
never a path, size, codec, format or anything else about the filesystem. A
regression test asserts that.
- internal/catalog/export.go composes the envelope (unit-testable, no HTTP):
keyset paging over ListBooks (never OFFSET), copies of the same book within
the library collapsed on exposedDedupKey, one chapter-count query per page.
The single author/narrator string splits into a list only where it clearly
holds several names: on ";", " & " and " and " always, and on a comma only
when every part still has two words, so "Alexandre Dumas, pere" stays whole.
series_position is the reader's form of the float index ("2", "2.5").
- internal/api/handlers_export.go is transport-only and streams the envelope
with a json.Encoder; 404 for an unknown library, 400 for a bad id.
- The "export" capability is added to GET /server (additive; old clients
ignore it).
- The admin console gains an Export button per library. It cannot be a plain
<a href> because the console authenticates with a bearer token, so it
fetches the endpoint, reads the Content-Disposition filename and saves via
a blob object URL - no CSP change needed. Strings added to all six locales,
and internal/web now has a key-parity test so a locale cannot fall behind.
Note: books rows are books, including folder-per-book ones (is_folder), so
nothing is skipped on is_folder - a multi-file book is already one row and
therefore one export entry.
Pre-merge pass over the export branch.
Simplification/reuse:
- splitNames folds its three separator passes into one regexp split, so the
cleaning no longer has to happen between passes (which left a stranded
joiner on "X; and Y"); splitOnCommas no longer re-cleans an already-clean
chunk, and slugify is strings.FieldsFunc instead of a hand-rolled
builder + dash flag.
- chapterCounts is one grouped query for the whole library instead of one
per export page, dropping the dynamic IN (?,?,...) placeholder building.
- Filename reads the date with strings.Cut; the always-empty Subtitle field
is gone (the envelope is additive, so it returns when the index has one).
- admin.js: apiFetch now owns the API prefix, bearer auth, the 401 re-login
and the {error} envelope, and api() is JSON decoding over it - the export
download shares that one contract instead of a second copy of it.
Correctness:
- Duplicate copies of a book merge rather than keeping whichever sorted
first: an untagged rip indexed before the tagged copy used to win the
(title, id) keyset and take the narrator/series/isbn/chapters with it.
mergeExportBook fills only fields the kept entry lacks.
- cleanNameParts trims a dangling comma from both ends, so a half-empty
"Last, First" tag no longer exports as ", Jane Doe".
Tests:
- The leak guard now asserts on the marshalled key sets as an allowlist, so
a leaked field fails even when its name is invisible to a substring scan
("format" is the envelope's own marker; "files" was unchecked). Verified
by mutation: adding rel_path/size/format/files to ExportBook fails it.
- The i18n parity check no longer compares en against itself, and drops an
unreachable block/name count guard.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
GET /api/v1/admin/libraries/{id}/export(admin only): downloads a library's book list as theaudiosilo-booksv1 envelope that meta.audiosilo.app's import parser already reads - title, authors, narrators, series and position, ASIN, ISBN, runtime, chapter count. Never paths, sizes, codecs or roots (an allowlist test on the marshalled JSON keys pins that).exportcapability flag onGET /server(additive; mirrored in audiosilo-frontend's types in a sibling PR).internal/catalog(keyset paging, one grouped chapter query, duplicate copies merged field-by-field so the tagged copy is never dropped for the sparse one).Why
The meta site's new Watching page lets a user mark which volumes of a watched series they own; this export is how they get their library in without typing it.
Checks
Full gate green (build, vet, test -race, golangci-lint). Simplify and code-review passes applied. Docs: audiosilo-docs
server-exportbranch (API reference, web UI page, user guide); the libraries screenshot is regenerated separately.