Current section
Files
Jump to
Current section
Files
priv/templates/rest_controller.eex
defmodule <%= inspect @controller_module %> do
@moduledoc """
Generated by Caravela from <%= inspect @domain_module %>.
Renders Svelte components via `caravela_svelte`'s Inertia-style
transport. Every action delegates CRUD to
`<%= inspect @context_module %>` (authorization, hooks, and —
when enabled — tenant scoping apply automatically) and ships the
entity's policy-derived `field_access` map as a prop so the
Svelte component can render permission-gated fields without a
round-trip.<%= if @realtime do %>
`realtime: true` on the entity means `create`, `update`, and
`delete` publish a JSON-Patch on the conventional per-actor
topic via `CaravelaSvelte.Caravela.broadcast_patch/3`. Clients
subscribed through `CaravelaSvelte.SSE` receive live updates.<% end %>
Regenerate with `mix caravela.gen.live <%= inspect @domain_module %>`.
Custom code placed below the `# --- CUSTOM ---` marker is
preserved.
"""
use Phoenix.Controller, formats: [:html]
alias <%= inspect @context_module %>
alias Caravela.ChangesetTranslator
alias CaravelaSvelte.Caravela, as: CS
def index(conn, _params) do
context = build_context(conn)
items = <%= @context_short %>.<%= @list_fn %>(context)
field_access = field_access(context)
conn
|> CS.put_field_access(field_access)
|> CaravelaSvelte.render(<%= inspect @component_index %>, %{
<%= @plural %>: items,
field_access: field_access
})
end
# --- CUSTOM :index_<%= @singular %> ---
# --- END :index_<%= @singular %> ---
def show(conn, %{"id" => id}) do
context = build_context(conn)
case <%= @context_short %>.<%= @get_fn %>(id, context) do
nil ->
send_not_found(conn)
entity ->
field_access = field_access(context)
conn
|> CS.put_field_access(field_access)
|> CaravelaSvelte.render(<%= inspect @component_show %>, %{
<%= @singular %>: entity,
field_access: field_access
})
end
end
# --- CUSTOM :show_<%= @singular %> ---
# --- END :show_<%= @singular %> ---
def new(conn, _params) do
context = build_context(conn)
changeset = <%= @context_short %>.<%= @change_fn %>(%<%= inspect @entity_module %>{}, %{}, context)
field_access = field_access(context)
conn
|> CS.put_field_access(field_access)
|> CaravelaSvelte.render(<%= inspect @component_form %>, %{
<%= @singular %>: changeset.data,
errors: %{},
action: "create",
field_access: field_access
})
end
# --- CUSTOM :new_<%= @singular %> ---
# --- END :new_<%= @singular %> ---
def edit(conn, %{"id" => id}) do
context = build_context(conn)
case <%= @context_short %>.<%= @get_fn %>(id, context) do
nil ->
send_not_found(conn)
entity ->
field_access = field_access(context)
conn
|> CS.put_field_access(field_access)
|> CaravelaSvelte.render(<%= inspect @component_form %>, %{
<%= @singular %>: entity,
errors: %{},
action: "update",
field_access: field_access
})
end
end
# --- CUSTOM :edit_<%= @singular %> ---
# --- END :edit_<%= @singular %> ---
def create(conn, params) do
context = build_context(conn)
attrs = Map.get(params, "<%= @singular %>", Map.drop(params, ["id"]))
case <%= @context_short %>.<%= @create_fn %>(attrs, context) do
{:ok, entity} -><%= if @realtime do %>
broadcast(entity, context, :create)
<% end %>
redirect(conn, to: "<%= @index_path %>/" <> to_string(entity.id))
{:error, :unauthorized} ->
send_unauthorized(conn)
{:error, %Ecto.Changeset{} = changeset} ->
field_access = field_access(context)
conn
|> CS.put_field_access(field_access)
|> put_status(:unprocessable_entity)
|> CaravelaSvelte.render(<%= inspect @component_form %>, %{
<%= @singular %>: changeset.data,
errors: ChangesetTranslator.translate(changeset),
action: "create",
field_access: field_access
})
{:error, reason} ->
send_error(conn, reason)
end
end
# --- CUSTOM :create_<%= @singular %> ---
# --- END :create_<%= @singular %> ---
def update(conn, %{"id" => id} = params) do
context = build_context(conn)
attrs = Map.get(params, "<%= @singular %>", Map.drop(params, ["id"]))
case <%= @context_short %>.<%= @get_fn %>(id, context) do
nil ->
send_not_found(conn)
entity ->
case <%= @context_short %>.<%= @update_fn %>(entity, attrs, context) do
{:ok, updated} -><%= if @realtime do %>
broadcast(updated, context, :update)
<% end %>
redirect(conn, to: "<%= @index_path %>/" <> to_string(updated.id))
{:error, :unauthorized} ->
send_unauthorized(conn)
{:error, %Ecto.Changeset{} = changeset} ->
field_access = field_access(context)
conn
|> CS.put_field_access(field_access)
|> put_status(:unprocessable_entity)
|> CaravelaSvelte.render(<%= inspect @component_form %>, %{
<%= @singular %>: entity,
errors: ChangesetTranslator.translate(changeset),
action: "update",
field_access: field_access
})
{:error, reason} ->
send_error(conn, reason)
end
end
end
# --- CUSTOM :update_<%= @singular %> ---
# --- END :update_<%= @singular %> ---
def delete(conn, %{"id" => id}) do
context = build_context(conn)
case <%= @context_short %>.<%= @get_fn %>(id, context) do
nil ->
send_not_found(conn)
entity ->
case <%= @context_short %>.<%= @delete_fn %>(entity, context) do
{:ok, _} -><%= if @realtime do %>
broadcast(entity, context, :delete)
<% end %>
redirect(conn, to: "<%= @index_path %>")
{:error, :unauthorized} ->
send_unauthorized(conn)
{:error, reason} ->
send_error(conn, reason)
end
end
end
# --- CUSTOM :delete_<%= @singular %> ---
# --- END :delete_<%= @singular %> ---
# --- Internal --------------------------------------------------------
defp build_context(conn) do
%{
current_user: conn.assigns[:current_user],<%= if @multi_tenant do %>
tenant: conn.assigns[:tenant],<% end %>
conn: conn
}
end
# Placeholder — slice 3 will derive this from the compiled policy.
# Kept here so generated code has a single call-site to extend.
defp field_access(_context), do: %{}
<%= if @realtime do %>
defp broadcast(%<%= inspect @entity_module %>{} = <%= @singular %>, context, op) do
ops = patch_ops(<%= @singular %>, op)
actor_id = context |> Map.get(:current_user) |> actor_id()
CS.broadcast_patch(<%= inspect @entity_short %>, actor_id, ops)
end
defp patch_ops(<%= @singular %>, :create),
do: [%{"op" => "add", "path" => "/<%= @plural %>/-", "value" => <%= @singular %>}]
defp patch_ops(<%= @singular %>, :update),
do: [%{"op" => "replace", "path" => "/<%= @plural %>/" <> to_string(<%= @singular %>.id), "value" => <%= @singular %>}]
defp patch_ops(<%= @singular %>, :delete),
do: [%{"op" => "remove", "path" => "/<%= @plural %>/" <> to_string(<%= @singular %>.id)}]
defp actor_id(nil), do: nil
defp actor_id(%{id: id}), do: id
defp actor_id(_), do: nil
<% end %>
defp send_not_found(conn),
do: conn |> put_status(:not_found) |> text("not_found")
defp send_unauthorized(conn),
do: conn |> put_status(:forbidden) |> text("unauthorized")
defp send_error(conn, reason),
do: conn |> put_status(:unprocessable_entity) |> text(inspect(reason))
<%= @custom_marker %>
end