PhoenixKitEcommerce. ProductSource. Catalogue. Query
(PhoenixKitEcommerce v0.5.11)
Copy Markdown
View Source
Ecto queries over phoenix_kit_catalogue's Item/Category schemas,
scoped to the one catalogue the shop reads (get_config("shop_catalogue"),
default name "decor3dprint", resolved through Catalogue.list_catalogues/0).
Returns raw catalogue structs — PhoenixKitEcommerce.ProductSource.Catalogue
turns them into view-structs via Catalogue.View. Nothing here is called
unless ProductSource.current/0 already picked this adapter, which
requires phoenix_kit_catalogue to be loaded; @compile {:no_warn_undefined, ...} only quietens the compiler's static xref
check for hosts that don't declare the optional dependency.
Summary
Functions
Facet counts for one attribute SET's values (set_slug — the set's
entities blueprint name, with or without the "catalogue_set_"
prefix, same lookup filter_by_metadata/2 uses), scoped to
storefront-visible items (active_visibility/1) in the shop
catalogue.
The uuid of the one catalogue the shop reads, or nil when it can't
be resolved (not yet bootstrapped, or the configured name matches
none). Resolved by name — a config read plus Catalogue.list_catalogues/0
— so every public function here accepts a pre-resolved value as
opts[:catalogue_uuid] (nil included, meaning "resolved, none")
and only falls back to this when the caller passed nothing;
ProductSource.Catalogue resolves it once per facade call and
threads it through, so a listing page pays for this once, not once
per query it composes.
Items belonging to one category that carry an image (own
featured_image_uuid or a non-empty media_order), {name, uuid}
pairs ordered by position then name — the same candidates
resolve_category_images/1's auto-detect step would pick the first
of. Backs the category form's featured-item picker (ShopSections. category/1), so an admin only ever sees items eligible to actually
supply the category's fallback image. Soft-deleted items are excluded;
otherwise unfiltered by shop status — an explicit pick is allowed to
name a draft item, same as featured_item_uuid always could as a raw
uuid.
Fetches one category by uuid, scoped to the shop catalogue.
Fetches one item by uuid, scoped to the shop catalogue. nil on a miss.
Lists the shop catalogue's categories. Options: :status/:parent_uuid
(see apply_category_filters/2), :search (the admin category search
box — matches the primary name and every language bucket's _name),
:catalogue_uuid (pre-resolved).
Fetches categories by uuid, scoped to the shop catalogue. Missing uuids dropped.
Lists items matching opts, ordered by position, name.
Fetches items by uuid, order preserved, missing uuids dropped, scoped
to the shop catalogue (same "one catalogue only" contract every other
read in this module enforces) — mirrors
PhoenixKitEcommerce.ProductSource.Legacy.list_products_by_ids/1.
list_items/1 plus the total count before pagination is applied.
Min/max base_price over active items, optionally scoped to a category.
Active-item counts grouped by category_uuid, "active" meaning
item.status == "active" and COALESCE(shop_status, 'active') = 'active' (spec principle 7, same fallback as the listing) — items
with no category are excluded, same as
ProductSource.Legacy.product_counts_by_category/0.
Resolves View.category_view/2's priority-2 image fallback for a batch
of categories, category_uuid => image uuid, in AT MOST two item
queries plus the one catalogue lookup they share — never one per
category (ProductSource.Catalogue.list_categories/1 builds a view for
every category in one pass, and the storefront/admin category lists have
no pagination ceiling on that count). The cost is fixed: it does not
grow with how many categories are passed in.
Translated display names for a batch of attribute-set BLUEPRINTS,
keyed by set uuid (AttributeSets.resolve_for_item/2's per-set :uuid
field) — ProductSource.Catalogue swaps a resolved set's :name for
this before handing sets to View.product_view/2 (which is pure and
has no way to read settings["translations"] itself). One
AttributeSets.get_set/2 call per DISTINCT set, never per item — a
product page has a handful of attached sets, and a listing page's many
items still share that same handful, so the count stays small; there
is no batched-by-uuid-list entities read to reach for instead. A set
that can't be resolved (deleted, or gated by get_set/2's own owner
check) is simply absent from the result — callers keep the
untranslated :name already on the resolved set for it.
Translated display name of one attribute set by its FILTER-CONFIG slug
(set_slug — with or without the catalogue_set_ prefix, same lookup
filter_by_metadata/2 uses), nil when the slug doesn't resolve to a
set. The sidebar's attribute_set/metadata_option filter section
reads this — a filter config only ever carries the slug, never the
set's uuid.
Distinct vendor values (from data["ecommerce"]["vendor"]) over
active items, with counts, optionally scoped to a category.
Functions
@spec attribute_set_counts(String.t(), keyword()) :: [ %{slug: String.t(), label: String.t(), count: non_neg_integer()} ]
Facet counts for one attribute SET's values (set_slug — the set's
entities blueprint name, with or without the "catalogue_set_"
prefix, same lookup filter_by_metadata/2 uses), scoped to
storefront-visible items (active_visibility/1) in the shop
catalogue.
Options: :category_uuid (scope to one category), :exclude_hidden_categories
(drop items whose category's shop_status is "hidden"), :language
(prefer data[language]["_title"] over the value's bare title —
the picker/sidebar's fuller per-language resolution is Block 5's
remaining work; this covers the plain value label), :catalogue_uuid
and :sets (pre-resolved by the caller — see catalogue_uuid/0 and
list_sets/0).
A value with no published EntityData row for the requested slug
never appears — draft/archived values (Block 5's resolver creates
unknown Shopify strings as draft) are excluded from storefront
facets by construction, not by a separate filter.
Ordered by the value's position, then its resolved label.
@spec catalogue_uuid() :: Ecto.UUID.t() | nil
The uuid of the one catalogue the shop reads, or nil when it can't
be resolved (not yet bootstrapped, or the configured name matches
none). Resolved by name — a config read plus Catalogue.list_catalogues/0
— so every public function here accepts a pre-resolved value as
opts[:catalogue_uuid] (nil included, meaning "resolved, none")
and only falls back to this when the caller passed nothing;
ProductSource.Catalogue resolves it once per facade call and
threads it through, so a listing page pays for this once, not once
per query it composes.
@spec category_item_image_options(Ecto.UUID.t() | nil) :: [ %{name: String.t(), uuid: Ecto.UUID.t(), image_uuid: Ecto.UUID.t()} ]
Items belonging to one category that carry an image (own
featured_image_uuid or a non-empty media_order), {name, uuid}
pairs ordered by position then name — the same candidates
resolve_category_images/1's auto-detect step would pick the first
of. Backs the category form's featured-item picker (ShopSections. category/1), so an admin only ever sees items eligible to actually
supply the category's fallback image. Soft-deleted items are excluded;
otherwise unfiltered by shop status — an explicit pick is allowed to
name a draft item, same as featured_item_uuid always could as a raw
uuid.
Fetches one category by uuid, scoped to the shop catalogue.
Fetches one item by uuid, scoped to the shop catalogue. nil on a miss.
@spec list_categories(keyword()) :: [PhoenixKitCatalogue.Schemas.Category.t()]
Lists the shop catalogue's categories. Options: :status/:parent_uuid
(see apply_category_filters/2), :search (the admin category search
box — matches the primary name and every language bucket's _name),
:catalogue_uuid (pre-resolved).
@spec list_categories_by_uuids([String.t()], keyword()) :: [ PhoenixKitCatalogue.Schemas.Category.t() ]
Fetches categories by uuid, scoped to the shop catalogue. Missing uuids dropped.
@spec list_items(keyword()) :: [PhoenixKitCatalogue.Schemas.Item.t()]
Lists items matching opts, ordered by position, name.
Fetches items by uuid, order preserved, missing uuids dropped, scoped
to the shop catalogue (same "one catalogue only" contract every other
read in this module enforces) — mirrors
PhoenixKitEcommerce.ProductSource.Legacy.list_products_by_ids/1.
@spec list_items_with_count(keyword()) :: {[PhoenixKitCatalogue.Schemas.Item.t()], non_neg_integer()}
list_items/1 plus the total count before pagination is applied.
Min/max base_price over active items, optionally scoped to a category.
@spec product_counts_by_category(keyword()) :: %{ required(String.t()) => non_neg_integer() }
Active-item counts grouped by category_uuid, "active" meaning
item.status == "active" and COALESCE(shop_status, 'active') = 'active' (spec principle 7, same fallback as the listing) — items
with no category are excluded, same as
ProductSource.Legacy.product_counts_by_category/0.
@spec resolve_category_images([PhoenixKitCatalogue.Schemas.Category.t()], keyword()) :: %{ required(Ecto.UUID.t()) => Ecto.UUID.t() }
Resolves View.category_view/2's priority-2 image fallback for a batch
of categories, category_uuid => image uuid, in AT MOST two item
queries plus the one catalogue lookup they share — never one per
category (ProductSource.Catalogue.list_categories/1 builds a view for
every category in one pass, and the storefront/admin category lists have
no pagination ceiling on that count). The cost is fixed: it does not
grow with how many categories are passed in.
For each category, the source is:
- An explicit
data["ecommerce"]["featured_item_uuid"]— that item's owndata["featured_image_uuid"], falling back to the first entry of itsdata["media_order"]. Resolved for every category that set one, in a singlelist_items_by_uuids/1call. - Otherwise, auto-detect: the first
active_visibility/1item in the category (ordered byposition, thenname) that carries an image by the same rule — exactly the old admin form's "Auto-detect (first product with image)" hint. Resolved for every OTHER category in one query fetching all their active items once, then walking the (already category/position-ordered) rows in Elixir to keep the first image-bearing one per category.
A category absent from the result has no image from either step (its
image_uuid, if any — priority 1 — is a category-view concern, not
this function's).
@spec set_display_names([Ecto.UUID.t()], String.t()) :: %{ required(Ecto.UUID.t()) => String.t() }
Translated display names for a batch of attribute-set BLUEPRINTS,
keyed by set uuid (AttributeSets.resolve_for_item/2's per-set :uuid
field) — ProductSource.Catalogue swaps a resolved set's :name for
this before handing sets to View.product_view/2 (which is pure and
has no way to read settings["translations"] itself). One
AttributeSets.get_set/2 call per DISTINCT set, never per item — a
product page has a handful of attached sets, and a listing page's many
items still share that same handful, so the count stays small; there
is no batched-by-uuid-list entities read to reach for instead. A set
that can't be resolved (deleted, or gated by get_set/2's own owner
check) is simply absent from the result — callers keep the
untranslated :name already on the resolved set for it.
Translated display name of one attribute set by its FILTER-CONFIG slug
(set_slug — with or without the catalogue_set_ prefix, same lookup
filter_by_metadata/2 uses), nil when the slug doesn't resolve to a
set. The sidebar's attribute_set/metadata_option filter section
reads this — a filter config only ever carries the slug, never the
set's uuid.
@spec vendor_counts(keyword()) :: [%{value: String.t(), count: non_neg_integer()}]
Distinct vendor values (from data["ecommerce"]["vendor"]) over
active items, with counts, optionally scoped to a category.