* feat(collections): add profile-scoped display filters
* refactor(collections): dedup display-filter helpers per review
Address code-review feedback on the profile-scoped display filters
without changing behavior:
- Widen CompletedHistoryItemMap to accept ProgressCompletionStore and
drop the duplicate completedHistoryItemMapForProgress copy.
- Extract the duplicated MDBList candidate retry loop into a generic
collectionutil.FetchMDBListWithFallback helper, used by both the user
and library collection syncers, and cover it with unit tests.
- Reuse validateOptionalLibraryIDs in HandleUpdateCollection instead of
an inline positive-ID loop.
- Import the shared COLLECTION_{WATCH,MEDIA}_FILTER_OPTIONS in the
template config form rather than redefining them locally.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(collections): sanitize query_definition library_ids fallback
readSourceConfigLibraryIDs validated source_config.library_ids (finite,
positive, truncated, deduplicated) but returned the query_definition
fallback raw, so legacy rows could surface zero/negative/duplicate IDs
that the backend now rejects on save. Extract a shared sanitizer and
apply it to both paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(docs): This makes the agents annoying to work with
* Improve playback session handling
* Support collection source order in catalog filters
* fix(collections): address display filter review feedback
* refactor(catalog): remove duplicate collection query params
* Hide episode media scope for collection overlays
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
3.6 KiB
Go
93 lines
3.6 KiB
Go
package usercollections
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// ServerVisibleCollection is the per-user view of a personal collection the
|
|
// owner has opted into their own library Collections tab. Personal collections
|
|
// are private to their owner; this list never crosses user boundaries.
|
|
type ServerVisibleCollection struct {
|
|
ID string `json:"id"`
|
|
CreatorProfileID string `json:"creator_profile_id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description,omitempty"`
|
|
CollectionType string `json:"collection_type"`
|
|
ItemCount int `json:"item_count"`
|
|
PosterPath string `json:"-"`
|
|
PosterURL string `json:"poster_url,omitempty"`
|
|
PosterThumbhash string `json:"poster_thumbhash,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
// serverVisibleListLimit caps how many opt-in collections a single library tab
|
|
// will surface so a user with hundreds of published collections can't blow up
|
|
// the response.
|
|
const serverVisibleListLimit = 500
|
|
|
|
// ListServerVisibleByLibrary returns the current user's personal collections
|
|
// that have opted into their library Collections tab and whose library scope
|
|
// matches the requested library. Imported exact collections read library_ids
|
|
// from source_config, while legacy rows can fall back to query_definition. A
|
|
// collection with no library_ids is treated as library-agnostic and therefore
|
|
// visible in every library tab. Other users' collections are never returned.
|
|
func ListServerVisibleByLibrary(ctx context.Context, pool *pgxpool.Pool, userID int, profileID string, libraryID int) ([]ServerVisibleCollection, error) {
|
|
rows, err := pool.Query(ctx,
|
|
`WITH visible_collections AS (
|
|
SELECT upc.id, upc.creator_profile_id, upc.name, upc.description, upc.collection_type,
|
|
upc.item_count, upc.poster_url, upc.poster_thumbhash, upc.created_at, upc.updated_at,
|
|
CASE
|
|
WHEN upc.collection_type = 'smart' THEN upc.query_definition
|
|
WHEN upc.source_config ? 'library_ids' THEN upc.source_config
|
|
ELSE upc.query_definition
|
|
END AS scope_config
|
|
FROM user_personal_collections upc
|
|
WHERE upc.user_id = $1
|
|
AND upc.include_in_server_collections = TRUE
|
|
AND EXISTS (
|
|
SELECT 1 FROM user_personal_collection_profiles vp
|
|
WHERE vp.user_id = upc.user_id
|
|
AND vp.collection_id = upc.id
|
|
AND vp.profile_id = $2
|
|
)
|
|
)
|
|
SELECT id, creator_profile_id, name, description, collection_type, item_count,
|
|
poster_url, poster_thumbhash, created_at, updated_at
|
|
FROM visible_collections
|
|
WHERE TRUE
|
|
AND (
|
|
NOT (scope_config ? 'library_ids')
|
|
OR jsonb_array_length(COALESCE(scope_config->'library_ids', '[]'::jsonb)) = 0
|
|
OR scope_config->'library_ids' @> to_jsonb($3::int)
|
|
)
|
|
ORDER BY name ASC
|
|
LIMIT $4`,
|
|
userID, profileID, libraryID, serverVisibleListLimit,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listing server-visible user collections: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []ServerVisibleCollection
|
|
for rows.Next() {
|
|
var c ServerVisibleCollection
|
|
var createdAt, updatedAt time.Time
|
|
if err := rows.Scan(
|
|
&c.ID, &c.CreatorProfileID, &c.Name, &c.Description, &c.CollectionType,
|
|
&c.ItemCount, &c.PosterPath, &c.PosterThumbhash, &createdAt, &updatedAt,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("scanning server-visible user collection: %w", err)
|
|
}
|
|
c.CreatedAt = createdAt.UTC().Format(time.RFC3339)
|
|
c.UpdatedAt = updatedAt.UTC().Format(time.RFC3339)
|
|
out = append(out, c)
|
|
}
|
|
return out, rows.Err()
|
|
}
|