feat(db): add saved_filters table and CRUD operations
Add database schema and SQL queries for generic saved filters system that allows users to save custom filter presets for any resource type. Database Schema: - Add saved_filters table with user_id, name, resource_type, filters (JSONB) - Create composite index on (user_id, resource_type) for efficient lookups - Create index on (user_id, name) for future name search feature - Add update_updated_at_column() trigger to auto-update timestamps - Make trigger creation idempotent with DROP TRIGGER IF EXISTS SQL Queries (5 new queries): - GetSavedFilters: List all filters for user + resource type - GetSavedFilterByID: Retrieve single filter by ID - CreateSavedFilter: Create new saved filter - UpdateSavedFilter: Update filter name/criteria - DeleteSavedFilter: Remove saved filter Design Decisions: - Generic resource_type field supports any resource (media-items, collections, devices) - JSONB filters field allows flexible schema without migrations - User-scoped via JWT (user_id foreign key with CASCADE delete) - Automatic updated_at timestamp via database trigger Generated Code: - database.SavedFilters model (10 fields including JSONB filters) - All 5 CRUD query functions with proper parameter types - pgtype.UUID wrappers for UUID parameters Part of: Saved Filters Implementation (Phase 1: Database) Related: #saved-filters-feature
This commit is contained in:
@@ -960,6 +960,39 @@ func (q *Queries) CreateRefreshToken(ctx context.Context, arg CreateRefreshToken
|
||||
return i, err
|
||||
}
|
||||
|
||||
const CreateSavedFilter = `-- name: CreateSavedFilter :one
|
||||
INSERT INTO saved_filters (user_id, name, resource_type, filters)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, user_id, name, resource_type, filters, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateSavedFilterParams struct {
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
ResourceType string `db:"resource_type" json:"resource_type"`
|
||||
Filters []byte `db:"filters" json:"filters"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSavedFilter(ctx context.Context, arg CreateSavedFilterParams) (SavedFilters, error) {
|
||||
row := q.db.QueryRow(ctx, CreateSavedFilter,
|
||||
arg.UserID,
|
||||
arg.Name,
|
||||
arg.ResourceType,
|
||||
arg.Filters,
|
||||
)
|
||||
var i SavedFilters
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.Name,
|
||||
&i.ResourceType,
|
||||
&i.Filters,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const CreateSyncConflict = `-- name: CreateSyncConflict :one
|
||||
INSERT INTO sync_conflicts (media_item_id, user_id, conflict_type, conflict_data)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
@@ -1389,6 +1422,21 @@ func (q *Queries) DeleteReadingProgress(ctx context.Context, arg DeleteReadingPr
|
||||
return err
|
||||
}
|
||||
|
||||
const DeleteSavedFilter = `-- name: DeleteSavedFilter :exec
|
||||
DELETE FROM saved_filters
|
||||
WHERE id = $1 AND user_id = $2
|
||||
`
|
||||
|
||||
type DeleteSavedFilterParams struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) DeleteSavedFilter(ctx context.Context, arg DeleteSavedFilterParams) error {
|
||||
_, err := q.db.Exec(ctx, DeleteSavedFilter, arg.ID, arg.UserID)
|
||||
return err
|
||||
}
|
||||
|
||||
const DeleteSyncConflict = `-- name: DeleteSyncConflict :exec
|
||||
DELETE FROM sync_conflicts WHERE id = $1
|
||||
`
|
||||
@@ -4393,6 +4441,70 @@ func (q *Queries) GetRefreshToken(ctx context.Context, token pgtype.UUID) (GetRe
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetSavedFilterByID = `-- name: GetSavedFilterByID :one
|
||||
SELECT id, user_id, name, resource_type, filters, created_at, updated_at FROM saved_filters
|
||||
WHERE id = $1 AND user_id = $2
|
||||
`
|
||||
|
||||
type GetSavedFilterByIDParams struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetSavedFilterByID(ctx context.Context, arg GetSavedFilterByIDParams) (SavedFilters, error) {
|
||||
row := q.db.QueryRow(ctx, GetSavedFilterByID, arg.ID, arg.UserID)
|
||||
var i SavedFilters
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.Name,
|
||||
&i.ResourceType,
|
||||
&i.Filters,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetSavedFilters = `-- name: GetSavedFilters :many
|
||||
SELECT id, user_id, name, resource_type, filters, created_at, updated_at FROM saved_filters
|
||||
WHERE user_id = $1 AND resource_type = $2
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
type GetSavedFiltersParams struct {
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
ResourceType string `db:"resource_type" json:"resource_type"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetSavedFilters(ctx context.Context, arg GetSavedFiltersParams) ([]SavedFilters, error) {
|
||||
rows, err := q.db.Query(ctx, GetSavedFilters, arg.UserID, arg.ResourceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SavedFilters{}
|
||||
for rows.Next() {
|
||||
var i SavedFilters
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.Name,
|
||||
&i.ResourceType,
|
||||
&i.Filters,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetStuckSyncQueueItems = `-- name: GetStuckSyncQueueItems :many
|
||||
SELECT id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at FROM sync_queue
|
||||
WHERE status = 'processing' AND created_at < NOW() - INTERVAL '1 hour'
|
||||
@@ -8373,6 +8485,42 @@ func (q *Queries) UpdateReadingProgress(ctx context.Context, arg UpdateReadingPr
|
||||
return i, err
|
||||
}
|
||||
|
||||
const UpdateSavedFilter = `-- name: UpdateSavedFilter :one
|
||||
UPDATE saved_filters
|
||||
SET name = $1,
|
||||
filters = $2,
|
||||
updated_at = NOW()
|
||||
WHERE id = $3 AND user_id = $4
|
||||
RETURNING id, user_id, name, resource_type, filters, created_at, updated_at
|
||||
`
|
||||
|
||||
type UpdateSavedFilterParams struct {
|
||||
Name string `db:"name" json:"name"`
|
||||
Filters []byte `db:"filters" json:"filters"`
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSavedFilter(ctx context.Context, arg UpdateSavedFilterParams) (SavedFilters, error) {
|
||||
row := q.db.QueryRow(ctx, UpdateSavedFilter,
|
||||
arg.Name,
|
||||
arg.Filters,
|
||||
arg.ID,
|
||||
arg.UserID,
|
||||
)
|
||||
var i SavedFilters
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.Name,
|
||||
&i.ResourceType,
|
||||
&i.Filters,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const UpdateSyncQueueItemStatus = `-- name: UpdateSyncQueueItemStatus :one
|
||||
UPDATE sync_queue
|
||||
SET
|
||||
|
||||
Reference in New Issue
Block a user