chore: refactored mcp.ex
This commit is contained in:
201
lib/bds/mcp/queries.ex
Normal file
201
lib/bds/mcp/queries.ex
Normal file
@@ -0,0 +1,201 @@
|
||||
defmodule BDS.MCP.Queries do
|
||||
@moduledoc false
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias BDS.MCP.Util
|
||||
alias BDS.PostLinks
|
||||
alias BDS.Posts.Post
|
||||
alias BDS.Posts.Translation, as: PostTranslation
|
||||
alias BDS.Projects
|
||||
alias BDS.Repo
|
||||
|
||||
@page_size 50
|
||||
|
||||
@spec page_size() :: pos_integer()
|
||||
def page_size, do: @page_size
|
||||
|
||||
@spec active_project!() :: Projects.Project.t()
|
||||
def active_project! do
|
||||
case Enum.find(Projects.list_projects(), & &1.is_active) do
|
||||
nil -> raise "no active project"
|
||||
project -> project
|
||||
end
|
||||
end
|
||||
|
||||
@spec post_summary(Post.t()) :: map()
|
||||
def post_summary(%Post{} = post) do
|
||||
%{
|
||||
"id" => post.id,
|
||||
"title" => post.title,
|
||||
"slug" => post.slug,
|
||||
"status" => Atom.to_string(post.status),
|
||||
"tags" => post.tags || [],
|
||||
"categories" => post.categories || [],
|
||||
"created_at" => post.created_at,
|
||||
"backlinks" => linked_posts(post.id, :incoming),
|
||||
"links_to" => linked_posts(post.id, :outgoing)
|
||||
}
|
||||
end
|
||||
|
||||
@spec post_detail(Post.t()) :: map()
|
||||
def post_detail(%Post{} = post) do
|
||||
post
|
||||
|> Util.sanitize()
|
||||
|> Map.put("content", post_body(post))
|
||||
|> Map.put("backlinks", linked_posts(post.id, :incoming))
|
||||
|> Map.put("links_to", linked_posts(post.id, :outgoing))
|
||||
|> Map.put("available_languages", available_languages(post.id, post.language))
|
||||
end
|
||||
|
||||
@spec translated_post_detail(Post.t(), String.t()) :: map()
|
||||
def translated_post_detail(%Post{} = post, language) do
|
||||
normalized_language = Util.normalize_term(language)
|
||||
|
||||
case Repo.get_by(PostTranslation, translation_for: post.id, language: normalized_language) do
|
||||
nil ->
|
||||
post_detail(post)
|
||||
|
||||
%PostTranslation{} = translation ->
|
||||
post_detail(post)
|
||||
|> Map.put("title", translation.title)
|
||||
|> Map.put("excerpt", translation.excerpt)
|
||||
|> Map.put("content", translation_body(translation))
|
||||
|> Map.put("language", translation.language)
|
||||
|> Map.put("canonical_language", post.language)
|
||||
end
|
||||
end
|
||||
|
||||
@spec search_filters(map()) :: map()
|
||||
def search_filters(params) do
|
||||
%{}
|
||||
|> Util.maybe_put(:category, Util.map_get(params, :category))
|
||||
|> Util.maybe_put(:tags, Util.map_get(params, :tags))
|
||||
|> Util.maybe_put(:language, Util.map_get(params, :language))
|
||||
|> Util.maybe_put(:missing_translation_language, Util.map_get(params, :missingTranslationLanguage))
|
||||
|> Util.maybe_put(:year, Util.map_get(params, :year))
|
||||
|> Util.maybe_put(:month, Util.map_get(params, :month))
|
||||
|> Util.maybe_put(:status, parse_status(Util.map_get(params, :status)))
|
||||
|> Map.put(:offset, Util.map_get(params, :offset, 0))
|
||||
|> Map.put(:limit, Util.map_get(params, :limit, @page_size))
|
||||
end
|
||||
|
||||
@spec parse_status(term()) :: atom() | nil
|
||||
def parse_status(nil), do: nil
|
||||
def parse_status(status) when is_atom(status), do: status
|
||||
def parse_status(status) when is_binary(status), do: String.to_existing_atom(status)
|
||||
|
||||
@spec group_rows(Post.t(), [String.t()]) :: [map()]
|
||||
def group_rows(_post, []), do: [%{}]
|
||||
|
||||
def group_rows(post, [dimension | rest]) do
|
||||
values = group_values(post, dimension)
|
||||
|
||||
for value <- values,
|
||||
tail <- group_rows(post, rest) do
|
||||
Map.put(tail, dimension, value)
|
||||
end
|
||||
end
|
||||
|
||||
defp group_values(post, "year") do
|
||||
[BDS.Persistence.from_unix_ms!(post.created_at).year]
|
||||
end
|
||||
|
||||
defp group_values(post, "month") do
|
||||
[BDS.Persistence.from_unix_ms!(post.created_at).month]
|
||||
end
|
||||
|
||||
defp group_values(post, "tag") do
|
||||
if Enum.empty?(post.tags || []), do: [nil], else: post.tags
|
||||
end
|
||||
|
||||
defp group_values(post, "category") do
|
||||
if Enum.empty?(post.categories || []), do: [nil], else: post.categories
|
||||
end
|
||||
|
||||
defp group_values(post, "status"), do: [Atom.to_string(post.status)]
|
||||
|
||||
@spec available_languages(String.t(), String.t() | nil) :: [String.t()]
|
||||
def available_languages(post_id, canonical_language) do
|
||||
languages =
|
||||
Repo.all(
|
||||
from translation in PostTranslation,
|
||||
where: translation.translation_for == ^post_id,
|
||||
select: translation.language
|
||||
)
|
||||
|
||||
([canonical_language] ++ languages)
|
||||
|> Enum.reject(&(&1 in [nil, ""]))
|
||||
|> Enum.uniq()
|
||||
end
|
||||
|
||||
@spec linked_posts(String.t(), :incoming | :outgoing) :: [map()]
|
||||
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
|
||||
|
||||
@spec post_body(Post.t()) :: String.t()
|
||||
def post_body(%Post{content: content}) when is_binary(content), do: content
|
||||
|
||||
def post_body(%Post{} = post) do
|
||||
project = Projects.get_project!(post.project_id)
|
||||
full_path = Path.join(Projects.project_data_dir(project), post.file_path || "")
|
||||
|
||||
case File.read(full_path) do
|
||||
{:ok, contents} ->
|
||||
case String.split(contents, "\n---\n", parts: 2) do
|
||||
[_frontmatter, body] -> String.trim_trailing(body, "\n")
|
||||
_parts -> contents
|
||||
end
|
||||
|
||||
{:error, _reason} ->
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
@spec translation_body(PostTranslation.t()) :: String.t()
|
||||
def translation_body(%PostTranslation{content: content}) when is_binary(content), do: content
|
||||
|
||||
def translation_body(%PostTranslation{} = translation) do
|
||||
project = Projects.get_project!(translation.project_id)
|
||||
full_path = Path.join(Projects.project_data_dir(project), translation.file_path || "")
|
||||
|
||||
case File.read(full_path) do
|
||||
{:ok, contents} ->
|
||||
case String.split(contents, "\n---\n", parts: 2) do
|
||||
[_frontmatter, body] -> String.trim_trailing(body, "\n")
|
||||
_parts -> contents
|
||||
end
|
||||
|
||||
{:error, _reason} ->
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
@spec tag_post_count(String.t(), String.t()) :: non_neg_integer()
|
||||
def tag_post_count(project_id, tag_name) do
|
||||
Repo.all(from post in Post, where: post.project_id == ^project_id)
|
||||
|> Enum.count(&(tag_name in (&1.tags || [])))
|
||||
end
|
||||
|
||||
@spec category_post_count(String.t(), String.t()) :: non_neg_integer()
|
||||
def category_post_count(project_id, category) do
|
||||
Repo.all(from post in Post, where: post.project_id == ^project_id)
|
||||
|> Enum.count(&(category in (&1.categories || [])))
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user