defmodule <%= inspect @context_module %> do @moduledoc """ Generated by Caravela from <%= inspect @domain_module %>. CRUD context with hooks and standalone authorization.<%= if @multi_tenant do %> Multi-tenant: every read is scoped by `tenant_id` and every write stamps `tenant_id` from the caller's `context.tenant`.<% end %> Regenerate with `mix caravela.gen.context <%= inspect @domain_module %>`. Custom code placed below the `# --- CUSTOM ---` marker is preserved. """ <%= if @multi_tenant do %> import Ecto.Query, only: [where: 3] <% end %> alias <%= inspect @repo_module %> <%= for e <- @entities do %> alias <%= inspect e.module %> <% end %> <%= for e <- @entities do %> @doc "List `<%= e.plural %>` the caller is allowed to read." def <%= e.list_fn %>(context \\ %{}) do <%= e.module_short %> |> scope_tenant(context) |> apply_read_permission(<%= inspect e.entity_name %>, context) |> Repo.all() end @doc "Fetch a single `<%= e.singular %>` by id. Returns `nil` if not found or not visible." def <%= e.get_fn %>(id, context \\ %{}) do <%= e.module_short %> |> scope_tenant(context) |> apply_read_permission(<%= inspect e.entity_name %>, context) |> Repo.get(id) end @doc "Fetch a single `<%= e.singular %>` by id. Raises if not found or not visible." def <%= e.get_bang_fn %>(id, context \\ %{}) do <%= e.module_short %> |> scope_tenant(context) |> apply_read_permission(<%= inspect e.entity_name %>, context) |> Repo.get!(id) end @doc "Build an unsaved `<%= e.singular %>` changeset, for use in forms." def <%= e.change_fn %>(%<%= e.module_short %>{} = <%= e.singular %>, attrs \\ %{}) do <%= e.module_short %>.changeset(<%= e.singular %>, attrs) end @doc "Create a `<%= e.singular %>`: authorize, apply on_create hook, insert." def <%= e.create_fn %>(attrs, context \\ %{}) do with :ok <- authorize_create(<%= inspect e.entity_name %>, context) do %<%= e.module_short %>{} |> <%= e.module_short %>.changeset(attrs) |> inject_tenant_id(context) |> apply_changeset_hook(:on_create, <%= inspect e.entity_name %>, context) |> Repo.insert() end end @doc "Update a `<%= e.singular %>`: authorize, apply on_update hook, update." def <%= e.update_fn %>(%<%= e.module_short %>{} = <%= e.singular %>, attrs, context \\ %{}) do with :ok <- authorize_update(<%= inspect e.entity_name %>, <%= e.singular %>, context) do <%= e.singular %> |> <%= e.module_short %>.changeset(attrs) |> apply_changeset_hook(:on_update, <%= inspect e.entity_name %>, context) |> Repo.update() end end @doc "Delete a `<%= e.singular %>`: authorize, run on_delete hook, delete." def <%= e.delete_fn %>(%<%= e.module_short %>{} = <%= e.singular %>, context \\ %{}) do with :ok <- authorize_delete(<%= inspect e.entity_name %>, <%= e.singular %>, context), :ok <- run_delete_hook(<%= inspect e.entity_name %>, <%= e.singular %>, context) do Repo.delete(<%= e.singular %>) end end <% end %> # --- Internal: permission + hook dispatch --------------------------------- defp apply_read_permission(query, entity, context) do <%= inspect @domain_module %>.__caravela_permission__(:can_read, entity, query, context) end <%= if @any_can_create do %> defp authorize_create(entity, context) do case <%= inspect @domain_module %>.__caravela_permission__(:can_create, entity, context) do true -> :ok false -> {:error, :unauthorized} end end <% else %> defp authorize_create(_entity, _context), do: :ok <% end %> <%= if @any_can_update do %> defp authorize_update(entity, struct, context) do case <%= inspect @domain_module %>.__caravela_permission__(:can_update, entity, struct, context) do true -> :ok false -> {:error, :unauthorized} end end <% else %> defp authorize_update(_entity, _struct, _context), do: :ok <% end %> <%= if @any_can_delete do %> defp authorize_delete(entity, struct, context) do case <%= inspect @domain_module %>.__caravela_permission__(:can_delete, entity, struct, context) do true -> :ok false -> {:error, :unauthorized} end end <% else %> defp authorize_delete(_entity, _struct, _context), do: :ok <% end %> defp apply_changeset_hook(changeset, action, entity, context) do <%= inspect @domain_module %>.__caravela_hook__(action, entity, changeset, context) end <%= if @any_on_delete do %> defp run_delete_hook(entity, struct, context) do case <%= inspect @domain_module %>.__caravela_hook__(:on_delete, entity, struct, context) do :ok -> :ok {:error, _} = err -> err other -> raise "on_delete hook must return :ok or {:error, reason}, got: " <> inspect(other) end end <% else %> defp run_delete_hook(_entity, _struct, _context), do: :ok <% end %> # --- Internal: multi-tenant scoping -------------------------------------- <%= if @multi_tenant do %> defp scope_tenant(query, %{tenant: %{id: tenant_id}}) when not is_nil(tenant_id) do where(query, [q], q.tenant_id == ^tenant_id) end defp scope_tenant(query, _context), do: query defp inject_tenant_id(changeset, %{tenant: %{id: tenant_id}}) when not is_nil(tenant_id) do Ecto.Changeset.put_change(changeset, :tenant_id, tenant_id) end defp inject_tenant_id(changeset, _context), do: changeset <% else %> defp scope_tenant(query, _context), do: query defp inject_tenant_id(changeset, _context), do: changeset <% end %> <%= @custom_marker %>