refactor(collections): retire the legacy rules surface — tree-only storage
The pre-tree rules machinery is deleted: no row ever carried a rules array or a non-default match mode (verified in the dev DB), the web never read the envelope keys, and the Android app consumes none of it. Dead code gets no documentation. Removed: - PUT /api/collections/:id/match-mode — endpoint, route, and the UpdateCollectionMatchMode query. The tree's root mode owns any|all. - auto_assign_rules / rule_match_mode from every envelope (create/list/detail/update), from the create/update request shapes, and from the schema: both columns DROPPED (schema.sql carries idempotent DROP COLUMN IF EXISTS heals for existing DBs; sqlc regenerated). - The rules CRUD service layer (AddRule/SetRuleEnabled/RemoveRule + validateRule + UpdateCollectionRulesJSON), ParseRules / EnabledRules / IsSmartCollection, RulesToFilterTree (+ the SSR preload branch), the legacy branch of EvaluateSmartMembership, and SmartEvalOptions.MatchMode. IsCollectionSmart = tree presence; create-path isSmart = collection_type=smart OR a filter_tree. Kept (live consumers): POST /api/collections/preview still evaluates rules payloads in memory (the custom-section builder's preview), and include_auto stays on the list (the Android app sends it). The custom-section builder's SAVE was already broken — its stringified auto_assign_rules payload failed request binding (probed live: 400 invalid request). Create now rejects that payload loudly with a 400 naming the retirement, instead of silently creating a hollow static section; custom-section-builder.md carries the flag. The builder's tree migration remains the flagged follow-up. Test-side (asymmetry rule — deleted behavior, deleted tests): TestPodcastSmartCollections rewritten onto trees (mode lives in the tree root), match-mode subtests and tree_wins_over_legacy_rules deleted, TestRulesToFilterTree deleted, contract-pin envelope keys retracted per the contract_pins 2026-10-31 retirement entry. Mirrors hand-synced: openapi.yaml, bruno create/update, docs/api/*.md, docs/schema.go, api.d.ts. Full container suite green (155s).
This commit is contained in:
1 parent
ab98015190
commit
a86bdac87e
27 files changed
+239
-1093
No files matched your search
@@ -3489,8 +3489,8 @@ DELETE FROM device_file_aliases WHERE id = $1;
|
||||
|
||||
-- Create collection
|
||||
-- name: CreateCollection :one
|
||||
INSERT INTO collections (user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, filter_tree)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
INSERT INTO collections (user_id, name, description, color, icon, view_settings, show_on_dashboard, filter_tree)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING *;
|
||||
|
||||
-- A2c §2.4: provenance + playlist flags on an existing collection without
|
||||
@@ -3502,11 +3502,6 @@ RETURNING *;
|
||||
-- dropping the phantom column write.)
|
||||
UPDATE collections SET view_settings = $2 WHERE id = $1;
|
||||
|
||||
-- Smart-collection match semantics (plan item 2.4): flip any|all on its
|
||||
-- own channel — the rules page toggle writes exactly this column.
|
||||
-- name: UpdateCollectionMatchMode :exec
|
||||
UPDATE collections SET rule_match_mode = $2 WHERE id = $1;
|
||||
|
||||
-- Get collection
|
||||
-- name: GetCollection :one
|
||||
SELECT * FROM collections WHERE id = $1;
|
||||
@@ -3533,18 +3528,11 @@ SET
|
||||
description = $3,
|
||||
color = $4,
|
||||
icon = $5,
|
||||
auto_assign_rules = $6,
|
||||
view_settings = $7,
|
||||
rule_match_mode = $8,
|
||||
filter_tree = $9
|
||||
view_settings = $6,
|
||||
filter_tree = $7
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- Replace ONLY the auto_assign_rules document (rule CRUD: read-modify-write
|
||||
-- of the JSONB without touching metadata stale clients did not send).
|
||||
-- name: UpdateCollectionRulesJSON :one
|
||||
UPDATE collections SET auto_assign_rules = $2 WHERE id = $1 RETURNING *;
|
||||
|
||||
-- Delete collection
|
||||
-- name: DeleteCollection :exec
|
||||
DELETE FROM collections WHERE id = $1;
|
||||
@@ -4034,8 +4022,8 @@ RETURNING *;
|
||||
DELETE FROM collection_items WHERE collection_id = $1;
|
||||
|
||||
-- name: CreateSystemCollection :one
|
||||
INSERT INTO collections (user_id, name, description, icon, color, show_on_dashboard, query_type, priority, is_system_collection, auto_assign_rules)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true, 'null'::jsonb)
|
||||
INSERT INTO collections (user_id, name, description, icon, color, show_on_dashboard, query_type, priority, is_system_collection)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true)
|
||||
RETURNING *;
|
||||
|
||||
-- name: ResetSystemCollectionMetadata :exec
|
||||
@@ -4151,22 +4139,6 @@ LIMIT sqlc.narg('limit');
|
||||
-- name: CountCollectionItems :one
|
||||
SELECT COUNT(*) FROM collection_items WHERE collection_id = $1;
|
||||
|
||||
-- Smart ("dynamic") collections: rules are evaluated LIVE at render, so
|
||||
-- membership comes from the candidate book set instead of collection_items.
|
||||
-- The set is scoped to the requesting user's visible libraries (family
|
||||
-- lens enforced at the source) and live items only — the same filters the
|
||||
-- static collection paths apply. The viewer's progress rides along (plan
|
||||
-- item 2.4): the played/unplayed episode rule evaluates per-user, exactly
|
||||
-- like the hide_finished predicate (percentage >= 0.999 = finished).
|
||||
-- name: GetVisibleLiveMediaForRules :many
|
||||
SELECT sqlc.embed(mi), COALESCE(rp.percentage, 0) AS viewer_percentage FROM media_items mi
|
||||
LEFT JOIN reading_progress rp ON rp.media_item_id = mi.id AND rp.user_id = sqlc.narg('viewer_user_id')::uuid
|
||||
WHERE (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
AND (sqlc.narg('visible_library_ids')::uuid[] IS NULL OR mi.library_id = ANY(sqlc.narg('visible_library_ids')::uuid[]))
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
ORDER BY mi.title ASC;
|
||||
|
||||
-- Live item count for a STATIC collection (rule-free cards on the
|
||||
-- collections page); same live filter as GetCollectionItems, plus the
|
||||
-- future-hybrid excluded exclusion and an optional library scope.
|
||||
|
||||
Reference in new issue
Block a user