From 59de90601a91d5d40bf3a3d591e6d55839972f6c Mon Sep 17 00:00:00 2001 From: KKlochko Date: Tue, 20 Aug 2024 21:46:00 +0300 Subject: [PATCH] Update the link api. --- .../controllers/api/v1/link_controller.ex | 43 ++++++++ .../controllers/api/v1/link_json.ex | 20 ++++ .../controllers/changeset_json.ex | 25 +++++ .../controllers/fallback_controller.ex | 24 +++++ lib/link_shortener_web/router.ex | 11 +- .../api/v1/link_controller_test.exs | 100 ++++++++++++++++++ 6 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 lib/link_shortener_web/controllers/api/v1/link_controller.ex create mode 100644 lib/link_shortener_web/controllers/api/v1/link_json.ex create mode 100644 lib/link_shortener_web/controllers/changeset_json.ex create mode 100644 lib/link_shortener_web/controllers/fallback_controller.ex create mode 100644 test/link_shortener_web/controllers/api/v1/link_controller_test.exs diff --git a/lib/link_shortener_web/controllers/api/v1/link_controller.ex b/lib/link_shortener_web/controllers/api/v1/link_controller.ex new file mode 100644 index 0000000..47c11ed --- /dev/null +++ b/lib/link_shortener_web/controllers/api/v1/link_controller.ex @@ -0,0 +1,43 @@ +defmodule LinkShortenerWeb.Api.V1.LinkController do + use LinkShortenerWeb, :controller + + alias LinkShortener.Links + alias LinkShortener.Links.Link + + action_fallback LinkShortenerWeb.FallbackController + + def index(conn, _params) do + links = Links.get_all() + render(conn, :index, links: links) + end + + def create(conn, %{"link" => link_params}) do + with {:ok, %Link{} = link} <- Links.insert_one(link_params) do + conn + |> put_status(:created) + |> put_resp_header("location", ~p"/api/v1/items/") + |> render(:show, link: link) + end + end + + def show(conn, %{"id" => id}) do + link = Links.get_one!(id) + render(conn, :show, link: link) + end + + def update(conn, %{"id" => id, "link" => link_params}) do + link = Links.get_one!(id) + + with {:ok, %Link{} = link} <- Links.update_one(link, link_params) do + render(conn, :show, link: link) + end + end + + def delete(conn, %{"id" => id}) do + link = Links.get_one!(id) + + with {:ok, %Link{}} <- Links.delete_one(link) do + send_resp(conn, :no_content, "") + end + end +end diff --git a/lib/link_shortener_web/controllers/api/v1/link_json.ex b/lib/link_shortener_web/controllers/api/v1/link_json.ex new file mode 100644 index 0000000..ba927f2 --- /dev/null +++ b/lib/link_shortener_web/controllers/api/v1/link_json.ex @@ -0,0 +1,20 @@ +defmodule LinkShortenerWeb.Api.V1.LinkJSON do + alias LinkShortener.Links.Link + + def index(%{links: links}) do + %{data: for(link <- links, do: link(link))} + end + + def show(%{link: link}) do + %{data: link(link)} + end + + def link(%Link{} = link) do + %{ + id: link.id, + name: link.name, + url: link.url, + shorten: link.shorten + } + end +end diff --git a/lib/link_shortener_web/controllers/changeset_json.ex b/lib/link_shortener_web/controllers/changeset_json.ex new file mode 100644 index 0000000..ebfef62 --- /dev/null +++ b/lib/link_shortener_web/controllers/changeset_json.ex @@ -0,0 +1,25 @@ +defmodule LinkShortenerWeb.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(LinkShortenerWeb.Gettext, "errors", msg, msg, count, opts) + # else + # Gettext.dgettext(LinkShortenerWeb.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/link_shortener_web/controllers/fallback_controller.ex b/lib/link_shortener_web/controllers/fallback_controller.ex new file mode 100644 index 0000000..4c2c9ec --- /dev/null +++ b/lib/link_shortener_web/controllers/fallback_controller.ex @@ -0,0 +1,24 @@ +defmodule LinkShortenerWeb.FallbackController do + @moduledoc """ + Translates controller action results into valid `Plug.Conn` responses. + + See `Phoenix.Controller.action_fallback/1` for more details. + """ + use LinkShortenerWeb, :controller + + # This clause handles errors returned by Ecto's insert/update/delete. + def call(conn, {:error, %Ecto.Changeset{} = changeset}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(json: LinkShortenerWeb.ChangesetJSON) + |> render(:error, changeset: changeset) + end + + # 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: LinkShortenerWeb.ErrorHTML, json: LinkShortenerWeb.ErrorJSON) + |> render(:"404") + end +end diff --git a/lib/link_shortener_web/router.ex b/lib/link_shortener_web/router.ex index 64ed714..14b27bb 100644 --- a/lib/link_shortener_web/router.ex +++ b/lib/link_shortener_web/router.ex @@ -20,10 +20,13 @@ defmodule LinkShortenerWeb.Router do get "/", PageController, :home end - # Other scopes may use custom stacks. - # scope "/api", LinkShortenerWeb do - # pipe_through :api - # end + scope "/api", LinkShortenerWeb do + pipe_through :api + + scope "/v1", Api.V1, as: :v1 do + resources "/links", LinkController + end + end # Enable LiveDashboard and Swoosh mailbox preview in development if Application.compile_env(:link_shortener, :dev_routes) do diff --git a/test/link_shortener_web/controllers/api/v1/link_controller_test.exs b/test/link_shortener_web/controllers/api/v1/link_controller_test.exs new file mode 100644 index 0000000..ebdc6f9 --- /dev/null +++ b/test/link_shortener_web/controllers/api/v1/link_controller_test.exs @@ -0,0 +1,100 @@ +defmodule LinkShortenerWeb.Api.V1.LinkControllerTest do + use LinkShortenerWeb.ConnCase + + import LinkShortener.LinksFixtures + + alias LinkShortener.Links.Link + alias LinkShortener.Links + + @create_attrs %{ + name: "some link name", + url: "https://gitlab.com/KKlochko/link_shortener", + shorten: "git_repo", + } + + @update_attrs %{ + name: "some updated link name", + url: "https://gitlab.com/KKlochko/link_shortener2", + shorten: "new_git_repo", + } + + @invalid_attrs %{ + name: nil, + url: nil, + shorten: nil, + } + + setup %{conn: conn} do + {:ok, conn: put_req_header(conn, "accept", "application/json")} + end + + describe "index" do + test "lists all links", %{conn: conn} do + conn = get(conn, ~p"/api/v1/links") + assert json_response(conn, 200)["data"] == [] + end + end + + describe "create link" do + test "renders link when data is valid", %{conn: conn} do + conn = post(conn, ~p"/api/v1/links", link: @create_attrs) + assert %{"id" => id} = json_response(conn, 201)["data"] + + conn = get(conn, ~p"/api/v1/links/#{id}") + + assert %{ + "id" => ^id, + "name" => "some link name", + "url" => "https://gitlab.com/KKlochko/link_shortener", + "shorten" => "git_repo", + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/api/v1/links", link: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "update link" do + setup [:create_link] + + test "renders link when data is valid", %{conn: conn, link: %Link{id: id} = link} do + conn = put(conn, ~p"/api/v1/links/#{id}", link: @update_attrs) + assert %{"id" => ^id} = json_response(conn, 200)["data"] + + conn = get(conn, ~p"/api/v1/links/#{id}") + + assert %{ + "id" => ^id, + "name" => "some updated link name", + "url" => "https://gitlab.com/KKlochko/link_shortener2", + "shorten" => "new_git_repo", + } = json_response(conn, 200)["data"] + end + + test "renders errors when data is invalid", %{conn: conn, link: %Link{id: id} = link} do + conn = put(conn, ~p"/api/v1/links/#{id}", link: @invalid_attrs) + assert json_response(conn, 422)["errors"] != %{} + end + end + + describe "delete link" do + setup [:create_link] + + test "deletes chosen link", %{conn: conn, link: %Link{id: id} = link} do + conn = delete(conn, ~p"/api/v1/links/#{id}") + assert response(conn, 204) + + assert_error_sent 404, fn -> + conn = get(conn, ~p"/api/v1/links/#{id}") + end + end + end + + defp create_link(_) do + link = link_fixture() + %{link: link} + end +end +