feat(dashboard): global (All Libraries) preferences scope
The dashboard's All Libraries view could render sections across every library but could not persist its own preferences: the PUT required a UUID library_id, and the prefs GET-without-library_id matched an arbitrary library's row through a loose NULL OR-clause. Storage: a dedicated user_dashboard_preferences_global table (one row per user, user_id PK → users CASCADE) instead of a NULL/sentinel library_id — chosen over both because (a) the column's FK to libraries rules out a sentinel UUID without dropping the constraint or minting a fake library row, and (b) intent becomes structural: only the explicit global path writes that table, so a buggy or outside-the-API write can never masquerade as global. System and user collections are user-scoped, so the order/hidden arrays are library-independent and valid in this scope. API: PUT /api/dashboard/preferences binds library_id as *string — JSON null/absent = global, empty string = 400 (a lost value must stay loud, not silently clobber the global scope), valid UUID = per-library (unchanged). GET without library_id now returns the global row instead of an arbitrary one; the per-library query's dead NULL OR-arm is removed. GET /sections keeps requiring library_id for per-library views and aggregates across libraries when absent (unchanged). Tests: global save/update-dedupe/read round-trip, empty-string 400, global-vs-per-library independence. Two suite failures are the handoff's documented pre-existing rot (duplicate system collections in the dev DB; a sections test expecting a 400 the handler never implemented) — reproduced and confirmed unrelated to this change. Live-verified on the dev stack: null save 200 x2 with a single row updated, empty string 400, global read carries the second save.
This commit is contained in:
1 parent
b8db1d109c
commit
25d44452c0
8 files changed
+304
-7
No files matched your search
@@ -2435,9 +2435,13 @@ LIMIT $1 OFFSET $2;
|
||||
-- ============================================
|
||||
|
||||
-- Dashboard preferences queries
|
||||
-- The handler routes by scope: a Valid library_id targets the per-library
|
||||
-- table below; an absent one targets user_dashboard_preferences_global
|
||||
-- (the "All Libraries" view). Never pass NULL here — the loose NULL match
|
||||
-- was removed because it returned an arbitrary library's row.
|
||||
-- name: GetDashboardPreferences :one
|
||||
SELECT * FROM user_dashboard_preferences
|
||||
WHERE user_id = sqlc.narg('user_id') AND (sqlc.narg('library_id')::uuid IS NULL OR library_id = sqlc.narg('library_id')::uuid);
|
||||
WHERE user_id = sqlc.narg('user_id') AND library_id = sqlc.narg('library_id')::uuid;
|
||||
|
||||
-- name: UpsertDashboardPreferences :one
|
||||
INSERT INTO user_dashboard_preferences (user_id, library_id, hidden_collections, collection_order, items_per_section)
|
||||
@@ -2459,6 +2463,22 @@ SET hidden_collections = $2,
|
||||
WHERE user_id = $1 AND library_id = $5
|
||||
RETURNING *;
|
||||
|
||||
-- Global (all-libraries) scope: exactly one row per user.
|
||||
-- name: GetGlobalDashboardPreferences :one
|
||||
SELECT user_id, hidden_collections, collection_order, items_per_section, updated_at FROM user_dashboard_preferences_global
|
||||
WHERE user_id = $1;
|
||||
|
||||
-- name: UpsertGlobalDashboardPreferences :one
|
||||
INSERT INTO user_dashboard_preferences_global (user_id, hidden_collections, collection_order, items_per_section)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (user_id)
|
||||
DO UPDATE SET
|
||||
hidden_collections = EXCLUDED.hidden_collections,
|
||||
collection_order = EXCLUDED.collection_order,
|
||||
items_per_section = EXCLUDED.items_per_section,
|
||||
updated_at = NOW()
|
||||
RETURNING user_id, hidden_collections, collection_order, items_per_section, updated_at;
|
||||
|
||||
-- Dashboard collections queries
|
||||
-- name: GetSystemCollectionsForDashboard :many
|
||||
SELECT * FROM collections
|
||||
|
||||
Reference in new issue
Block a user