Current section
Files
Jump to
Current section
Files
web/controllers/admin_controller.ex
defmodule ExAdmin.AdminController do
@moduledoc false
use ExAdmin.Web, :controller
# require Logger
import ExAdmin
import ExAdmin.Utils
import ExAdmin.ParamsToAtoms
require IEx
plug :set_layout
def action(%{private: %{phoenix_action: action}} = conn, _options) do
handle_action(conn, action, conn.params["resource"])
end
defp handle_action(conn, action, nil) do
ExAdmin.get_all_registered
|> Enum.sort(&(elem(&1,1).menu[:priority] < elem(&2,1).menu[:priority]))
|> hd
|> case do
{_, %{controller_route: resource}} ->
conn = scrub_params(conn, resource, action)
params = filter_params(conn.params)
conn
|> struct(path_info: conn.path_info ++ [resource])
|> struct(params: Map.put(conn.params, "resource", resource))
|> handle_action(action, resource)
_other ->
throw :invalid_route
end
end
defp handle_action(conn, action, resource) do
conn = scrub_params(conn, resource, action)
params = filter_params(conn.params)
case get_registered_by_controller_route(resource) do
nil ->
throw :invalid_route
%{__struct__: _} = defn ->
conn
|> handle_plugs(action, defn)
|> handle_before_filter(action, defn, params)
|> handle_custom_actions(action, defn, params)
_ ->
apply(__MODULE__, action, [conn, params])
end
end
defp scrub_params(conn, required_key, action) when action in [:create, :update] do
if conn.params[required_key] do
Phoenix.Controller.scrub_params conn, required_key
else
conn
end
end
defp scrub_params(conn, _required_key, _action), do: conn
def handle_custom_actions({conn, params}, action, defn, _) do
handle_custom_actions(conn, action, defn, params)
end
def handle_custom_actions(conn, action, defn, params) do
%{member_actions: member_actions, collection_actions: collection_actions} = defn
cond do
member_action = Keyword.get(member_actions, action) ->
member_action.(conn, params)
collection_action = Keyword.get(collection_actions, action) ->
collection_action.(conn, params)
true ->
apply(__MODULE__, action, [conn, params])
end
end
def handle_before_filter(conn, action, defn, params) do
case defn.controller_filters[:before_filter] do
nil ->
conn
{name, opts} ->
filter = cond do
opts[:only] ->
if action in opts[:only], do: true, else: false
opts[:except] ->
if not action in opts[:except], do: true, else: false
true -> true
end
if filter, do: apply(defn.__struct__, name, [conn, params]), else: conn
end
end
defp handle_plugs(conn, :nested, _defn), do: conn
defp handle_plugs(conn, _action, defn) do
case Application.get_env(:ex_admin, :plug, []) do
list when is_list(list) -> list
item -> [{item, []}]
end
|> Keyword.merge(defn.plugs)
|> Enum.reduce(conn, fn({name, opts}, conn) ->
apply(name, :call, [conn, opts])
end)
|> authorized?
end
defp authorized?(%{assigns: %{authorized: true}} = conn), do: conn
defp authorized?(%{assigns: %{authorized: false}}) do
throw :unauthorized
end
defp authorized?(conn), do: conn
defp set_layout(conn, _) do
put_layout(conn, "admin.html")
end
def index(conn, params) do
require Logger
defn = get_registered_by_controller_route(params[:resource])
{contents, page} = case defn do
nil ->
throw :invalid_route
%{type: :page} = defn ->
{defn.__struct__ |> apply(:page_view, [conn]), nil}
defn ->
model = defn.__struct__
page = case conn.assigns[:page] do
nil ->
model.run_query(repo, defn, :index, params |> Map.to_list)
page ->
page
end
counts = model.run_query_counts repo, defn, :index, params |> Map.to_list
if function_exported? model, :index_view, 3 do
{apply(model, :index_view, [conn, page, counts]), page}
else
{ExAdmin.Index.default_index_view(conn, page, counts), page}
end
end
conn
|> render("admin.html", html: contents, defn: defn, resource: page,
filters: (if false in defn.index_filters, do: false, else: defn.index_filters))
end
def show(conn, params) do
{contents, resource, defn} = case get_registered_by_controller_route(params[:resource]) do
nil ->
throw :invalid_route
defn ->
model = defn.__struct__
resource = unless Application.get_all_env(:auth_ex) == [] do
resource_name = AuthEx.Utils.resource_name conn, model: defn.resource_model
case conn.assigns[resource_name] do
nil ->
model.run_query(repo, defn, :show, params[:id])
res ->
res
end
else
model.run_query(repo, defn, :show, params[:id])
end
if function_exported? model, :show_view, 2 do
{apply(model, :show_view, [conn, resource]), resource, defn}
else
{ExAdmin.Show.default_show_view(conn, resource), resource, defn}
end
end
render conn, "admin.html", html: contents, resource: resource, filters: nil, defn: defn
end
def edit(conn, params) do
{contents, resource, defn} = case get_registered_by_controller_route(params[:resource]) do
nil ->
throw :invalid_route
defn ->
model = defn.__struct__
resource = model.run_query(repo, defn, :edit, params[:id])
if function_exported? model, :form_view, 3 do
{apply(model, :form_view, [conn, resource, params]), resource, defn}
else
{ExAdmin.Form.default_form_view(conn, resource, params), resource, defn}
end
end
render conn, "admin.html", html: contents, resource: resource, filters: nil, defn: defn
end
def new(conn, params) do
{contents, resource, defn} = case get_registered_by_controller_route(params[:resource]) do
nil ->
throw :invalid_route
defn ->
model = defn.__struct__
resource = model.__struct__.resource_model.__struct__
{do_form_view(model, conn, resource, params), resource, defn}
end
render conn, "admin.html", html: contents, resource: resource, filters: nil, defn: defn
end
defp do_form_view(model, conn, resource, params) do
if function_exported? model, :form_view, 3 do
apply(model, :form_view, [conn, resource, params])
else
ExAdmin.Form.default_form_view conn, resource, params
end
end
def create(conn, params) do
case get_registered_by_controller_route(params[:resource]) do
nil ->
throw :invalid_route
defn ->
model = defn.__struct__
resource = model.__struct__.resource_model.__struct__
resource_model = model.__struct__.resource_model
|> base_name |> String.downcase |> String.to_atom
changeset_fn = Keyword.get(defn.changesets, :create, &resource.__struct__.changeset/2)
changeset = ExAdmin.Repo.changeset(changeset_fn, resource, params[resource_model])
if changeset.valid? do
resource = ExAdmin.Repo.insert(changeset)
put_flash(conn, :notice, "#{base_name model} was successfully created.")
|> redirect(to: get_route_path(resource, :show, resource.id))
else
conn = put_flash(conn, :inline_error, changeset.errors)
contents = do_form_view model, conn, changeset.changeset.model, params
conn |> render("admin.html", html: contents, resource: resource, filters: nil, defn: defn)
end
end
end
def update(conn, params) do
case get_registered_by_controller_route(params[:resource]) do
nil ->
throw :invalid_route
defn ->
model = defn.__struct__
resource_model = model.__struct__.resource_model
|> base_name |> String.downcase |> String.to_atom
resource = model.run_query(repo, defn, :edit, params[:id])
changeset_fn = Keyword.get(defn.changesets, :update, &resource.__struct__.changeset/2)
changeset = ExAdmin.Repo.changeset(changeset_fn, resource, params[resource_model])
if changeset.valid? do
ExAdmin.Repo.update(changeset)
put_flash(conn, :notice, "#{base_name model} was successfully updated")
|> redirect(to: get_route_path(resource, :show, resource.id))
else
conn = put_flash(conn, :inline_error, changeset.errors)
contents = do_form_view model, conn, changeset.changeset.model, params
conn |> render("admin.html", html: contents, resource: resource, filters: nil, defn: defn)
end
end
end
def destroy(conn, params) do
resource_model =
case get_registered_by_controller_route(params[:resource]) do
nil ->
throw :invalid_route
defn ->
model = defn.__struct__
resource_model = model.__struct__.resource_model
|> base_name |> String.downcase |> String.to_atom
model.run_query(repo, defn, :edit, params[:id])
|> ExAdmin.Repo.delete(params[resource_model])
base_name model
end
put_flash(conn, :notice, "#{resource_model} was successfully destroyed.")
|> redirect(to: get_route_path(conn, :index))
end
def batch_action(conn, %{batch_action: "destroy"} = params) do
defn = get_registered_by_controller_route!(params[:resource])
model = defn.__struct__
resource_model = model.__struct__.resource_model
ids = params[:collection_selection]
count = Enum.count ids
ids
|> Enum.map(&(String.to_integer(&1)))
|> Enum.each(fn(id) ->
repo.delete repo.get(resource_model, id)
end)
put_flash(conn, :notice, "Successfully destroyed #{count} #{pluralize params[:resource], count}")
|> redirect(to: get_route_path(conn, :index))
end
def csv(conn, params) do
case get_registered_by_controller_route(params[:resource]) do
nil ->
throw :invalid_route
defn ->
model = defn.__struct__
csv = case model.run_query(repo, defn, :csv) do
[] -> []
[resource | resources] ->
ExAdmin.View.Adapter.build_csv(resource, resources)
end
conn
|> put_resp_content_type("text/csv")
|> put_resp_header("Content-Disposition", "inline; filename=\"#{params[:resource]}.csv\"")
|> send_resp(conn.status || 200, csv)
end
end
@nested_key_list for i <- 1..5, do: {String.to_atom("nested#{i}"), String.to_atom("id#{i}")}
def nested(conn, params) do
contents = case get_registered_by_controller_route(params[:resource]) do
nil ->
throw :invalid_route
defn ->
model = defn.__struct__
items = apply(model, :get_blocks, [conn, defn.resource_model.__struct__, params])
block = deep_find(items, String.to_atom(params[:field_name]))
resources = block[:opts][:collection].(conn, defn.resource_model.__struct__)
contents = apply(model, :ajax_view, [conn, params, resources, block])
contents
end
send_resp(conn, conn.status || 200, "text/javascript", contents)
end
def dashboard(conn, _) do
redirect conn, to: "/admin/contacts"
end
def deep_find(items, name) do
Enum.reduce items, nil, fn(item, acc) ->
case item do
%{inputs: inputs} ->
case Enum.find inputs, &(&1[:name] == name) do
nil -> acc
found -> found
end
_ -> acc
end
end
end
defp send_resp(conn, default_status, default_content_type, body) do
conn
|> ensure_resp_content_type(default_content_type)
|> Plug.Conn.send_resp(conn.status || default_status, body)
end
defp ensure_resp_content_type(%{resp_headers: resp_headers} = conn, content_type) do
if List.keyfind(resp_headers, "content-type", 0) do
conn
else
content_type = content_type <> "; charset=utf-8"
%{conn | resp_headers: [{"content-type", content_type}|resp_headers]}
end
end
def repo, do: Application.get_env(:ex_admin, :repo)
end