diff --git a/lib/outlook/authors.ex b/lib/outlook/authors.ex new file mode 100644 index 0000000..180b7b0 --- /dev/null +++ b/lib/outlook/authors.ex @@ -0,0 +1,104 @@ +defmodule Outlook.Authors do + @moduledoc """ + The Authors context. + """ + + import Ecto.Query, warn: false + alias Outlook.Repo + + alias Outlook.Authors.Author + + @doc """ + Returns the list of authors. + + ## Examples + + iex> list_authors() + [%Author{}, ...] + + """ + def list_authors do + Repo.all(Author) + end + + @doc """ + Gets a single author. + + Raises `Ecto.NoResultsError` if the Author does not exist. + + ## Examples + + iex> get_author!(123) + %Author{} + + iex> get_author!(456) + ** (Ecto.NoResultsError) + + """ + def get_author!(id), do: Repo.get!(Author, id) + + @doc """ + Creates a author. + + ## Examples + + iex> create_author(%{field: value}) + {:ok, %Author{}} + + iex> create_author(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_author(attrs \\ %{}) do + %Author{} + |> Author.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a author. + + ## Examples + + iex> update_author(author, %{field: new_value}) + {:ok, %Author{}} + + iex> update_author(author, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_author(%Author{} = author, attrs) do + author + |> Author.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a author. + + ## Examples + + iex> delete_author(author) + {:ok, %Author{}} + + iex> delete_author(author) + {:error, %Ecto.Changeset{}} + + """ + def delete_author(%Author{} = author) do + Repo.delete(author) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking author changes. + + ## Examples + + iex> change_author(author) + %Ecto.Changeset{data: %Author{}} + + """ + def change_author(%Author{} = author, attrs \\ %{}) do + Author.changeset(author, attrs) + end +end diff --git a/lib/outlook/authors/author.ex b/lib/outlook/authors/author.ex new file mode 100644 index 0000000..38b12c6 --- /dev/null +++ b/lib/outlook/authors/author.ex @@ -0,0 +1,20 @@ +defmodule Outlook.Authors.Author do + use Ecto.Schema + import Ecto.Changeset + + schema "authors" do + field :description, :string + field :homepage_name, :string + field :homepage_url, :string + field :name, :string + + timestamps() + end + + @doc false + def changeset(author, attrs) do + author + |> cast(attrs, [:name, :description, :homepage_name, :homepage_url]) + |> validate_required([:name, :description, :homepage_name, :homepage_url]) + end +end diff --git a/lib/outlook_web/live/author_live/form_component.ex b/lib/outlook_web/live/author_live/form_component.ex new file mode 100644 index 0000000..2faaedb --- /dev/null +++ b/lib/outlook_web/live/author_live/form_component.ex @@ -0,0 +1,84 @@ +defmodule OutlookWeb.AuthorLive.FormComponent do + use OutlookWeb, :live_component + + alias Outlook.Authors + + @impl true + def render(assigns) do + ~H""" +
+ <.header> + <%= @title %> + <:subtitle>Use this form to manage author records in your database. + + + <.simple_form + :let={f} + for={@changeset} + id="author-form" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + > + <.input field={{f, :name}} type="text" label="name" /> + <.input field={{f, :description}} type="text" label="description" /> + <.input field={{f, :homepage_name}} type="text" label="homepage_name" /> + <.input field={{f, :homepage_url}} type="text" label="homepage_url" /> + <:actions> + <.button phx-disable-with="Saving...">Save Author + + +
+ """ + end + + @impl true + def update(%{author: author} = assigns, socket) do + changeset = Authors.change_author(author) + + {:ok, + socket + |> assign(assigns) + |> assign(:changeset, changeset)} + end + + @impl true + def handle_event("validate", %{"author" => author_params}, socket) do + changeset = + socket.assigns.author + |> Authors.change_author(author_params) + |> Map.put(:action, :validate) + + {:noreply, assign(socket, :changeset, changeset)} + end + + def handle_event("save", %{"author" => author_params}, socket) do + save_author(socket, socket.assigns.action, author_params) + end + + defp save_author(socket, :edit, author_params) do + case Authors.update_author(socket.assigns.author, author_params) do + {:ok, _author} -> + {:noreply, + socket + |> put_flash(:info, "Author updated successfully") + |> push_navigate(to: socket.assigns.navigate)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, :changeset, changeset)} + end + end + + defp save_author(socket, :new, author_params) do + case Authors.create_author(author_params) do + {:ok, _author} -> + {:noreply, + socket + |> put_flash(:info, "Author created successfully") + |> push_navigate(to: socket.assigns.navigate)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, changeset: changeset)} + end + end +end diff --git a/lib/outlook_web/live/author_live/index.ex b/lib/outlook_web/live/author_live/index.ex new file mode 100644 index 0000000..8d5fef0 --- /dev/null +++ b/lib/outlook_web/live/author_live/index.ex @@ -0,0 +1,46 @@ +defmodule OutlookWeb.AuthorLive.Index do + use OutlookWeb, :live_view + + alias Outlook.Authors + alias Outlook.Authors.Author + + @impl true + def mount(_params, _session, socket) do + {:ok, assign(socket, :authors, list_authors())} + end + + @impl true + def handle_params(params, _url, socket) do + {:noreply, apply_action(socket, socket.assigns.live_action, params)} + end + + defp apply_action(socket, :edit, %{"id" => id}) do + socket + |> assign(:page_title, "Edit Author") + |> assign(:author, Authors.get_author!(id)) + end + + defp apply_action(socket, :new, _params) do + socket + |> assign(:page_title, "New Author") + |> assign(:author, %Author{}) + end + + defp apply_action(socket, :index, _params) do + socket + |> assign(:page_title, "Listing Authors") + |> assign(:author, nil) + end + + @impl true + def handle_event("delete", %{"id" => id}, socket) do + author = Authors.get_author!(id) + {:ok, _} = Authors.delete_author(author) + + {:noreply, assign(socket, :authors, list_authors())} + end + + defp list_authors do + Authors.list_authors() + end +end diff --git a/lib/outlook_web/live/author_live/index.html.heex b/lib/outlook_web/live/author_live/index.html.heex new file mode 100644 index 0000000..085fdae --- /dev/null +++ b/lib/outlook_web/live/author_live/index.html.heex @@ -0,0 +1,42 @@ +<.header> + Listing Authors + <:actions> + <.link patch={~p"/authors/new"}> + <.button>New Author + + + + +<.table id="authors" rows={@authors} row_click={&JS.navigate(~p"/authors/#{&1}")}> + <:col :let={author} label="Name"><%= author.name %> + <:col :let={author} label="Description"><%= author.description %> + <:col :let={author} label="Homepage name"><%= author.homepage_name %> + <:col :let={author} label="Homepage url"><%= author.homepage_url %> + <:action :let={author}> +
+ <.link navigate={~p"/authors/#{author}"}>Show +
+ <.link patch={~p"/authors/#{author}/edit"}>Edit + + <:action :let={author}> + <.link phx-click={JS.push("delete", value: %{id: author.id})} data-confirm="Are you sure?"> + Delete + + + + +<.modal + :if={@live_action in [:new, :edit]} + id="author-modal" + show + on_cancel={JS.navigate(~p"/authors")} +> + <.live_component + module={OutlookWeb.AuthorLive.FormComponent} + id={@author.id || :new} + title={@page_title} + action={@live_action} + author={@author} + navigate={~p"/authors"} + /> + diff --git a/lib/outlook_web/live/author_live/show.ex b/lib/outlook_web/live/author_live/show.ex new file mode 100644 index 0000000..3e0777e --- /dev/null +++ b/lib/outlook_web/live/author_live/show.ex @@ -0,0 +1,21 @@ +defmodule OutlookWeb.AuthorLive.Show do + use OutlookWeb, :live_view + + alias Outlook.Authors + + @impl true + def mount(_params, _session, socket) do + {:ok, socket} + end + + @impl true + def handle_params(%{"id" => id}, _, socket) do + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:author, Authors.get_author!(id))} + end + + defp page_title(:show), do: "Show Author" + defp page_title(:edit), do: "Edit Author" +end diff --git a/lib/outlook_web/live/author_live/show.html.heex b/lib/outlook_web/live/author_live/show.html.heex new file mode 100644 index 0000000..3429bd0 --- /dev/null +++ b/lib/outlook_web/live/author_live/show.html.heex @@ -0,0 +1,29 @@ +<.header> + Author <%= @author.id %> + <:subtitle>This is a author record from your database. + <:actions> + <.link patch={~p"/authors/#{@author}/show/edit"} phx-click={JS.push_focus()}> + <.button>Edit author + + + + +<.list> + <:item title="Name"><%= @author.name %> + <:item title="Description"><%= @author.description %> + <:item title="Homepage name"><%= @author.homepage_name %> + <:item title="Homepage url"><%= @author.homepage_url %> + + +<.back navigate={~p"/authors"}>Back to authors + +<.modal :if={@live_action == :edit} id="author-modal" show on_cancel={JS.patch(~p"/authors/#{@author}")}> + <.live_component + module={OutlookWeb.AuthorLive.FormComponent} + id={@author.id} + title={@page_title} + action={@live_action} + author={@author} + navigate={~p"/authors/#{@author}"} + /> + diff --git a/lib/outlook_web/router.ex b/lib/outlook_web/router.ex index eee36e2..c2f3205 100644 --- a/lib/outlook_web/router.ex +++ b/lib/outlook_web/router.ex @@ -69,6 +69,13 @@ defmodule OutlookWeb.Router do live "/users/settings", UserSettingsLive, :edit live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email end + + live "/authors", AuthorLive.Index, :index + live "/authors/new", AuthorLive.Index, :new + live "/authors/:id/edit", AuthorLive.Index, :edit + + live "/authors/:id", AuthorLive.Show, :show + live "/authors/:id/show/edit", AuthorLive.Show, :edit end scope "/", OutlookWeb do diff --git a/priv/repo/migrations/20221226154920_create_authors.exs b/priv/repo/migrations/20221226154920_create_authors.exs new file mode 100644 index 0000000..8b8fc02 --- /dev/null +++ b/priv/repo/migrations/20221226154920_create_authors.exs @@ -0,0 +1,14 @@ +defmodule Outlook.Repo.Migrations.CreateAuthors do + use Ecto.Migration + + def change do + create table(:authors) do + add :name, :string + add :description, :text + add :homepage_name, :string + add :homepage_url, :string + + timestamps() + end + end +end diff --git a/test/outlook/authors_test.exs b/test/outlook/authors_test.exs new file mode 100644 index 0000000..b12648d --- /dev/null +++ b/test/outlook/authors_test.exs @@ -0,0 +1,65 @@ +defmodule Outlook.AuthorsTest do + use Outlook.DataCase + + alias Outlook.Authors + + describe "authors" do + alias Outlook.Authors.Author + + import Outlook.AuthorsFixtures + + @invalid_attrs %{description: nil, homepage_name: nil, homepage_url: nil, name: nil} + + test "list_authors/0 returns all authors" do + author = author_fixture() + assert Authors.list_authors() == [author] + end + + test "get_author!/1 returns the author with given id" do + author = author_fixture() + assert Authors.get_author!(author.id) == author + end + + test "create_author/1 with valid data creates a author" do + valid_attrs = %{description: "some description", homepage_name: "some homepage_name", homepage_url: "some homepage_url", name: "some name"} + + assert {:ok, %Author{} = author} = Authors.create_author(valid_attrs) + assert author.description == "some description" + assert author.homepage_name == "some homepage_name" + assert author.homepage_url == "some homepage_url" + assert author.name == "some name" + end + + test "create_author/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Authors.create_author(@invalid_attrs) + end + + test "update_author/2 with valid data updates the author" do + author = author_fixture() + update_attrs = %{description: "some updated description", homepage_name: "some updated homepage_name", homepage_url: "some updated homepage_url", name: "some updated name"} + + assert {:ok, %Author{} = author} = Authors.update_author(author, update_attrs) + assert author.description == "some updated description" + assert author.homepage_name == "some updated homepage_name" + assert author.homepage_url == "some updated homepage_url" + assert author.name == "some updated name" + end + + test "update_author/2 with invalid data returns error changeset" do + author = author_fixture() + assert {:error, %Ecto.Changeset{}} = Authors.update_author(author, @invalid_attrs) + assert author == Authors.get_author!(author.id) + end + + test "delete_author/1 deletes the author" do + author = author_fixture() + assert {:ok, %Author{}} = Authors.delete_author(author) + assert_raise Ecto.NoResultsError, fn -> Authors.get_author!(author.id) end + end + + test "change_author/1 returns a author changeset" do + author = author_fixture() + assert %Ecto.Changeset{} = Authors.change_author(author) + end + end +end diff --git a/test/outlook_web/live/author_live_test.exs b/test/outlook_web/live/author_live_test.exs new file mode 100644 index 0000000..a581644 --- /dev/null +++ b/test/outlook_web/live/author_live_test.exs @@ -0,0 +1,110 @@ +defmodule OutlookWeb.AuthorLiveTest do + use OutlookWeb.ConnCase + + import Phoenix.LiveViewTest + import Outlook.AuthorsFixtures + + @create_attrs %{description: "some description", homepage_name: "some homepage_name", homepage_url: "some homepage_url", name: "some name"} + @update_attrs %{description: "some updated description", homepage_name: "some updated homepage_name", homepage_url: "some updated homepage_url", name: "some updated name"} + @invalid_attrs %{description: nil, homepage_name: nil, homepage_url: nil, name: nil} + + defp create_author(_) do + author = author_fixture() + %{author: author} + end + + describe "Index" do + setup [:create_author] + + test "lists all authors", %{conn: conn, author: author} do + {:ok, _index_live, html} = live(conn, ~p"/authors") + + assert html =~ "Listing Authors" + assert html =~ author.description + end + + test "saves new author", %{conn: conn} do + {:ok, index_live, _html} = live(conn, ~p"/authors") + + assert index_live |> element("a", "New Author") |> render_click() =~ + "New Author" + + assert_patch(index_live, ~p"/authors/new") + + assert index_live + |> form("#author-form", author: @invalid_attrs) + |> render_change() =~ "can't be blank" + + {:ok, _, html} = + index_live + |> form("#author-form", author: @create_attrs) + |> render_submit() + |> follow_redirect(conn, ~p"/authors") + + assert html =~ "Author created successfully" + assert html =~ "some description" + end + + test "updates author in listing", %{conn: conn, author: author} do + {:ok, index_live, _html} = live(conn, ~p"/authors") + + assert index_live |> element("#authors-#{author.id} a", "Edit") |> render_click() =~ + "Edit Author" + + assert_patch(index_live, ~p"/authors/#{author}/edit") + + assert index_live + |> form("#author-form", author: @invalid_attrs) + |> render_change() =~ "can't be blank" + + {:ok, _, html} = + index_live + |> form("#author-form", author: @update_attrs) + |> render_submit() + |> follow_redirect(conn, ~p"/authors") + + assert html =~ "Author updated successfully" + assert html =~ "some updated description" + end + + test "deletes author in listing", %{conn: conn, author: author} do + {:ok, index_live, _html} = live(conn, ~p"/authors") + + assert index_live |> element("#authors-#{author.id} a", "Delete") |> render_click() + refute has_element?(index_live, "#author-#{author.id}") + end + end + + describe "Show" do + setup [:create_author] + + test "displays author", %{conn: conn, author: author} do + {:ok, _show_live, html} = live(conn, ~p"/authors/#{author}") + + assert html =~ "Show Author" + assert html =~ author.description + end + + test "updates author within modal", %{conn: conn, author: author} do + {:ok, show_live, _html} = live(conn, ~p"/authors/#{author}") + + assert show_live |> element("a", "Edit") |> render_click() =~ + "Edit Author" + + assert_patch(show_live, ~p"/authors/#{author}/show/edit") + + assert show_live + |> form("#author-form", author: @invalid_attrs) + |> render_change() =~ "can't be blank" + + {:ok, _, html} = + show_live + |> form("#author-form", author: @update_attrs) + |> render_submit() + |> follow_redirect(conn, ~p"/authors/#{author}") + + assert html =~ "Author updated successfully" + assert html =~ "some updated description" + end + end +end diff --git a/test/support/fixtures/authors_fixtures.ex b/test/support/fixtures/authors_fixtures.ex new file mode 100644 index 0000000..7cb97a3 --- /dev/null +++ b/test/support/fixtures/authors_fixtures.ex @@ -0,0 +1,23 @@ +defmodule Outlook.AuthorsFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Outlook.Authors` context. + """ + + @doc """ + Generate a author. + """ + def author_fixture(attrs \\ %{}) do + {:ok, author} = + attrs + |> Enum.into(%{ + description: "some description", + homepage_name: "some homepage_name", + homepage_url: "some homepage_url", + name: "some name" + }) + |> Outlook.Authors.create_author() + + author + end +end