300 lines
8.2 KiB
Elixir
300 lines
8.2 KiB
Elixir
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 and
|
|
fragment("CAST(? AS TEXT) = ?", post.status, ^normalized_status),
|
|
order_by: [asc: post.created_at]
|
|
)
|
|
)
|
|
|> 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
|
|
column = Atom.to_string(field)
|
|
|
|
%{rows: rows} =
|
|
Ecto.Adapters.SQL.query!(
|
|
Repo,
|
|
"SELECT trim(je.value) AS name, COUNT(*) AS cnt " <>
|
|
"FROM posts, json_each(posts.#{column}) je " <>
|
|
"WHERE posts.project_id = ?1 AND trim(je.value) != '' " <>
|
|
"GROUP BY name ORDER BY lower(name), cnt",
|
|
[project_id]
|
|
)
|
|
|
|
Enum.map(rows, fn [name, count] -> %{"name" => name, "count" => count} end)
|
|
end
|
|
end
|