legendary-doc-site/apps/content/lib/commands/update_menu.ex
2020-07-20 22:04:04 +00:00

178 lines
5.4 KiB
Elixir

defmodule Content.UpdateMenu do
alias Content.{Menu, Post, Postmeta, Repo, TermRelationship}
alias Ecto.Multi
import Ecto.Query
def run(id, new_menu_params) do
current_posts = Menu.nav_menu_items_for_id(id)
post_ids_in_new_menu = recursive_post_ids(new_menu_params)
deleted_post_ids =
current_posts
|> Enum.reject(& &1."ID" in post_ids_in_new_menu)
|> Enum.map(& &1."ID")
Multi.new()
|> process_nodes(id, 0, new_menu_params |> add_order())
|> Multi.delete_all(:stale_nodes, from(p in Post, where: p."ID" in ^deleted_post_ids))
|> Repo.transaction()
end
def add_order(tree) do
{_next_order, nodes} = add_order_starting_at(tree, 1)
nodes
end
def add_order_starting_at(tree, starting_at) do
tree
|> Enum.reduce({starting_at, []}, fn node, {order, new_nodes} ->
{next_order, new_child_nodes} = add_order_starting_at(node["children"], order + 1)
new_node =
node
|> Map.merge(%{
"order" => order,
"children" => new_child_nodes,
})
{next_order, new_nodes ++ [new_node]}
end)
end
defp process_nodes(multi, menu_id, parent_id, nodes) do
nodes
|> Enum.reduce(multi, fn node, m ->
case node["post_id"] do
nil ->
create_node(m, menu_id, parent_id, node)
_id ->
update_node(m, menu_id, parent_id, node)
end
end)
end
defp create_node(multi, menu_id, parent_id, node) do
post =
Post.changeset(
%Post{},
%{
post_author: 1,
post_title: node["title"],
post_status: "publish",
comment_status: "closed",
ping_status: "closed",
menu_order: node["order"],
post_type: "nav_menu_item",
comment_count: 0,
}
)
step_name = "#{parent_id}.create_node.#{node["order"]}"
multi
|> Multi.insert(step_name, post)
|> Multi.run("#{step_name}.term_relationship", fn _repo, %{^step_name => post} ->
tr =
TermRelationship.changeset(
%TermRelationship{},
%{
object_id: post."ID",
term_taxonomy_id: menu_id,
term_order: 0,
}
)
Repo.insert(tr)
end)
|> Multi.merge(fn %{^step_name => post} ->
Multi.new()
|> insert_metas(type_of_node(node), post, parent_id, node)
end)
|> Multi.merge(fn %{^step_name => post} ->
Multi.new()
|> process_nodes(menu_id, post."ID", node["children"])
end)
end
defp insert_metas(multi, "post", post, parent_id, node) do
multi
|> update_meta(post."ID", "_menu_item_type", "post_type")
|> update_meta(post."ID", "_menu_item_object", "page")
|> update_meta(post."ID", "_menu_item_object_id", node["target_id"])
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
end
defp insert_metas(multi, "category", post, parent_id, node) do
multi
|> update_meta(post."ID", "_menu_item_type", "taxonomy")
|> update_meta(post."ID", "_menu_item_object", "category")
|> update_meta(post."ID", "_menu_item_object_id", node["target_id"])
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
end
defp insert_metas(multi, "link", post, parent_id, node) do
multi
|> update_meta(post."ID", "_menu_item_type", "custom")
|> update_meta(post."ID", "_menu_item_object", "custom")
|> update_meta(post."ID", "_menu_item_object_id", post."ID")
|> update_meta(post."ID", "_menu_item_url", node["url"])
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
end
defp type_of_node(%{"url" => url}) when url != nil, do: "link"
defp type_of_node(%{"related_item" => %{"resource" => "posts"}}), do: "post"
defp type_of_node(%{"related_item" => %{"resource" => "category"}}), do: "category"
defp update_node(multi, menu_id, parent_id, node) do
multi
|> update_meta(node["post_id"], "_menu_item_menu_item_parent", parent_id)
|> update_order(node["post_id"], node["order"])
|> process_nodes(menu_id, node["post_id"], node["children"])
end
defp update_meta(multi, post_id, meta_key, new_value) when is_integer(new_value),
do: update_meta(multi, post_id, meta_key, new_value |> Integer.to_string())
defp update_meta(multi, post_id, meta_key, new_value) do
step_name = "#{post_id}.update_meta.#{meta_key}"
type = Postmeta.__schema__(:type, :meta_value)
cast_value = Ecto.Type.cast(type, new_value)
Postmeta
|> where([pm], pm.meta_key == ^meta_key)
|> where([pm], pm.post_id == ^post_id)
|> Repo.one()
|> case do
nil ->
multi
|> Multi.insert(
step_name,
Postmeta.changeset(
%Postmeta{},
%{
post_id: post_id,
meta_key: meta_key,
meta_value: new_value
}
)
)
%{meta_value: ^cast_value} ->
# No change needed
multi
meta ->
multi
|> Multi.update(step_name, Postmeta.changeset(meta, %{meta_value: new_value}))
end
end
defp update_order(multi, post_id, new_order) do
step_name = "#{post_id}.update_order"
multi
|> Multi.update_all(step_name, from(p in Post, where: p."ID" == ^post_id), [set: [menu_order: new_order]])
end
defp recursive_post_ids(params) do
params
|> Enum.flat_map(& [&1["post_id"]|recursive_post_ids(&1["children"])])
end
end