legendary-doc-site/apps/admin/kaffy/lib/kaffy/utils.ex
2020-07-27 20:28:41 +00:00

405 lines
10 KiB
Elixir

defmodule Kaffy.Utils do
@moduledoc false
@doc """
Returns the :admin_title config if present, otherwise returns "Kaffy"
"""
@spec title() :: String.t()
def title() do
env(:admin_title, "Kaffy")
end
@doc """
Returns the JSON package used by phoenix configs. If no such config exists, raise an exception.
"""
@spec json() :: atom()
def json() do
case Application.get_env(:phoenix, :json_library) do
nil ->
raise "A json package must be configured. For example: config :phoenix, :json_library, Jason"
j ->
j
end
end
@doc """
Returns the Repo from Kaffy configs. If it is not present, raise an exception.
"""
@spec repo() :: atom()
def repo() do
case env(:ecto_repo) do
nil -> raise "Must define :ecto_repo for Kaffy to work properly."
r -> r
end
end
@doc """
Returns the version of the provided app.
Example:
> get_version_of(:phoenix)
> "1.5.3"
"""
@spec get_version_of(atom()) :: String.t()
def get_version_of(package) do
{:ok, version} = :application.get_key(package, :vsn)
to_string(version)
end
@doc """
Returns true when phoenix's version has the same prefix as the provided argument, false otherwise.
Example:
phoenix_version?("1.4.")
> true # returns true for all phoenix 1.4.x versions
"""
@spec phoenix_version?(String.t()) :: boolean()
def phoenix_version?(prefix) do
version = get_version_of(:phoenix)
String.starts_with?(version, prefix)
end
@doc """
Returns the router helper module from the configs. Raises if the router isn't specified.
"""
@spec router() :: atom()
def router() do
case env(:router) do
nil -> raise "The :router config must be specified: config :kaffy, router: MyAppWeb.Router"
r -> r
end
|> Module.concat(Helpers)
end
@doc """
Returns a keyword list of all the resources specified in config.exs.
If the :resources key isn't specified, this function will load all application modules,
filters the schemas modules, combine them into a keyword list, and returns that list.
Example:
```elixir
full_resources()
[
categories: [
schemas: [
category: [
schema: Bakery.Categories.Category,
admin: Bakery.Categories.CategoryAdmin
]
]
]
]
```
"""
@spec full_resources(Plug.Conn.t()) :: [any()]
def full_resources(conn) do
case env(:resources) do
f when is_function(f) -> f.(conn)
l when is_list(l) -> l
_ -> setup_resources()
end
end
@doc """
Returns a list of contexts as atoms.
Example:
iex> contexts()
[:blog, :products, :users]
"""
@spec contexts(Plug.Conn.t()) :: [atom()]
def contexts(conn) do
full_resources(conn)
|> Enum.map(fn {context, _options} -> context end)
end
@doc """
Returns the context name based on the configs.
Example:
```elixir
context = [
categories: [
schemas: [
category: [schema: Bakery.Categories.Category]
]
]
]
context_name(context)
> "Categories"
context = [
categories: [
name: "Types",
schemas: [
category: [schema: Bakery.Categories.Category]
]
]
]
context_name(context)
> "Types"
```
"""
@spec context_name(Plug.Conn.t(), list()) :: String.t()
def context_name(conn, context) do
default = Kaffy.ResourceAdmin.humanize_term(context)
get_in(full_resources(conn), [context, :name]) || default
end
@doc """
Returns the context list from the configs for a specific schema.
This is usually used to get the name or other information of the schema context.
"""
@spec get_context_for_schema(Plug.Conn.t(), module()) :: list()
def get_context_for_schema(conn, schema) do
contexts(conn)
|> Enum.filter(fn c ->
schemas = Enum.map(schemas_for_context(conn, c), fn {_k, v} -> Keyword.get(v, :schema) end)
schema in schemas
end)
|> Enum.at(0)
end
def get_schema_key(conn, context, schema) do
schemas_for_context(conn, context)
|> Enum.reduce([], fn {k, v}, keys ->
case schema == Keyword.get(v, :schema) do
true -> [k | keys]
false -> keys
end
end)
|> Enum.at(0)
end
@doc """
Returns the resource entry from the configs.
Example:
iex> get_resource("blog", "post")
[schema: MyApp.Blog.Post, admin: MyApp.Blog.PostAdmin]
"""
@spec get_resource(Plug.Conn.t(), String.t(), String.t()) :: list()
def get_resource(conn, context, resource) do
{context, resource} = convert_to_atoms(context, resource)
get_in(full_resources(conn), [context, :resources, resource])
end
@doc """
Returns all the schemas for the given context.
Example:
iex> schemas_for_context("blog")
[
post: [schema: MyApp.Blog.Post, admin: MyApp.Blog.PostAdmin],
comment: [schema: MyApp.Blog.Comment],
]
"""
@spec schemas_for_context(Plug.Conn.t(), list()) :: list()
def schemas_for_context(conn, context) do
context = convert_to_atom(context)
get_in(full_resources(conn), [context, :resources])
end
# @doc """
# Get the schema for the provided context/resource combination.
# iex> schema_for_resource("blog", "post")
# MyApp.Blog.Post
# """
# @spec schema_for_resource(String.t(), String.t()) :: module()
# def schema_for_resource(context, resource) do
# {context, resource} = convert_to_atoms(context, resource)
# get_in(full_resources(), [context, :schemas, resource, :schema])
# end
# @doc """
# Like schema_for_resource/2, but returns the admin module, or nil if an admin module doesn't exist.
# iex> admin-fro_resource("blog", "post")
# MyApp.Blog.PostAdmin
# """
# @spec admin_for_resource(String.t(), String.t()) :: module() | nil
# def admin_for_resource(context, resource) do
# {context, resource} = convert_to_atoms(context, resource)
# get_in(full_resources(), [context, :schemas, resource, :admin])
# end
def get_assigned_value_or_default(resource, function, default, params \\ [], add_schema \\ true) do
admin = resource[:admin]
schema = resource[:schema]
arguments = if add_schema, do: [schema] ++ params, else: params
case !is_nil(admin) && has_function?(admin, function) do
true -> apply(admin, function, arguments)
false -> default
end
end
@doc """
Returns true if the given module implements the given function, false otherwise.
iex> has_function?(MyApp.Blog.PostAdmin, :form_fields)
true
"""
@spec has_function?(module(), atom()) :: boolean()
def has_function?(admin, func) do
functions = admin.__info__(:functions)
Keyword.has_key?(functions, func)
end
@doc """
Returns true if `thing` is a module, false otherwise.
"""
@spec is_module(module()) :: boolean()
def is_module(thing), do: is_atom(thing) && function_exported?(thing, :__info__, 1)
@doc """
Returns whether the dashbaord link should be displayed or hidden. Default behavior is to show the dashboard link.
This option is taken from the :hide_dashboard config option.
iex> show_dashboard?()
true
"""
@spec show_dashboard?() :: boolean()
def show_dashboard?() do
env(:hide_dashboard, false) == false
end
@doc """
Takes a conn struct and returns the route to display as the root route.
This option can be optionally provided in the configs. If it is not provided, the default route is the dashboard.
Options are:
- [kaffy: :dashboard]
- [schema: ["blog", "post"]]
- [page: "my-custom-page"]
iex> home_page(conn)
"/admin/dashboard"
"""
@spec home_page(Plug.Conn.t()) :: String.t()
def home_page(conn) do
case env(:home_page, kaffy: :dashboard) do
[kaffy: :dashboard] ->
router().kaffy_dashboard_path(conn, :dashboard)
[schema: [context, resource]] ->
router().kaffy_resource_path(conn, :index, context, resource)
[page: slug] ->
router().kaffy_page_path(conn, :index, slug)
end
end
def extensions(conn) do
exts = env(:extensions, [])
stylesheets =
Enum.map(exts, fn ext ->
case function_exported?(ext, :stylesheets, 1) do
true -> ext.stylesheets(conn)
false -> []
end
end)
javascripts =
Enum.map(exts, fn ext ->
case function_exported?(ext, :javascripts, 1) do
true -> ext.javascripts(conn)
false -> []
end
end)
%{stylesheets: stylesheets, javascripts: javascripts}
end
defp env(key, default \\ nil) do
Application.get_env(:kaffy, key, default)
end
defp convert_to_atoms(context, resource) do
{convert_to_atom(context), convert_to_atom(resource)}
end
defp convert_to_atom(string) do
if is_binary(string), do: String.to_existing_atom(string), else: string
end
defp setup_resources do
otp_app = env(:otp_app)
{:ok, mods} = :application.get_key(otp_app, :modules)
get_schemas(mods)
|> build_resources()
end
defp get_schemas(mods) do
Enum.filter(mods, fn m ->
functions = m.__info__(:functions)
Keyword.has_key?(functions, :__schema__) && Map.has_key?(m.__struct__, :__meta__)
end)
end
defp build_resources(schemas) do
Enum.reduce(schemas, [], fn schema, resources ->
schema_module =
to_string(schema)
|> String.split(".")
context_module =
schema_module
|> Enum.reverse()
|> tl()
|> Enum.reverse()
|> Enum.join(".")
context_name =
schema_module
|> Enum.at(-2)
|> Macro.underscore()
|> String.to_atom()
schema_name_string =
schema_module
|> Enum.at(-1)
schema_name =
schema_name_string
|> Macro.underscore()
|> String.to_atom()
schema_admin = String.to_atom("#{context_module}.#{schema_name_string}Admin")
schema_options =
case function_exported?(schema_admin, :__info__, 1) do
true -> [schema: schema, admin: schema_admin]
false -> [schema: schema]
end
humanized_context = Kaffy.ResourceAdmin.humanize_term(context_name)
resources = Keyword.put_new(resources, context_name, name: humanized_context, resources: [])
resources = put_in(resources, [context_name, :resources, schema_name], schema_options)
existing_schemas = get_in(resources, [context_name, :resources]) |> Enum.sort()
put_in(resources, [context_name, :resources], existing_schemas)
end)
|> Enum.sort()
end
def get_task_modules() do
env(:scheduled_tasks, [])
end
end