Packages

phoenix_kit

1.7.34
1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules sitemap sources shop.ex
Raw

lib/modules/sitemap/sources/shop.ex

defmodule PhoenixKit.Modules.Sitemap.Sources.Shop do
@moduledoc """
Shop source for sitemap generation.
Collects catalog page, active category pages, and active product pages
from the PhoenixKit Shop module for inclusion in the sitemap.
## URL Structure
- Catalog: `/shop` (or `/et/shop` for non-default language)
- Categories: `/shop/category/:slug` (or `/et/shop/category/:slug`)
- Products: `/shop/product/:slug` (or `/et/shop/product/:slug`)
## Excluded URLs
- `/cart` — user-specific
- `/checkout` — user-specific
- `/checkout/complete/:uuid` — user-specific
## Enabling
This source is enabled when:
1. Shop module is enabled (`shop_enabled` setting)
2. Shop sitemap inclusion is enabled (`sitemap_include_shop` setting, default: true)
## Sitemap Properties
- Catalog page: priority 0.8, changefreq "daily", category "Shop"
- Categories: priority 0.7, changefreq "weekly", category "Shop > Categories"
- Products: priority 0.8, changefreq "weekly", category "Shop > Products"
"""
@behaviour PhoenixKit.Modules.Sitemap.Sources.Source
require Logger
alias PhoenixKit.Modules.Languages
alias PhoenixKit.Modules.Shop
alias PhoenixKit.Modules.Sitemap.UrlEntry
alias PhoenixKit.Settings
@impl true
def source_name, do: :shop
@impl true
def enabled? do
Shop.enabled?() and
Settings.get_boolean_setting("sitemap_include_shop", true)
rescue
_ -> false
end
@impl true
def collect(opts \\ []) do
if enabled?() do
do_collect(opts)
else
[]
end
rescue
error ->
Logger.warning("Shop sitemap source failed to collect: #{inspect(error)}")
[]
end
defp do_collect(opts) do
base_url = Keyword.get(opts, :base_url)
language = Keyword.get(opts, :language, default_language())
is_default = Keyword.get(opts, :is_default_language, true)
catalog_entries(base_url, language, is_default) ++
category_entries(base_url, language, is_default) ++
product_entries(base_url, language, is_default)
end
# Catalog page entry (/shop)
defp catalog_entries(base_url, language, is_default) do
canonical_path = "/shop"
url = build_url(Shop.catalog_url(language), base_url)
[
UrlEntry.new(%{
loc: url,
lastmod: nil,
changefreq: "daily",
priority: 0.8,
title: "Shop",
category: "Shop",
source: :shop,
canonical_path: build_canonical(canonical_path, language, is_default)
})
]
end
# Active category entries (/shop/category/:slug)
defp category_entries(base_url, language, is_default) do
Shop.list_active_categories()
|> Enum.filter(&has_slug?(&1, language))
|> Enum.map(fn category ->
url = build_url(Shop.category_url(category, language), base_url)
canonical_path = "/shop/category/#{category_canonical_slug(category)}"
UrlEntry.new(%{
loc: url,
lastmod: category.updated_at,
changefreq: "weekly",
priority: 0.7,
title: category_name(category, language),
category: "Shop > Categories",
source: :shop,
canonical_path: build_canonical(canonical_path, language, is_default)
})
end)
rescue
error ->
Logger.warning("Failed to collect shop categories: #{inspect(error)}")
[]
end
# Active product entries (/shop/product/:slug)
defp product_entries(base_url, language, is_default) do
Shop.list_products(status: "active")
|> Enum.filter(&has_slug?(&1, language))
|> Enum.map(fn product ->
url = build_url(Shop.product_url(product, language), base_url)
canonical_path = "/shop/product/#{product_canonical_slug(product)}"
UrlEntry.new(%{
loc: url,
lastmod: product.updated_at,
changefreq: "weekly",
priority: 0.8,
title: product_title(product, language),
category: "Shop > Products",
source: :shop,
canonical_path: build_canonical(canonical_path, language, is_default)
})
end)
rescue
error ->
Logger.warning("Failed to collect shop products: #{inspect(error)}")
[]
end
# Extract display name from localized category name map
defp category_name(category, language) do
case category.name do
%{} = names ->
dialect = Languages.DialectMapper.base_to_dialect(language)
Map.get(names, language) || Map.get(names, dialect) || first_value(names)
name when is_binary(name) ->
name
_ ->
"Category"
end
end
# Extract display title from localized product title map
defp product_title(product, language) do
case product.title do
%{} = titles ->
dialect = Languages.DialectMapper.base_to_dialect(language)
Map.get(titles, language) || Map.get(titles, dialect) || first_value(titles)
title when is_binary(title) ->
title
_ ->
"Product"
end
end
# Get canonical slug (default language or first available) for canonical_path.
# Slug field is always a map (localized slugs) or nil.
defp category_canonical_slug(category) do
case category.slug do
%{} = slugs ->
default = default_language()
dialect = Languages.DialectMapper.base_to_dialect(default)
Map.get(slugs, default) || Map.get(slugs, dialect) || first_value(slugs) ||
to_string(category.id)
_ ->
to_string(category.id)
end
end
defp product_canonical_slug(product) do
case product.slug do
%{} = slugs ->
default = default_language()
dialect = Languages.DialectMapper.base_to_dialect(default)
Map.get(slugs, default) || Map.get(slugs, dialect) || first_value(slugs) ||
to_string(product.id)
_ ->
to_string(product.id)
end
end
# Check if entity has a slug for the given language (base code, dialect, or raw key)
defp has_slug?(entity, language) do
slug_map = entity.slug || %{}
base = Languages.DialectMapper.extract_base(language)
dialect = Languages.DialectMapper.base_to_dialect(language)
Map.has_key?(slug_map, language) or
Map.has_key?(slug_map, base) or
Map.has_key?(slug_map, dialect)
end
defp first_value(map) when map_size(map) > 0 do
map |> Map.values() |> List.first()
end
defp first_value(_), do: nil
# Build canonical path only for default language entries (used for hreflang grouping)
defp build_canonical(path, _language, true), do: path
defp build_canonical(path, _language, _is_default), do: path
defp default_language do
if Languages.enabled?() do
case Languages.get_default_language() do
%{"code" => code} -> Languages.DialectMapper.extract_base(code)
%{code: code} -> Languages.DialectMapper.extract_base(code)
_ -> "en"
end
else
"en"
end
rescue
_ -> "en"
end
# Build full URL: if the path from Shop.*_url already includes the base,
# just return it; otherwise prepend base_url
defp build_url(path, nil) do
base = Settings.get_setting("site_url", "")
normalized_base = String.trim_trailing(base, "/")
"#{normalized_base}#{path}"
end
defp build_url(path, base_url) when is_binary(base_url) do
normalized_base = String.trim_trailing(base_url, "/")
"#{normalized_base}#{path}"
end
end