From 6076654aa4b87780ef09c384f9294becf2fad62f Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Tue, 21 Apr 2026 13:56:18 +0200 Subject: [PATCH] After 'mix phx.gen.json Items Item items name:string description:string info:string amount:integer factor:float type:string --no-context --no-schema'. --- .../controllers/changeset_json.ex | 25 +++++ .../controllers/fallback_controller.ex | 16 +++ .../controllers/item_controller.ex | 43 +++++++ .../controllers/item_json.ex | 29 +++++ lib/generic_rest_server_web/router.ex | 8 +- .../controllers/item_controller_test.exs | 106 ++++++++++++++++++ 6 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 lib/generic_rest_server_web/controllers/changeset_json.ex create mode 100644 lib/generic_rest_server_web/controllers/fallback_controller.ex create mode 100644 lib/generic_rest_server_web/controllers/item_controller.ex create mode 100644 lib/generic_rest_server_web/controllers/item_json.ex create mode 100644 test/generic_rest_server_web/controllers/item_controller_test.exs diff --git a/lib/generic_rest_server_web/controllers/changeset_json.ex b/lib/generic_rest_server_web/controllers/changeset_json.ex new file mode 100644 index 0000000..dfac3a1 --- /dev/null +++ b/lib/generic_rest_server_web/controllers/changeset_json.ex @@ -0,0 +1,25 @@ +defmodule GenericRestServerWeb.ChangesetJSON do + @doc """ + Renders changeset errors. + """ + def error(%{changeset: changeset}) do + # When encoded, the changeset returns its errors + # as a JSON object. So we just pass it forward. + %{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)} + end + + defp translate_error({msg, opts}) do + # You can make use of gettext to translate error messages by + # uncommenting and adjusting the following code: + + # if count = opts[:count] do + # Gettext.dngettext(GenericRestServerWeb.Gettext, "errors", msg, msg, count, opts) + # else + # Gettext.dgettext(GenericRestServerWeb.Gettext, "errors", msg, opts) + # end + + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end) + end) + end +end diff --git a/lib/generic_rest_server_web/controllers/fallback_controller.ex b/lib/generic_rest_server_web/controllers/fallback_controller.ex new file mode 100644 index 0000000..5002084 --- /dev/null +++ b/lib/generic_rest_server_web/controllers/fallback_controller.ex @@ -0,0 +1,16 @@ +defmodule GenericRestServerWeb.FallbackController do + @moduledoc """ + Translates controller action results into valid `Plug.Conn` responses. + + See `Phoenix.Controller.action_fallback/1` for more details. + """ + use GenericRestServerWeb, :controller + + # This clause is an example of how to handle resources that cannot be found. + def call(conn, {:error, :not_found}) do + conn + |> put_status(:not_found) + |> put_view(html: GenericRestServerWeb.ErrorHTML, json: GenericRestServerWeb.ErrorJSON) + |> render(:"404") + end +end diff --git a/lib/generic_rest_server_web/controllers/item_controller.ex b/lib/generic_rest_server_web/controllers/item_controller.ex new file mode 100644 index 0000000..a108fc0 --- /dev/null +++ b/lib/generic_rest_server_web/controllers/item_controller.ex @@ -0,0 +1,43 @@ +defmodule GenericRestServerWeb.ItemController do + use GenericRestServerWeb, :controller + + alias GenericRestServer.Items + alias GenericRestServer.Items.Item + + action_fallback GenericRestServerWeb.FallbackController + + def index(conn, _params) do + items = Items.list_items(conn.assigns.current_scope) + render(conn, :index, items: items) + end + + def create(conn, %{"item" => item_params}) do + with {:ok, %Item{} = item} <- Items.create_item(conn.assigns.current_scope, item_params) do + conn + |> put_status(:created) + |> put_resp_header("location", ~p"/api/items/#{item}") + |> render(:show, item: item) + end + end + + def show(conn, %{"id" => id}) do + item = Items.get_item!(conn.assigns.current_scope, id) + render(conn, :show, item: item) + end + + def update(conn, %{"id" => id, "item" => item_params}) do + item = Items.get_item!(conn.assigns.current_scope, id) + + with {:ok, %Item{} = item} <- Items.update_item(conn.assigns.current_scope, item, item_params) do + render(conn, :show, item: item) + end + end + + def delete(conn, %{"id" => id}) do + item = Items.get_item!(conn.assigns.current_scope, id) + + with {:ok, %Item{}} <- Items.delete_item(conn.assigns.current_scope, item) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/generic_rest_server_web/controllers/item_json.ex b/lib/generic_rest_server_web/controllers/item_json.ex new file mode 100644 index 0000000..0dce52f --- /dev/null +++ b/lib/generic_rest_server_web/controllers/item_json.ex @@ -0,0 +1,29 @@ +defmodule GenericRestServerWeb.ItemJSON do + alias GenericRestServer.Items.Item + + @doc """ + Renders a list of items. + """ + def index(%{items: items}) do + %{data: for(item <- items, do: data(item))} + end + + @doc """ + Renders a single item. + """ + def show(%{item: item}) do + %{data: data(item)} + end + + defp data(%Item{} = item) do + %{ + id: item.id, + name: item.name, + description: item.description, + info: item.info, + amount: item.amount, + factor: item.factor, + type: item.type + } + end +end diff --git a/lib/generic_rest_server_web/router.ex b/lib/generic_rest_server_web/router.ex index 59afe04..0e42077 100644 --- a/lib/generic_rest_server_web/router.ex +++ b/lib/generic_rest_server_web/router.ex @@ -24,9 +24,11 @@ defmodule GenericRestServerWeb.Router do end # Other scopes may use custom stacks. - # scope "/api", GenericRestServerWeb do - # pipe_through :api - # end + scope "/api", GenericRestServerWeb do + pipe_through :api + + resources "/items", ItemController, except: [:new, :edit] + end # Enable LiveDashboard and Swoosh mailbox preview in development if Application.compile_env(:generic_rest_server, :dev_routes) do diff --git a/test/generic_rest_server_web/controllers/item_controller_test.exs b/test/generic_rest_server_web/controllers/item_controller_test.exs new file mode 100644 index 0000000..d8fc6b1 --- /dev/null +++ b/test/generic_rest_server_web/controllers/item_controller_test.exs @@ -0,0 +1,106 @@ +defmodule GenericRestServerWeb.ItemControllerTest do + use GenericRestServerWeb.ConnCase + + import GenericRestServer.ItemsFixtures + alias GenericRestServer.Items.Item + + @create_attrs %{ + info: "some info", + name: "some name", + type: "some type", + description: "some description", + amount: 42, + factor: 120.5 + } + @update_attrs %{ + info: "some updated info", + name: "some updated name", + type: "some updated type", + description: "some updated description", + amount: 43, + factor: 456.7 + } + @invalid_attrs %{info: nil, name: nil, type: nil, description: nil, amount: nil, factor: nil} + + setup :register_and_log_in_user + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all items", %{conn: conn} do + conn = get(conn, ~p"/api/items") + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create item" do + test "renders item when data is valid", %{conn: conn} do + conn = post(conn, ~p"/api/items", item: @create_attrs) + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get(conn, ~p"/api/items/#{id}") + + assert %{ + "id" => ^id, + "amount" => 42, + "description" => "some description", + "factor" => 120.5, + "info" => "some info", + "name" => "some name", + "type" => "some type" + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/api/items", item: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update item" do + setup [:create_item] + + test "renders item when data is valid", %{conn: conn, item: %Item{id: id} = item} do + conn = put(conn, ~p"/api/items/#{item}", item: @update_attrs) + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get(conn, ~p"/api/items/#{id}") + + assert %{ + "id" => ^id, + "amount" => 43, + "description" => "some updated description", + "factor" => 456.7, + "info" => "some updated info", + "name" => "some updated name", + "type" => "some updated type" + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn, item: item} do + conn = put(conn, ~p"/api/items/#{item}", item: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete item" do + setup [:create_item] + + test "deletes chosen item", %{conn: conn, item: item} do + conn = delete(conn, ~p"/api/items/#{item}") + assert response(conn, 204) + + assert_error_sent 404, fn -> + get(conn, ~p"/api/items/#{item}") + end + end + end + + defp create_item(%{scope: scope}) do + item = item_fixture(scope) + + %{item: item} + end +end