Files
bDS2/lib/bds/posts/slugs.ex
2026-05-01 09:52:11 +02:00

87 lines
2.3 KiB
Elixir

defmodule BDS.Posts.Slugs do
@moduledoc false
import Ecto.Query
alias BDS.Posts.Post
alias BDS.Repo
alias BDS.Slug
@spec available(String.t(), String.t(), String.t() | nil) :: boolean()
def available(project_id, slug, exclude_post_id \\ nil) do
normalized_slug = slug |> to_string() |> String.trim()
query =
from(post in Post,
where: post.project_id == ^project_id and post.slug == ^normalized_slug,
select: post.id,
limit: 1
)
case Repo.one(query) do
nil -> true
^exclude_post_id -> true
_other -> false
end
end
@spec unique_for_title(String.t(), String.t(), String.t() | nil) :: String.t()
def unique_for_title(project_id, title, exclude_post_id \\ nil) do
base_slug = title |> default_source() |> Slug.slugify()
if available(project_id, base_slug, exclude_post_id) do
base_slug
else
Stream.iterate(2, &(&1 + 1))
|> Enum.find_value(fn counter ->
candidate = "#{base_slug}-#{counter}"
if available(project_id, candidate, exclude_post_id), do: candidate, else: nil
end)
end
end
@doc "Pick a free slug, falling back to `untitled` for blank input."
@spec unique(String.t(), String.t()) :: String.t()
def unique(project_id, base_slug) do
normalized = if base_slug == "", do: "untitled", else: base_slug
if available?(project_id, normalized) do
normalized
else
find_unique(project_id, normalized, 2)
end
end
@doc "Pick a free slug for an imported post by re-slugifying the source value."
@spec unique_for_import(String.t(), String.t()) :: String.t()
def unique_for_import(project_id, slug) do
normalized = slug |> default_source() |> Slug.slugify()
if available?(project_id, normalized) do
normalized
else
find_unique(project_id, normalized, 2)
end
end
@spec default_source(String.t()) :: String.t()
def default_source(""), do: "untitled"
def default_source(title), do: title
defp find_unique(project_id, base_slug, suffix) do
candidate = "#{base_slug}-#{suffix}"
if available?(project_id, candidate) do
candidate
else
find_unique(project_id, base_slug, suffix + 1)
end
end
defp available?(project_id, slug) do
not Repo.exists?(
from post in Post, where: post.project_id == ^project_id and post.slug == ^slug
)
end
end