schema(dashboard): implement Phase 1 unified collections architecture
Add support for Carousel-style dashboard with unified collections architecture: Database Schema Changes: - Add user_dashboard_preferences table: - hidden_collections: TEXT[] for managing section visibility - collection_order: TEXT[] for custom ordering - items_per_section: INT for limiting items per section - Update collections table: - user_id: Make nullable to support system-owned collections (NULL = system) - show_on_dashboard: BOOLEAN for controlling visibility - query_type: TEXT for different query types (continue-reading, recently-added, etc.) - priority: INT for display order (lower = higher priority) - is_system_collection: BOOLEAN for flagging system defaults - Update collection_items table: - Add excluded BOOLEAN for user overrides of auto-assigned items Indexes: - idx_collections_dashboard: (user_id, show_on_dashboard, priority) WHERE show_on_dashboard = true - idx_dashboard_prefs_user_library: (user_id, library_id) - idx_collection_items_excluded: (collection_id, excluded) WHERE excluded = true System Collections (pre-seeded defaults): - continue-reading: Books with 0 < progress < 1 - recently-added: Newly added items to library - recently-read: Books with progress >= 1 - not-started: Books with progress = 0 or no record This implements Phase 1 of the Carousel-style dashboard redesign plan.
This commit is contained in:
@@ -14,18 +14,23 @@ type CollectionItems struct {
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"`
|
||||
AddedByUserID pgtype.UUID `db:"added_by_user_id" json:"added_by_user_id"`
|
||||
Excluded pgtype.Bool `db:"excluded" json:"excluded"`
|
||||
}
|
||||
|
||||
type Collections struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description pgtype.Text `db:"description" json:"description"`
|
||||
Color pgtype.Text `db:"color" json:"color"`
|
||||
Icon pgtype.Text `db:"icon" json:"icon"`
|
||||
AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"`
|
||||
ViewSettings []byte `db:"view_settings" json:"view_settings"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description pgtype.Text `db:"description" json:"description"`
|
||||
Color pgtype.Text `db:"color" json:"color"`
|
||||
Icon pgtype.Text `db:"icon" json:"icon"`
|
||||
AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"`
|
||||
ViewSettings []byte `db:"view_settings" json:"view_settings"`
|
||||
ShowOnDashboard pgtype.Bool `db:"show_on_dashboard" json:"show_on_dashboard"`
|
||||
QueryType pgtype.Text `db:"query_type" json:"query_type"`
|
||||
Priority pgtype.Int4 `db:"priority" json:"priority"`
|
||||
IsSystemCollection pgtype.Bool `db:"is_system_collection" json:"is_system_collection"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
type DeviceCatalogs struct {
|
||||
@@ -373,6 +378,17 @@ type UnlinkedBooks struct {
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
type UserDashboardPreferences struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
||||
HiddenCollections []string `db:"hidden_collections" json:"hidden_collections"`
|
||||
CollectionOrder []string `db:"collection_order" json:"collection_order"`
|
||||
ItemsPerSection pgtype.Int4 `db:"items_per_section" json:"items_per_section"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
type Users struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
Email string `db:"email" json:"email"`
|
||||
|
||||
@@ -16,7 +16,7 @@ const AddBookToCollection = `-- name: AddBookToCollection :one
|
||||
INSERT INTO collection_items (collection_id, media_item_id, added_by_user_id)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (collection_id, media_item_id) DO NOTHING
|
||||
RETURNING id, collection_id, media_item_id, added_at, added_by_user_id
|
||||
RETURNING id, collection_id, media_item_id, added_at, added_by_user_id, excluded
|
||||
`
|
||||
|
||||
type AddBookToCollectionParams struct {
|
||||
@@ -36,6 +36,7 @@ func (q *Queries) AddBookToCollection(ctx context.Context, arg AddBookToCollecti
|
||||
&i.MediaItemID,
|
||||
&i.AddedAt,
|
||||
&i.AddedByUserID,
|
||||
&i.Excluded,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -232,7 +233,7 @@ const CreateCollection = `-- name: CreateCollection :one
|
||||
|
||||
INSERT INTO collections (user_id, name, description, color, icon, auto_assign_rules, view_settings)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, created_at
|
||||
RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at
|
||||
`
|
||||
|
||||
type CreateCollectionParams struct {
|
||||
@@ -267,6 +268,10 @@ func (q *Queries) CreateCollection(ctx context.Context, arg CreateCollectionPara
|
||||
&i.Icon,
|
||||
&i.AutoAssignRules,
|
||||
&i.ViewSettings,
|
||||
&i.ShowOnDashboard,
|
||||
&i.QueryType,
|
||||
&i.Priority,
|
||||
&i.IsSystemCollection,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
@@ -1542,7 +1547,7 @@ func (q *Queries) GetAnnotationsForBook(ctx context.Context, arg GetAnnotationsF
|
||||
}
|
||||
|
||||
const GetCollection = `-- name: GetCollection :one
|
||||
SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, created_at FROM collections WHERE id = $1
|
||||
SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at FROM collections WHERE id = $1
|
||||
`
|
||||
|
||||
// Get collection
|
||||
@@ -1558,13 +1563,17 @@ func (q *Queries) GetCollection(ctx context.Context, id pgtype.UUID) (Collection
|
||||
&i.Icon,
|
||||
&i.AutoAssignRules,
|
||||
&i.ViewSettings,
|
||||
&i.ShowOnDashboard,
|
||||
&i.QueryType,
|
||||
&i.Priority,
|
||||
&i.IsSystemCollection,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetCollectionItems = `-- name: GetCollectionItems :many
|
||||
SELECT ci.id, ci.collection_id, ci.media_item_id, ci.added_at, ci.added_by_user_id, mi.title, mi.author, mi.cover_image_path
|
||||
SELECT ci.id, ci.collection_id, ci.media_item_id, ci.added_at, ci.added_by_user_id, ci.excluded, mi.title, mi.author, mi.cover_image_path
|
||||
FROM collection_items ci
|
||||
JOIN media_items mi ON ci.media_item_id = mi.id
|
||||
WHERE ci.collection_id = $1
|
||||
@@ -1577,6 +1586,7 @@ type GetCollectionItemsRow struct {
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"`
|
||||
AddedByUserID pgtype.UUID `db:"added_by_user_id" json:"added_by_user_id"`
|
||||
Excluded pgtype.Bool `db:"excluded" json:"excluded"`
|
||||
Title string `db:"title" json:"title"`
|
||||
Author pgtype.Text `db:"author" json:"author"`
|
||||
CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"`
|
||||
@@ -1598,6 +1608,7 @@ func (q *Queries) GetCollectionItems(ctx context.Context, collectionID pgtype.UU
|
||||
&i.MediaItemID,
|
||||
&i.AddedAt,
|
||||
&i.AddedByUserID,
|
||||
&i.Excluded,
|
||||
&i.Title,
|
||||
&i.Author,
|
||||
&i.CoverImagePath,
|
||||
@@ -1614,7 +1625,7 @@ func (q *Queries) GetCollectionItems(ctx context.Context, collectionID pgtype.UU
|
||||
|
||||
const GetCollectionWithBookCount = `-- name: GetCollectionWithBookCount :one
|
||||
SELECT
|
||||
c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.created_at,
|
||||
c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.show_on_dashboard, c.query_type, c.priority, c.is_system_collection, c.created_at,
|
||||
COUNT(ci.id) as book_count
|
||||
FROM collections c
|
||||
LEFT JOIN collection_items ci ON c.id = ci.collection_id
|
||||
@@ -1623,16 +1634,20 @@ GROUP BY c.id
|
||||
`
|
||||
|
||||
type GetCollectionWithBookCountRow struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description pgtype.Text `db:"description" json:"description"`
|
||||
Color pgtype.Text `db:"color" json:"color"`
|
||||
Icon pgtype.Text `db:"icon" json:"icon"`
|
||||
AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"`
|
||||
ViewSettings []byte `db:"view_settings" json:"view_settings"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
BookCount int64 `db:"book_count" json:"book_count"`
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description pgtype.Text `db:"description" json:"description"`
|
||||
Color pgtype.Text `db:"color" json:"color"`
|
||||
Icon pgtype.Text `db:"icon" json:"icon"`
|
||||
AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"`
|
||||
ViewSettings []byte `db:"view_settings" json:"view_settings"`
|
||||
ShowOnDashboard pgtype.Bool `db:"show_on_dashboard" json:"show_on_dashboard"`
|
||||
QueryType pgtype.Text `db:"query_type" json:"query_type"`
|
||||
Priority pgtype.Int4 `db:"priority" json:"priority"`
|
||||
IsSystemCollection pgtype.Bool `db:"is_system_collection" json:"is_system_collection"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
BookCount int64 `db:"book_count" json:"book_count"`
|
||||
}
|
||||
|
||||
// Get collection with book count
|
||||
@@ -1648,6 +1663,10 @@ func (q *Queries) GetCollectionWithBookCount(ctx context.Context, id pgtype.UUID
|
||||
&i.Icon,
|
||||
&i.AutoAssignRules,
|
||||
&i.ViewSettings,
|
||||
&i.ShowOnDashboard,
|
||||
&i.QueryType,
|
||||
&i.Priority,
|
||||
&i.IsSystemCollection,
|
||||
&i.CreatedAt,
|
||||
&i.BookCount,
|
||||
)
|
||||
@@ -1655,7 +1674,7 @@ func (q *Queries) GetCollectionWithBookCount(ctx context.Context, id pgtype.UUID
|
||||
}
|
||||
|
||||
const GetCollectionsByUser = `-- name: GetCollectionsByUser :many
|
||||
SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, created_at FROM collections WHERE user_id = $1 ORDER BY created_at DESC
|
||||
SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at FROM collections WHERE user_id = $1 ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
// Get collections by user
|
||||
@@ -1677,6 +1696,10 @@ func (q *Queries) GetCollectionsByUser(ctx context.Context, userID pgtype.UUID)
|
||||
&i.Icon,
|
||||
&i.AutoAssignRules,
|
||||
&i.ViewSettings,
|
||||
&i.ShowOnDashboard,
|
||||
&i.QueryType,
|
||||
&i.Priority,
|
||||
&i.IsSystemCollection,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -1690,7 +1713,7 @@ func (q *Queries) GetCollectionsByUser(ctx context.Context, userID pgtype.UUID)
|
||||
}
|
||||
|
||||
const GetCollectionsForBook = `-- name: GetCollectionsForBook :many
|
||||
SELECT c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.created_at
|
||||
SELECT c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.show_on_dashboard, c.query_type, c.priority, c.is_system_collection, c.created_at
|
||||
FROM collections c
|
||||
JOIN collection_items ci ON c.id = ci.collection_id
|
||||
WHERE ci.media_item_id = $1
|
||||
@@ -1715,6 +1738,10 @@ func (q *Queries) GetCollectionsForBook(ctx context.Context, mediaItemID pgtype.
|
||||
&i.Icon,
|
||||
&i.AutoAssignRules,
|
||||
&i.ViewSettings,
|
||||
&i.ShowOnDashboard,
|
||||
&i.QueryType,
|
||||
&i.Priority,
|
||||
&i.IsSystemCollection,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -6483,7 +6510,7 @@ SET
|
||||
auto_assign_rules = $6,
|
||||
view_settings = $7
|
||||
WHERE id = $1
|
||||
RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, created_at
|
||||
RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at
|
||||
`
|
||||
|
||||
type UpdateCollectionParams struct {
|
||||
@@ -6517,6 +6544,10 @@ func (q *Queries) UpdateCollection(ctx context.Context, arg UpdateCollectionPara
|
||||
&i.Icon,
|
||||
&i.AutoAssignRules,
|
||||
&i.ViewSettings,
|
||||
&i.ShowOnDashboard,
|
||||
&i.QueryType,
|
||||
&i.Priority,
|
||||
&i.IsSystemCollection,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
|
||||
Reference in New Issue
Block a user