chore: next big god module down
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
270
lib/bds/scripting/capabilities/posts.ex
Normal file
270
lib/bds/scripting/capabilities/posts.ex
Normal file
@@ -0,0 +1,270 @@
|
||||
defmodule BDS.Scripting.Capabilities.Posts do
|
||||
@moduledoc false
|
||||
|
||||
import Ecto.Query
|
||||
import BDS.Scripting.Capabilities.Util
|
||||
|
||||
alias BDS.PostLinks
|
||||
alias BDS.Posts
|
||||
alias BDS.Posts.Post
|
||||
alias BDS.Posts.Translation, as: PostTranslation
|
||||
alias BDS.Preview
|
||||
alias BDS.Repo
|
||||
alias BDS.Search
|
||||
|
||||
def create_post(project_id, attrs) do
|
||||
attrs
|
||||
|> normalize_map()
|
||||
|> Map.put("project_id", project_id)
|
||||
|> Posts.create_post()
|
||||
|> unwrap_result(&post_payload/1)
|
||||
end
|
||||
|
||||
def update_post(project_id, post_id, attrs) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{} -> Posts.update_post(post_id, normalize_map(attrs)) |> unwrap_result(&post_payload/1)
|
||||
_other -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def delete_post(project_id, post_id) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{} -> boolean_result(Posts.delete_post(post_id))
|
||||
_other -> false
|
||||
end
|
||||
end
|
||||
|
||||
def load_post(project_id, post_id) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{} = post -> post_payload(post)
|
||||
_other -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def list_posts(project_id) do
|
||||
Repo.all(from(post in Post, where: post.project_id == ^project_id, order_by: [asc: post.created_at]))
|
||||
|> Enum.map(&post_payload/1)
|
||||
end
|
||||
|
||||
def load_post_by_slug(project_id, slug) do
|
||||
Repo.one(
|
||||
from(post in Post,
|
||||
where: post.project_id == ^project_id and post.slug == ^(string_or_nil(slug) || ""),
|
||||
limit: 1
|
||||
)
|
||||
)
|
||||
|> case do
|
||||
%Post{} = post -> post_payload(post)
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def publish_post(project_id, post_id) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{} -> Posts.publish_post(post_id) |> unwrap_result(&post_payload/1)
|
||||
_other -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def discard_post(project_id, post_id) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{} -> Posts.discard_post_changes(post_id) |> unwrap_result(&post_payload/1)
|
||||
_other -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def filter_posts(project_id, filters) do
|
||||
project_id
|
||||
|> Search.search_posts("", normalize_search_filters(filters))
|
||||
|> unwrap_result(fn %{posts: posts} -> Enum.map(posts, &post_payload/1) end)
|
||||
end
|
||||
|
||||
def generate_unique_post_slug(project_id, title, exclude_post_id) do
|
||||
Posts.unique_slug_for_title(project_id, string_or_nil(title) || "", string_or_nil(exclude_post_id))
|
||||
end
|
||||
|
||||
def posts_by_status(project_id, status) do
|
||||
normalized_status = string_or_nil(status) || ""
|
||||
|
||||
Repo.all(from(post in Post, where: post.project_id == ^project_id, order_by: [asc: post.created_at]))
|
||||
|> Enum.filter(&(to_string(&1.status) == normalized_status))
|
||||
|> Enum.map(&post_payload/1)
|
||||
end
|
||||
|
||||
def post_counts_by_year_month(project_id) do
|
||||
Posts.post_counts_by_year_month(project_id)
|
||||
|> sanitize()
|
||||
end
|
||||
|
||||
def post_dashboard_stats(project_id) do
|
||||
Posts.dashboard_stats(project_id)
|
||||
|> sanitize()
|
||||
end
|
||||
|
||||
def linked_posts_for(project_id, post_id, direction) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{id: id} -> linked_posts(id, direction)
|
||||
_other -> []
|
||||
end
|
||||
end
|
||||
|
||||
def preview_url(project_id, post_id, options) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{} = post ->
|
||||
with {:ok, server} <- Preview.start_preview(project_id) do
|
||||
base_url = "http://#{server.host}:#{server.port}"
|
||||
canonical_path = canonical_preview_path(post.created_at, post.slug)
|
||||
options = normalize_map(options)
|
||||
language = options |> Map.get("lang") |> string_or_nil() |> blank_to_nil()
|
||||
|
||||
query =
|
||||
%{}
|
||||
|> maybe_put_query("draft", truthy?(Map.get(options, "draft")) && "true")
|
||||
|> maybe_put_query("post_id", truthy?(Map.get(options, "draft")) && post.id)
|
||||
|> maybe_put_query("lang", language)
|
||||
|
||||
if map_size(query) == 0 do
|
||||
base_url <> canonical_path
|
||||
else
|
||||
base_url <> canonical_path <> "?" <> URI.encode_query(query)
|
||||
end
|
||||
else
|
||||
_other -> nil
|
||||
end
|
||||
|
||||
_other ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def post_slug_available?(project_id, slug, exclude_post_id) do
|
||||
Posts.slug_available(project_id, string_or_nil(slug) || "", string_or_nil(exclude_post_id))
|
||||
end
|
||||
|
||||
def publish_post_translation(project_id, post_id, language) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{} -> Posts.publish_post_translation(post_id, string_or_nil(language) || "") |> unwrap_result()
|
||||
_other -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def rebuild_post_links(project_id) do
|
||||
case Posts.rebuild_post_links(project_id) do
|
||||
:ok -> true
|
||||
end
|
||||
end
|
||||
|
||||
def rebuild_posts_from_files(project_id) do
|
||||
project_id
|
||||
|> Posts.rebuild_posts_from_files()
|
||||
|> unwrap_result(fn posts -> Enum.map(posts, &post_payload/1) end)
|
||||
end
|
||||
|
||||
def reindex_project_search(project_id) do
|
||||
case Search.reindex_project(project_id) do
|
||||
:ok -> true
|
||||
end
|
||||
end
|
||||
|
||||
def search_posts(project_id, query) do
|
||||
project_id
|
||||
|> Search.search_posts(string_or_nil(query) || "")
|
||||
|> unwrap_result(fn %{posts: posts} -> Enum.map(posts, &post_payload/1) end)
|
||||
end
|
||||
|
||||
def post_tags(project_id), do: names_with_counts(project_id, :tags) |> Enum.map(& &1["name"])
|
||||
def post_tags_with_counts(project_id), do: names_with_counts(project_id, :tags)
|
||||
def post_categories(project_id), do: names_with_counts(project_id, :categories) |> Enum.map(& &1["name"])
|
||||
def post_categories_with_counts(project_id), do: names_with_counts(project_id, :categories)
|
||||
|
||||
def list_post_translations(project_id, post_id) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{id: id} ->
|
||||
id
|
||||
|> Posts.list_post_translations()
|
||||
|> unwrap_result(fn translations -> Enum.map(translations, &sanitize/1) end)
|
||||
|
||||
_other ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def load_post_translation(project_id, post_id, language) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{id: id} ->
|
||||
Repo.one(
|
||||
from(translation in PostTranslation,
|
||||
where:
|
||||
translation.translation_for == ^id and
|
||||
translation.language == ^(string_or_nil(language) || ""),
|
||||
limit: 1
|
||||
)
|
||||
)
|
||||
|> sanitize_nilable()
|
||||
|
||||
_other ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def has_published_post_version(project_id, post_id) do
|
||||
case fetch_post(project_id, post_id) do
|
||||
%Post{status: :published} -> true
|
||||
%Post{published_at: published_at, file_path: file_path} -> not is_nil(published_at) or file_path not in [nil, ""]
|
||||
_other -> false
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_post(project_id, post_id) do
|
||||
Repo.one(
|
||||
from(post in Post,
|
||||
where: post.project_id == ^project_id and post.id == ^(string_or_nil(post_id) || ""),
|
||||
limit: 1
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def post_payload(%Post{} = post) do
|
||||
post
|
||||
|> sanitize()
|
||||
|> Map.put("backlinks", linked_posts(post.id, :incoming))
|
||||
|> Map.put("links_to", linked_posts(post.id, :outgoing))
|
||||
end
|
||||
|
||||
def linked_posts(post_id, :incoming) do
|
||||
PostLinks.list_incoming_links(post_id)
|
||||
|> Enum.map(&load_linked_post(&1.source_post_id))
|
||||
|> Enum.reject(&is_nil/1)
|
||||
end
|
||||
|
||||
def linked_posts(post_id, :outgoing) do
|
||||
PostLinks.list_outgoing_links(post_id)
|
||||
|> Enum.map(&load_linked_post(&1.target_post_id))
|
||||
|> Enum.reject(&is_nil/1)
|
||||
end
|
||||
|
||||
defp load_linked_post(post_id) do
|
||||
case Repo.get(Post, post_id) do
|
||||
%Post{} = post -> %{"id" => post.id, "title" => post.title, "slug" => post.slug}
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp canonical_preview_path(created_at_ms, slug) do
|
||||
datetime = DateTime.from_unix!(created_at_ms, :millisecond)
|
||||
"/#{datetime.year}/#{pad2(datetime.month)}/#{pad2(datetime.day)}/#{string_or_nil(slug) || ""}"
|
||||
end
|
||||
|
||||
def names_with_counts(project_id, field) when field in [:tags, :categories] do
|
||||
Repo.all(
|
||||
from(post in Post,
|
||||
where: post.project_id == ^project_id,
|
||||
order_by: [asc: post.created_at]
|
||||
)
|
||||
)
|
||||
|> Enum.flat_map(&(Map.get(&1, field) || []))
|
||||
|> Enum.reduce(%{}, fn name, acc -> Map.update(acc, name, 1, &(&1 + 1)) end)
|
||||
|> Enum.map(fn {name, count} -> %{"name" => name, "count" => count} end)
|
||||
|> Enum.sort_by(&{String.downcase(&1["name"]), &1["count"]})
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user