mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-24 05:41:53 +00:00
Implements feedBack#636 item 2 (P1) — saved library filters that stay live, the homelab primitive FeedBack was missing (Plex smart collections / Navidrome .nsp / *arr custom filters). A collection is a saved /api/library query surfaced as a registered library provider, so it appears in the v3 source picker and inherits the whole Songs UI (paging, stats, A–Z rail, art) with no new screen. - Storage reuses the playlist subsystem: a `playlists.rules` JSON column (additive, idempotent migration). A row with rules != NULL is a smart collection; list_playlists + get_playlist filter `rules IS NULL`, so collections are excluded from the manual-playlist list and read-only to every playlist mutation that gates on get_playlist. - SmartCollectionProvider (kind="local" — matched songs are local rows, so the client's play/art paths stay on the local branch) delegates query_page/ query_stats/query_artists to the local DB with the stored rules applied; tuning_names/get_art delegate straight through. Registered via a boot scan + on create/update (replace=True) / delete. - Rules mirror the raw /api/library query params; `_sanitize_collection_rules` drops unknown keys and is applied at API ingress AND on provider load, so a hand-edited / imported bad value can't crash a query. - API: GET/POST/PUT/DELETE /api/collections. Frontend: a "+ Save as collection" action in the v3 filter drawer (local provider + active filters only) that names the current filter set and switches to it. Reviewed by Codex; 3 findings fixed (local-kind playback path, save gated to local provider, re-sanitize persisted rules). Tests: tests/test_collections_api.py (CRUD, provider filtering, restart re-registration, kind=local, corrupt-rule tolerance, playlist isolation), tests/js/v3_collections.test.js. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35 lines
1.6 KiB
JavaScript
35 lines
1.6 KiB
JavaScript
// Pins the v3 "Save as collection" wiring in static/v3/songs.js (#636 item 2).
|
|
// A smart collection is a saved live library filter, surfaced as a source in
|
|
// the provider picker; the drawer can save the current filter set as one.
|
|
// Source-level only — same strategy as tests/js/v3_az_rail.test.js.
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const SONGS_JS = path.join(__dirname, '..', '..', 'static', 'v3', 'songs.js');
|
|
const src = fs.readFileSync(SONGS_JS, 'utf8');
|
|
|
|
test('currentFilterRules builds the raw query-param rule object', () => {
|
|
assert.match(src, /function\s+currentFilterRules/);
|
|
// Multi-value filters are CSV strings (what the backend stores / re-parses).
|
|
assert.match(src, /r\.tunings\s*=\s*f\.tunings\.join\(','\)/);
|
|
assert.match(src, /r\.arrangements_has\s*=\s*f\.arr_has\.join\(','\)/);
|
|
});
|
|
|
|
test('saving POSTs to /api/collections with name + rules', () => {
|
|
assert.match(
|
|
src,
|
|
/fetch\('\/api\/collections',[\s\S]*?JSON\.stringify\(\{\s*name,\s*rules\s*\}\)/,
|
|
'saveCurrentAsCollection must POST {name, rules} to /api/collections',
|
|
);
|
|
// After save, switch the source to the new collection and rebuild the UI.
|
|
assert.match(src, /state\.provider\s*=\s*'collection:'\s*\+\s*col\.id/);
|
|
});
|
|
|
|
test('the drawer shows a Save-as-collection action only when filters are set', () => {
|
|
assert.match(src, /Object\.keys\(currentFilterRules\(\)\)\.length[\s\S]*?data-drawer-save/);
|
|
assert.match(src, /data-drawer-save[\s\S]*?saveCurrentAsCollection/);
|
|
});
|