summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLogan Hunt <loganhunt@simponic.xyz>2022-04-15 13:00:42 -0600
committerLogan Hunt <loganhunt@simponic.xyz>2022-04-15 13:00:42 -0600
commit3cf9f4a364ac91cca30799c8379a682139425e71 (patch)
treedb94f64634e0a840b0a5d1eeef43460ef4e8dd21
parentdb7c2321cd0af59f9e810e84c7d4eb83ec416458 (diff)
downloadaggiedit-3cf9f4a364ac91cca30799c8379a682139425e71.tar.gz
aggiedit-3cf9f4a364ac91cca30799c8379a682139425e71.zip
Add comments and vote models; pub/sub voting on posts
-rw-r--r--lib/aggiedit/accounts/user.ex2
-rw-r--r--lib/aggiedit/roles.ex2
-rw-r--r--lib/aggiedit/rooms.ex21
-rw-r--r--lib/aggiedit/rooms/comment.ex20
-rw-r--r--lib/aggiedit/rooms/post.ex6
-rw-r--r--lib/aggiedit/rooms/vote.ex20
-rw-r--r--lib/aggiedit_web/live/post_live/index.ex21
-rw-r--r--lib/aggiedit_web/live/post_live/index.html.heex26
-rw-r--r--lib/aggiedit_web/templates/layout/root.html.heex1
-rw-r--r--priv/repo/migrations/20220406185124_create_posts.exs1
-rw-r--r--priv/repo/migrations/20220415015055_create_post_votes.exs15
-rw-r--r--priv/repo/migrations/20220415021530_create_post_comments.exs16
12 files changed, 141 insertions, 10 deletions
diff --git a/lib/aggiedit/accounts/user.ex b/lib/aggiedit/accounts/user.ex
index 65c2463..45c6525 100644
--- a/lib/aggiedit/accounts/user.ex
+++ b/lib/aggiedit/accounts/user.ex
@@ -14,6 +14,8 @@ defmodule Aggiedit.Accounts.User do
belongs_to :room, Room, on_replace: :update
has_many :posts, Aggiedit.Rooms.Post
+ has_many :votes, Aggiedit.Post.Vote
+ has_many :comments, Aggiedit.Post.Comment
timestamps()
end
diff --git a/lib/aggiedit/roles.ex b/lib/aggiedit/roles.ex
index 3ec6bfd..41da54a 100644
--- a/lib/aggiedit/roles.ex
+++ b/lib/aggiedit/roles.ex
@@ -6,7 +6,7 @@ defmodule Aggiedit.Roles do
def guard?(user, action, object)
def guard?(%User{role: :admin}, _, _), do: true
def guard?(%User{room_id: rid}, :index, %Room{id: rid}), do: true
- def guard?(%User{room_id: rid}, :show, %Post{room_id: rid}), do: true
+ def guard?(%User{room_id: rid}, action, %Post{room_id: rid}) when action in [:show, :vote], do: true
def guard?(%User{id: id, room_id: rid}, action, %Post{user_id: id, room_id: rid}) when action in [:delete, :edit], do: true
def guard?(_, _, _), do: false
diff --git a/lib/aggiedit/rooms.ex b/lib/aggiedit/rooms.ex
index 898dfe1..41df588 100644
--- a/lib/aggiedit/rooms.ex
+++ b/lib/aggiedit/rooms.ex
@@ -6,9 +6,11 @@ defmodule Aggiedit.Rooms do
import Ecto.Query, warn: false
alias Aggiedit.Repo
- alias Aggiedit.Accounts
+ alias Aggiedit.Accounts.User
alias Aggiedit.Rooms.Room
+ alias Aggiedit.Post.{Vote, Comment}
+
alias Phoenix.PubSub
def list_rooms do
@@ -91,6 +93,23 @@ defmodule Aggiedit.Rooms do
Post.changeset(post, attrs)
end
+ def vote_count(post) do
+ votes = post
+ |> Repo.preload(:votes)
+ |> Map.get(:votes)
+ |> Enum.map(fn vote -> if vote.is_up, do: 1, else: -1 end)
+ |> Enum.sum()
+ end
+
+ def vote_post(%Post{} = post, %User{} = user, direction) do
+ is_up = direction == "upvote"
+ vote = %Vote{is_up: is_up, user: user, post: post}
+ |> Repo.insert(on_conflict: [set: [is_up: is_up]], conflict_target: [:user_id, :post_id])
+ post = change_post(post, %{score: vote_count(post)})
+ |> Repo.update()
+ broadcast_post_over_room(post, :post_voted)
+ end
+
defp broadcast_post_over_room({:error, _reason}=error, _event), do: error
defp broadcast_post_over_room({:ok, post}, event) do
PubSub.broadcast(Aggiedit.PubSub, "room:#{post.room_id}:posts", {event, post})
diff --git a/lib/aggiedit/rooms/comment.ex b/lib/aggiedit/rooms/comment.ex
new file mode 100644
index 0000000..6747ba3
--- /dev/null
+++ b/lib/aggiedit/rooms/comment.ex
@@ -0,0 +1,20 @@
+defmodule Aggiedit.Post.Comment do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ schema "post_comments" do
+ field :comment, :string
+
+ belongs_to :user, Aggiedit.Accounts.User
+ belongs_to :post, Aggiedit.Rooms.Post
+
+ timestamps()
+ end
+
+ @doc false
+ def changeset(comment, attrs) do
+ comment
+ |> cast(attrs, [:comment])
+ |> validate_required([:comment])
+ end
+end
diff --git a/lib/aggiedit/rooms/post.ex b/lib/aggiedit/rooms/post.ex
index ee9450d..e1aa59a 100644
--- a/lib/aggiedit/rooms/post.ex
+++ b/lib/aggiedit/rooms/post.ex
@@ -5,18 +5,22 @@ defmodule Aggiedit.Rooms.Post do
schema "posts" do
field :body, :string
field :title, :string
+ field :score, :integer
belongs_to :room, Aggiedit.Rooms.Room
belongs_to :user, Aggiedit.Accounts.User
belongs_to :upload, Aggiedit.Uploads.Upload
+ has_many :votes, Aggiedit.Post.Vote
+ has_many :comments, Aggiedit.Post.Comment
+
timestamps()
end
@doc false
def changeset(post, attrs) do
post
- |> cast(attrs, [:title, :body])
+ |> cast(attrs, [:title, :body, :score])
|> validate_required([:title, :body])
end
diff --git a/lib/aggiedit/rooms/vote.ex b/lib/aggiedit/rooms/vote.ex
new file mode 100644
index 0000000..f402e87
--- /dev/null
+++ b/lib/aggiedit/rooms/vote.ex
@@ -0,0 +1,20 @@
+defmodule Aggiedit.Post.Vote do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ schema "post_votes" do
+ field :is_up, :boolean
+
+ belongs_to :user, Aggiedit.Accounts.User
+ belongs_to :post, Aggiedit.Rooms.Post
+
+ timestamps()
+ end
+
+ @doc false
+ def changeset(vote, attrs) do
+ vote
+ |> cast(attrs, [:direction])
+ |> validate_required([:direction])
+ end
+end
diff --git a/lib/aggiedit_web/live/post_live/index.ex b/lib/aggiedit_web/live/post_live/index.ex
index 59ec234..d231ea0 100644
--- a/lib/aggiedit_web/live/post_live/index.ex
+++ b/lib/aggiedit_web/live/post_live/index.ex
@@ -12,7 +12,14 @@ defmodule AggieditWeb.PostLive.Index do
case socket.assigns do
%{:room => room} ->
if connected?(socket), do: Rooms.subscribe(socket.assigns.room)
- {:ok, assign(socket, %{:posts => room |> Repo.preload(posts: [:user, :upload]) |> Map.get(:posts)}), temporary_assigns: [posts: []]}
+ posts = room
+ |> Repo.preload(posts: [:user, :upload])
+ |> Map.get(:posts)
+ votes = socket.assigns.current_user
+ |> Repo.preload(:votes)
+ |> Map.get(:votes)
+ |> Enum.reduce(%{}, fn v, a -> Map.put(a, v.post_id, v) end)
+ {:ok, assign(socket, %{:posts => posts, :votes => votes}), temporary_assigns: [posts: []]}
_ -> {:ok, socket}
end
end
@@ -50,6 +57,16 @@ defmodule AggieditWeb.PostLive.Index do
|> assign(:post, nil)
end
+ def handle_event(vote, %{"id" => id}, socket) when vote in ["upvote", "downvote"] do
+ post = Rooms.get_post!(id)
+ if Roles.guard?(socket.assigns.current_user, :vote, post) do
+ Rooms.vote_post(post, socket.assigns.current_user, vote)
+ {:noreply, socket}
+ else
+ {:noreply, socket |> put_flash(:error, "You don't have permission to do that.") |> redirect(to: Routes.post_show_path(socket, :show, socket.assigns.room, post))}
+ end
+ end
+
@impl true
def handle_event("delete", %{"id" => id}, socket) do
post = Rooms.get_post!(id)
@@ -62,7 +79,7 @@ defmodule AggieditWeb.PostLive.Index do
end
@impl true
- def handle_info({action, post}, socket) when action in [:post_created, :post_updated, :post_deleted] do
+ def handle_info({action, post}, socket) when action in [:post_created, :post_updated, :post_deleted, :post_voted] do
{:noreply, update(socket, :posts, fn posts ->
[posts | post]
end)}
diff --git a/lib/aggiedit_web/live/post_live/index.html.heex b/lib/aggiedit_web/live/post_live/index.html.heex
index efb42cb..89767f8 100644
--- a/lib/aggiedit_web/live/post_live/index.html.heex
+++ b/lib/aggiedit_web/live/post_live/index.html.heex
@@ -18,13 +18,29 @@
<div id="posts" phx-update="prepend">
<%= for post <- @posts do %>
<div id={"post-#{post.id}"} class="card d-flex flex-row align-items-center p-2 m-2 shadow">
- <%= if !is_nil(post.upload) do %>
- <%= live_redirect to: Routes.post_show_path(@socket, :show, @room, post) do %>
- <div class="card-image d-flex justify-content-center" style="width: 100px">
+ <div class="d-flex flex-column m-2">
+ <%=
+ has_vote = Map.has_key?(@votes, post.id)
+ is_upvote = has_vote && @votes[post.id].is_up
+ ""
+ %>
+ <div class="d-flex">
+ <span><%= link "", to: "#", phx_click: "upvote", phx_value_id: post.id, class: "bi bi-arrow-up-circle#{if has_vote && is_upvote, do: "-fill", else: ""}" %></span>
+ </div>
+ <div class="d-flex">
+ <%= post.score %>
+ </div>
+ <div class="d-flex">
+ <span><%= link "", to: "#", phx_click: "downvote", phx_value_id: post.id, class: "bi bi-arrow-down-circle#{if has_vote && !is_upvote, do: "-fill", else: ""}" %></span>
+ </div>
+ </div>
+ <div class="m-2 card-image d-flex justify-content-center" style="width: 100px">
+ <%= if !is_nil(post.upload) do %>
+ <%= live_redirect to: Routes.post_show_path(@socket, :show, @room, post) do %>
<img class="fluid-img thumbnail" src={Routes.static_path(@socket, "/uploads/#{post.upload.file}")} />
- </div>
+ <% end %>
<% end %>
- <% end %>
+ </div>
<div class="card-body">
<%= live_redirect to: Routes.post_show_path(@socket, :show, @room, post) do %>
<h4 class="card-title"><%= post.title %></h4>
diff --git a/lib/aggiedit_web/templates/layout/root.html.heex b/lib/aggiedit_web/templates/layout/root.html.heex
index a164571..14c7605 100644
--- a/lib/aggiedit_web/templates/layout/root.html.heex
+++ b/lib/aggiedit_web/templates/layout/root.html.heex
@@ -11,6 +11,7 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css">
</head>
<body class="bg-secondary min-vh-100">
<header>
diff --git a/priv/repo/migrations/20220406185124_create_posts.exs b/priv/repo/migrations/20220406185124_create_posts.exs
index 223265c..9febae8 100644
--- a/priv/repo/migrations/20220406185124_create_posts.exs
+++ b/priv/repo/migrations/20220406185124_create_posts.exs
@@ -5,6 +5,7 @@ defmodule Aggiedit.Repo.Migrations.CreatePosts do
create table(:posts) do
add :title, :text
add :body, :text
+ add :score, :integer, default: 0
add :user_id, references(:users, on_delete: :nothing)
add :upload_id, references(:uploads, on_delete: :nothing)
add :room_id, references(:rooms, on_delete: :nothing)
diff --git a/priv/repo/migrations/20220415015055_create_post_votes.exs b/priv/repo/migrations/20220415015055_create_post_votes.exs
new file mode 100644
index 0000000..444a89c
--- /dev/null
+++ b/priv/repo/migrations/20220415015055_create_post_votes.exs
@@ -0,0 +1,15 @@
+defmodule Aggiedit.Repo.Migrations.CreatePostVotes do
+ use Ecto.Migration
+
+ def change do
+ create table(:post_votes) do
+ add :is_up, :boolean
+ add :user_id, references(:users, on_delete: :nothing)
+ add :post_id, references(:posts, on_delete: :delete_all)
+
+ timestamps()
+ end
+
+ create unique_index(:post_votes, [:user_id, :post_id])
+ end
+end
diff --git a/priv/repo/migrations/20220415021530_create_post_comments.exs b/priv/repo/migrations/20220415021530_create_post_comments.exs
new file mode 100644
index 0000000..14797a2
--- /dev/null
+++ b/priv/repo/migrations/20220415021530_create_post_comments.exs
@@ -0,0 +1,16 @@
+defmodule Aggiedit.Repo.Migrations.CreatePostComments do
+ use Ecto.Migration
+
+ def change do
+ create table(:post_comments) do
+ add :comment, :text
+ add :user_id, references(:users, on_delete: :delete_all)
+ add :post_id, references(:posts, on_delete: :delete_all)
+
+ timestamps()
+ end
+
+ create index(:post_comments, [:user_id])
+ create index(:post_comments, [:post_id])
+ end
+end