Current section

Files

Jump to
dumper priv templates dumper_controller.ex
Raw

priv/templates/dumper_controller.ex

# Copyright 2024 Adobe. All rights reserved.
# This file is licensed to you under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software distributed under
# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.
defmodule <%= @web_module %>.DumperController do
use <%= @web_module %>, :controller
import Ecto.Query
alias <%= @app_module %>.Repo
def home(conn, _) do
{:ok, modules} = :application.get_key(<%= @application %>, :modules)
render(conn, :home, modules: Enum.filter(modules, &is_ecto_schema?/1))
end
def resource(conn, %{"id" => id, "resource" => resource}) do
# This handles routes like /foo?id=1234, which is used by the "Find by ID" form on index pages.
conn |> redirect(to: ~p"/dumper/#{resource}/#{id}") |> halt()
end
def resource(conn, %{"resource" => resource} = params) do
cond do
# index
module = to_module(resource, :index) ->
page = Map.merge(%{"page" => 1, "page_size" => 25}, params)
query = Ecto.Queryable.to_query(module)
query = if :inserted_at in module.__schema__(:fields), do: order_by(query, desc: :inserted_at), else: query
render(conn, :index, module: module, records: paginate(query, page))
# show
module = to_module(resource, :show) ->
[id] = Enum.take(resource, -1)
record = module |> Repo.get!(id) |> Repo.preload(module.__schema__(:associations))
render(conn, :show, module: module, fields: module.__schema__(:fields), record: record)
:else ->
conn |> Plug.Conn.put_status(404) |> Plug.Conn.halt()
end
end
defp to_module(resource, action) do
resource = if action == :show, do: Enum.drop(resource, -1), else: resource
module = resource |> Enum.map(&Macro.camelize/1) |> Module.safe_concat()
if is_ecto_schema?(module), do: module
rescue
# Module.safe_concat will raise if the resource string doesn't map to an existing atom
ArgumentError -> nil
end
defp is_ecto_schema?(module) do
# true iff it 1. is a module 2. has a __schema__ function 3. is not an Embedded Schema
match?({:module, _}, Code.ensure_loaded(module)) &&
{:__schema__, 1} in module.__info__(:functions) &&
module.__schema__(:source) != nil
end
defp paginate(query, %{"page" => page, "page_size" => page_size}) do
page = if is_binary(page), do: String.to_integer(page), else: page
page = max(1, page)
page_size = if is_binary(page_size), do: String.to_integer(page_size), else: page_size
page_size = max(1, page_size)
entries =
query
|> limit(^page_size)
|> offset(^(page_size * (page - 1)))
|> Repo.all()
total_pages = ceil(Repo.aggregate(query, :count) / page_size)
%{
entries: entries,
has_prev?: page > 1,
has_next?: page < total_pages,
page: page,
page_size: page_size
}
end
end