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