defmodule Thesis.Notifications do @moduledoc """ Builds a map of Notifications that is passed to the client side. The key should be a representation of the category/type of notification (this is used to determine where and how it's displayed). The value is a list of notification strings. Example of final map: %{ "page-settings": [], "add-page": [], "import-export-restore": [], "other": [] } """ alias Thesis.Backup import Thesis.Config import Thesis.Utilities import Ecto.Query, only: [limit: 2] @allowed_notification_keys [ "page-settings", "add-page", "import-export-restore", "other", :page_settings, :add_page, :import_export_restore, :other ] def all(conn) do %{ "page-settings" => [], "add-page" => [], "import-export-restore" => [], "other" => [] } |> notifications_from_host_app(conn, notifications()) |> notifications_regarding_env(Application.get_env(:thesis, :env)) |> notifications_regarding_page(conn.assigns[:thesis_page]) end # TODO: add page specific-notifications def notifications_regarding_page(acc, nil), do: acc def notifications_regarding_page(acc, %_{}) do acc end def notifications_regarding_env(acc, :test), do: acc def notifications_regarding_env(acc, _) do acc |> notification_to_run_migration_for_version_0_3_0() end @doc """ Adds notifications provided by the host app to the accumulator. iex> notifications_from_host_app(%{"page-settings" => []}, nil, [page_settings: ["notif"]]) %{"page-settings" => ["notif"]} iex> notifications_from_host_app(%{"page-settings" => []}, nil, [page_settings: :invalid]) %{"page-settings" => []} iex> notifications_from_host_app(%{"page-settings" => ["notif1"]}, nil, [page_settings: ["notif2"]]) %{"page-settings" => ["notif2", "notif1"]} iex> notifications_from_host_app(%{"other" => [], "add-page" => ["notif"]}, nil, [other: ["notif"]]) %{"other" => ["notif"], "add-page" => ["notif"]} iex> notifications_from_host_app(%{"other" => []}, nil, nil) %{"other" => []} """ def notifications_from_host_app(acc, conn, notifications) when is_list(notifications) do case verify_notifications_structure(conn, notifications) do true -> Enum.reduce(notifications, acc, fn({type, list}, acc) -> update_notifications_map(acc, conn, type, list) end) _ -> acc end end def notifications_from_host_app(acc, _, _), do: acc @doc """ Verifies that the notifications provided by the host app are in the correct format. iex> verify_notifications_structure(nil, [:invalid]) false iex> verify_notifications_structure(nil, %{}) false iex> verify_notifications_structure(nil, %{invalid_key: []}) false iex> verify_notifications_structure(nil, %{add_page: "invalid value"}) false iex> verify_notifications_structure(nil, %{add_page: ["notif", "notif", :atom]}) false iex> verify_notifications_structure(nil, [add_page: [], page_settings: ["notif"]]) false """ def verify_notifications_structure(_conn, notifications) when is_list(notifications) and length(notifications) == 0, do: false def verify_notifications_structure(conn, notifications) when is_list(notifications) do case Keyword.keyword?(notifications) do true -> notifications |> Enum.map(fn(n) -> n |> is_notification_key_allowed?() |> is_notification_value_data_type_valid?() |> is_notification_value_valid?(conn) end) |> Enum.all?() _ -> false end end def verify_notifications_structure(_, _), do: false defp update_notifications_map(acc, conn, type, notifications_list) when is_function(notifications_list) do update_notifications_map(acc, conn, type, notifications_list.(conn)) end defp update_notifications_map(acc, conn, type, notifications_list) when is_atom(type) do update_notifications_map(acc, conn, parameterize(type), notifications_list) end defp update_notifications_map(acc, _conn, type, notifications_list) do %{acc | type => notifications_list ++ acc["#{type}"]} end # UTILITIES defp version() do {:ok, version} = :application.get_key(:thesis, :vsn) List.to_string(version) end defp is_notification_key_allowed?({k, _} = n) when k in @allowed_notification_keys, do: n defp is_notification_key_allowed?(_), do: false defp is_notification_value_data_type_valid?({_, v} = n) when is_list(v) or is_function(v), do: n defp is_notification_value_data_type_valid?(_), do: false defp is_notification_value_valid?({_, v}, _) when is_list(v) and length(v) == 0, do: false defp is_notification_value_valid?({k, v}, conn) when is_function(v) do is_notification_value_valid?({k, v.(conn)}, conn) end defp is_notification_value_valid?({_, v} = n, _) when is_list(v) do case is_list(v) and Enum.all?(v, &is_binary/1) do true -> n _ -> false end end defp is_notification_value_valid?(_, _), do: false # NOTIFICATIONS # remove in 0.3.1: reminder that revisions will not work without the new migration defp notification_to_run_migration_for_version_0_3_0(acc) do notification_to_run_migration_for_version_0_3_0(acc, Version.compare("0.3.0", version())) end defp notification_to_run_migration_for_version_0_3_0(acc, nil), do: acc defp notification_to_run_migration_for_version_0_3_0(acc, c) when is_atom(c) and c in [:eq, :gt] do notification_to_run_migration_for_version_0_3_0(acc, repo().one(limit(Backup, 1))) end defp notification_to_run_migration_for_version_0_3_0(acc, %{page_data: data}) do notification_to_run_migration_for_version_0_3_0(acc, String.valid?(data)) end defp notification_to_run_migration_for_version_0_3_0(acc, false) do update_notifications_map(acc, nil, "import-export-restore", ["Page revisions may not work! Please ask your developer to update Thesis."]) end defp notification_to_run_migration_for_version_0_3_0(acc, _), do: acc end