Browse Source

basically working

master
Julian Noble 2 years ago
parent
commit
0087d7e587
  1. 116
      lib/chirp/timeline.ex
  2. 22
      lib/chirp/timeline/post.ex
  3. 81
      lib/chirp_web/live/post_live/form_component.ex
  4. 61
      lib/chirp_web/live/post_live/index.ex
  5. 34
      lib/chirp_web/live/post_live/index.html.heex
  6. 78
      lib/chirp_web/live/post_live/post_component.ex
  7. 21
      lib/chirp_web/live/post_live/show.ex
  8. 32
      lib/chirp_web/live/post_live/show.html.heex
  9. 9
      lib/chirp_web/router.ex
  10. 65
      test/chirp/timeline_test.exs
  11. 110
      test/chirp_web/live/post_live_test.exs
  12. 23
      test/support/fixtures/timeline_fixtures.ex

116
lib/chirp/timeline.ex

@ -0,0 +1,116 @@
defmodule Chirp.Timeline do
@moduledoc """
The Timeline context.
"""
import Ecto.Query, warn: false
alias Chirp.Repo
alias Chirp.Timeline.Post
@doc """
Returns the list of posts.
## Examples
iex> list_posts()
[%Post{}, ...]
"""
def list_posts do
Repo.all(from p in Post, order_by: [desc: p.id])
end
@doc """
Gets a single post.
Raises `Ecto.NoResultsError` if the Post does not exist.
## Examples
iex> get_post!(123)
%Post{}
iex> get_post!(456)
** (Ecto.NoResultsError)
"""
def get_post!(id), do: Repo.get!(Post, id)
@doc """
Creates a post.
## Examples
iex> create_post(%{field: value})
{:ok, %Post{}}
iex> create_post(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_post(attrs \\ %{}) do
%Post{}
|> Post.changeset(attrs)
|> Repo.insert()
|> broadcast(:post_created)
end
@doc """
Updates a post.
## Examples
iex> update_post(post, %{field: new_value})
{:ok, %Post{}}
iex> update_post(post, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_post(%Post{} = post, attrs) do
post
|> Post.changeset(attrs)
|> Repo.update()
|> broadcast(:post_updated)
end
@doc """
Deletes a post.
## Examples
iex> delete_post(post)
{:ok, %Post{}}
iex> delete_post(post)
{:error, %Ecto.Changeset{}}
"""
def delete_post(%Post{} = post) do
Repo.delete(post)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking post changes.
## Examples
iex> change_post(post)
%Ecto.Changeset{data: %Post{}}
"""
def change_post(%Post{} = post, attrs \\ %{}) do
Post.changeset(post, attrs)
end
def subscribe do
Phoenix.PubSub.subscribe(Chirp.PubSub,"posts")
end
defp broadcast({:error, _reason} = error, _event), do: error
defp broadcast({:ok,post}, event) do
Phoenix.PubSub.broadcast(Chirp.PubSub, "posts", {event,post})
{:ok, post}
end
end

22
lib/chirp/timeline/post.ex

@ -0,0 +1,22 @@
defmodule Chirp.Timeline.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :body, :string
field :likes_count, :integer, default: 0
field :reposts_count, :integer, default: 0
field :username, :string, default: "julz"
timestamps()
end
@doc false
def changeset(post, attrs) do
post
|> cast(attrs, [:body])
|> validate_required([:body])
|> validate_length(:body, min: 2, max: 250)
end
end

81
lib/chirp_web/live/post_live/form_component.ex

@ -0,0 +1,81 @@
defmodule ChirpWeb.PostLive.FormComponent do
use ChirpWeb, :live_component
alias Chirp.Timeline
@impl true
def render(assigns) do
~H"""
<div>
<.header>
<%= @title %>
<:subtitle>Use this form to manage post records in your database.</:subtitle>
</.header>
<.simple_form
:let={f}
for={@changeset}
id="post-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={{f, :body}} type="textarea" label="body" />
<:actions>
<.button phx-disable-with="Saving...">Save Post</.button>
</:actions>
</.simple_form>
</div>
"""
end
@impl true
def update(%{post: post} = assigns, socket) do
changeset = Timeline.change_post(post)
{:ok,
socket
|> assign(assigns)
|> assign(:changeset, changeset)}
end
@impl true
def handle_event("validate", %{"post" => post_params}, socket) do
changeset =
socket.assigns.post
|> Timeline.change_post(post_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event("save", %{"post" => post_params}, socket) do
save_post(socket, socket.assigns.action, post_params)
end
defp save_post(socket, :edit, post_params) do
case Timeline.update_post(socket.assigns.post, post_params) do
{:ok, _post} ->
{:noreply,
socket
|> put_flash(:info, "Post updated successfully")
|> push_navigate(to: socket.assigns.navigate)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, :changeset, changeset)}
end
end
defp save_post(socket, :new, post_params) do
case Timeline.create_post(post_params) do
{:ok, _post} ->
{:noreply,
socket
|> put_flash(:info, "Post created successfully")
|> push_navigate(to: socket.assigns.navigate)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
end

61
lib/chirp_web/live/post_live/index.ex

@ -0,0 +1,61 @@
defmodule ChirpWeb.PostLive.Index do
use ChirpWeb, :live_view
alias Chirp.Timeline
alias Chirp.Timeline.Post
@impl true
def mount(_params, _session, socket) do
if connected?(socket), do: Timeline.subscribe()
{:ok, assign(socket, :posts, list_posts()), temporary_assigns: [posts: []]}
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 Post")
|> assign(:post, Timeline.get_post!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Post")
|> assign(:post, %Post{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Posts")
|> assign(:post, nil)
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
post = Timeline.get_post!(id)
{:ok, _} = Timeline.delete_post(post)
{:noreply, assign(socket, :posts, list_posts())}
end
def handle_event("like", %{"id" => _id}, socket) do
{:noreply,socket}
end
def handle_event("repost", %{"id" => _id}, socket) do
{:noreply,socket}
end
def handle_info({:post_created,post}, socket) do
{:noreply, update(socket, :posts, fn posts-> [post|posts] end)}
end
def handle_info({:post_updated,post}, socket) do
{:noreply, update(socket, :posts, fn posts-> [post|posts] end)}
end
defp list_posts do
Timeline.list_posts()
end
end

34
lib/chirp_web/live/post_live/index.html.heex

@ -0,0 +1,34 @@
<.header>
Timeline
<:actions>
<.link patch={~p"/posts/new"}>
<.button>New Post</.button>
</.link>
</:actions>
</.header>
<!-- row_click={&JS.navigate(~p"/posts/#{&1}")} -->
<div id="posts" phx-update="prepend">
<%= for post <- @posts do %>
<.live_component
module={ChirpWeb.PostLive.PostComponent}
id={post.id}
post={post}
/>
<% end %>
</div>
<%= if @live_action in [:new, :edit] do %>
<.modal id="post-modal" show on_cancel={JS.navigate(~p"/posts")}>
<.live_component
module={ChirpWeb.PostLive.FormComponent}
id={@post.id || :new}
title={@page_title}
action={@live_action}
post={@post}
navigate={~p"/posts"}
/>
</.modal>
<% end %>

78
lib/chirp_web/live/post_live/post_component.ex

@ -0,0 +1,78 @@
defmodule ChirpWeb.PostLive.PostComponent do
use ChirpWeb, :live_component
@spec render(any) :: Phoenix.LiveView.Rendered.t()
def render(assigns) do
~H"""
<div id={"post #{assigns.post.id}"} class="container mx-auto">
<div class="flex flex-row">
<div class="basis-1/4">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<div label="Username"><%= @post.username %></div>
</div>
<div class="basis-3/4">
<div class="p-6 max-w-lg bg-white rounded-lg border border-gray-200 shadow-md dark:bg-gray-800 dark:border-gray-700">
<pre label="Body" class="mb-3 font-normal text-gray-500 dark:text-gray-400"><%= @post.body %></pre>
<div class="grid grid-flow-col gap-4 auto-cols-max">
<div class="p-1">
<.link navigate={~p"/posts/#{@post}"} class="inline-flex items-center text-blue-600 hover:underline">
Show
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="p-1 w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</.link>
</div>
<div class="p-1 flex flex-row">
<.link phx-click="like" phx-value-id={@post.id}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
</svg>
</.link>
<span label="Likes count">
<%= @post.likes_count %>
</span>
</div>
<div class="p-1 flex flex-row">
<.link phx-click="repost" phx-value-id={@post.id}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12c0-1.232-.046-2.453-.138-3.662a4.006 4.006 0 00-3.7-3.7 48.678 48.678 0 00-7.324 0 4.006 4.006 0 00-3.7 3.7c-.017.22-.032.441-.046.662M19.5 12l3-3m-3 3l-3-3m-12 3c0 1.232.046 2.453.138 3.662a4.006 4.006 0 003.7 3.7 48.656 48.656 0 007.324 0 4.006 4.006 0 003.7-3.7c.017-.22.032-.441.046-.662M4.5 12l3 3m-3-3l-3 3" />
</svg>
</.link>
<div label="Reposts countt">
<%= @post.reposts_count %>
</div>
</div>
<div class="p-1">
<.link patch={~p"/posts/#{@post}/edit"}>Edit</.link>
</div>
<div class="p-1">
<.link phx-click={JS.push("delete", value: %{id: @post.id})} data-confirm="Are you sure?">
Delete
</.link>
</div>
</div>
</div>
</div>
</div>
</div>
"""
end
end

21
lib/chirp_web/live/post_live/show.ex

@ -0,0 +1,21 @@
defmodule ChirpWeb.PostLive.Show do
use ChirpWeb, :live_view
alias Chirp.Timeline
@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(:post, Timeline.get_post!(id))}
end
defp page_title(:show), do: "Show Post"
defp page_title(:edit), do: "Edit Post"
end

32
lib/chirp_web/live/post_live/show.html.heex

@ -0,0 +1,32 @@
<.header>
Post <%= @post.id %>
<:subtitle>This is a post record from your database.</:subtitle>
<:actions>
<.link patch={~p"/posts/#{@post}/show/edit"} phx-click={JS.push_focus()}>
<.button>Edit post</.button>
</.link>
</:actions>
</.header>
<.list>
<:item title="Username"><%= @post.username %></:item>
<:item title="Body"><%= @post.body %></:item>
<:item title="Likes count"><%= @post.likes_count %></:item>
<:item title="Reposts count"><%= @post.reposts_count %></:item>
</.list>
<.back navigate={~p"/posts"}>Back to posts</.back>
<%= if @live_action in [:edit] do %>
<.modal id="post-modal" show on_cancel={JS.patch(~p"/posts/#{@post}")}>
<.live_component
module={ChirpWeb.PostLive.FormComponent}
id={@post.id}
title={@page_title}
action={@live_action}
post={@post}
navigate={~p"/posts/#{@post}"}
/>
</.modal>
<% end %>

9
lib/chirp_web/router.ex

@ -18,6 +18,15 @@ defmodule ChirpWeb.Router do
pipe_through :browser
get "/", PageController, :index
live "/posts", PostLive.Index, :index
live "/posts/new", PostLive.Index, :new
live "/posts/:id/edit", PostLive.Index, :edit
live "/posts/:id", PostLive.Show, :show
live "/posts/:id/show/edit", PostLive.Show, :edit
end
# Other scopes may use custom stacks.

65
test/chirp/timeline_test.exs

@ -0,0 +1,65 @@
defmodule Chirp.TimelineTest do
use Chirp.DataCase
alias Chirp.Timeline
describe "posts" do
alias Chirp.Timeline.Post
import Chirp.TimelineFixtures
@invalid_attrs %{body: nil, likes_count: nil, reposts_count: nil, username: nil}
test "list_posts/0 returns all posts" do
post = post_fixture()
assert Timeline.list_posts() == [post]
end
test "get_post!/1 returns the post with given id" do
post = post_fixture()
assert Timeline.get_post!(post.id) == post
end
test "create_post/1 with valid data creates a post" do
valid_attrs = %{body: "some body", likes_count: 42, reposts_count: 42, username: "some username"}
assert {:ok, %Post{} = post} = Timeline.create_post(valid_attrs)
assert post.body == "some body"
assert post.likes_count == 42
assert post.reposts_count == 42
assert post.username == "some username"
end
test "create_post/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Timeline.create_post(@invalid_attrs)
end
test "update_post/2 with valid data updates the post" do
post = post_fixture()
update_attrs = %{body: "some updated body", likes_count: 43, reposts_count: 43, username: "some updated username"}
assert {:ok, %Post{} = post} = Timeline.update_post(post, update_attrs)
assert post.body == "some updated body"
assert post.likes_count == 43
assert post.reposts_count == 43
assert post.username == "some updated username"
end
test "update_post/2 with invalid data returns error changeset" do
post = post_fixture()
assert {:error, %Ecto.Changeset{}} = Timeline.update_post(post, @invalid_attrs)
assert post == Timeline.get_post!(post.id)
end
test "delete_post/1 deletes the post" do
post = post_fixture()
assert {:ok, %Post{}} = Timeline.delete_post(post)
assert_raise Ecto.NoResultsError, fn -> Timeline.get_post!(post.id) end
end
test "change_post/1 returns a post changeset" do
post = post_fixture()
assert %Ecto.Changeset{} = Timeline.change_post(post)
end
end
end

110
test/chirp_web/live/post_live_test.exs

@ -0,0 +1,110 @@
defmodule ChirpWeb.PostLiveTest do
use ChirpWeb.ConnCase
import Phoenix.LiveViewTest
import Chirp.TimelineFixtures
@create_attrs %{body: "some body", likes_count: 42, reposts_count: 42, username: "some username"}
@update_attrs %{body: "some updated body", likes_count: 43, reposts_count: 43, username: "some updated username"}
@invalid_attrs %{body: nil, likes_count: nil, reposts_count: nil, username: nil}
defp create_post(_) do
post = post_fixture()
%{post: post}
end
describe "Index" do
setup [:create_post]
test "lists all posts", %{conn: conn, post: post} do
{:ok, _index_live, html} = live(conn, ~p"/posts")
assert html =~ "Listing Posts"
assert html =~ post.body
end
test "saves new post", %{conn: conn} do
{:ok, index_live, _html} = live(conn, ~p"/posts")
assert index_live |> element("a", "New Post") |> render_click() =~
"New Post"
assert_patch(index_live, ~p"/posts/new")
assert index_live
|> form("#post-form", post: @invalid_attrs)
|> render_change() =~ "can&#39;t be blank"
{:ok, _, html} =
index_live
|> form("#post-form", post: @create_attrs)
|> render_submit()
|> follow_redirect(conn, ~p"/posts")
assert html =~ "Post created successfully"
assert html =~ "some body"
end
test "updates post in listing", %{conn: conn, post: post} do
{:ok, index_live, _html} = live(conn, ~p"/posts")
assert index_live |> element("#posts-#{post.id} a", "Edit") |> render_click() =~
"Edit Post"
assert_patch(index_live, ~p"/posts/#{post}/edit")
assert index_live
|> form("#post-form", post: @invalid_attrs)
|> render_change() =~ "can&#39;t be blank"
{:ok, _, html} =
index_live
|> form("#post-form", post: @update_attrs)
|> render_submit()
|> follow_redirect(conn, ~p"/posts")
assert html =~ "Post updated successfully"
assert html =~ "some updated body"
end
test "deletes post in listing", %{conn: conn, post: post} do
{:ok, index_live, _html} = live(conn, ~p"/posts")
assert index_live |> element("#posts-#{post.id} a", "Delete") |> render_click()
refute has_element?(index_live, "#post-#{post.id}")
end
end
describe "Show" do
setup [:create_post]
test "displays post", %{conn: conn, post: post} do
{:ok, _show_live, html} = live(conn, ~p"/posts/#{post}")
assert html =~ "Show Post"
assert html =~ post.body
end
test "updates post within modal", %{conn: conn, post: post} do
{:ok, show_live, _html} = live(conn, ~p"/posts/#{post}")
assert show_live |> element("a", "Edit") |> render_click() =~
"Edit Post"
assert_patch(show_live, ~p"/posts/#{post}/show/edit")
assert show_live
|> form("#post-form", post: @invalid_attrs)
|> render_change() =~ "can&#39;t be blank"
{:ok, _, html} =
show_live
|> form("#post-form", post: @update_attrs)
|> render_submit()
|> follow_redirect(conn, ~p"/posts/#{post}")
assert html =~ "Post updated successfully"
assert html =~ "some updated body"
end
end
end

23
test/support/fixtures/timeline_fixtures.ex

@ -0,0 +1,23 @@
defmodule Chirp.TimelineFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Chirp.Timeline` context.
"""
@doc """
Generate a post.
"""
def post_fixture(attrs \\ %{}) do
{:ok, post} =
attrs
|> Enum.into(%{
body: "some body",
likes_count: 42,
reposts_count: 42,
username: "some username"
})
|> Chirp.Timeline.create_post()
post
end
end
Loading…
Cancel
Save