From 551c7515ae1b7981889db94cf6ab81b9a59c5911 Mon Sep 17 00:00:00 2001 From: "byte[]" Date: Sat, 16 Nov 2019 23:59:24 -0500 Subject: [PATCH] add commenting and notifications generation --- lib/philomena/comments.ex | 78 +++++++++++++++---- lib/philomena/comments/comment.ex | 13 +++- lib/philomena/images/image.ex | 2 + lib/philomena/notifications.ex | 34 +++++++- lib/philomena/notifications/notification.ex | 4 +- .../controllers/image/comment_controller.ex | 71 +++++++++++++++++ .../controllers/image_controller.ex | 15 +++- lib/philomena_web/router.ex | 2 +- .../templates/image/comment/_form.html.slime | 25 ++++++ .../templates/image/show.html.slime | 14 ++++ 10 files changed, 234 insertions(+), 24 deletions(-) create mode 100644 lib/philomena_web/templates/image/comment/_form.html.slime diff --git a/lib/philomena/comments.ex b/lib/philomena/comments.ex index 45e0afcb..7b6876e8 100644 --- a/lib/philomena/comments.ex +++ b/lib/philomena/comments.ex @@ -4,22 +4,12 @@ defmodule Philomena.Comments do """ import Ecto.Query, warn: false + alias Ecto.Multi alias Philomena.Repo alias Philomena.Comments.Comment - - @doc """ - Returns the list of comments. - - ## Examples - - iex> list_comments() - [%Comment{}, ...] - - """ - def list_comments do - Repo.all(Comment) - end + alias Philomena.Images.Image + alias Philomena.Notifications @doc """ Gets a single comment. @@ -49,10 +39,48 @@ defmodule Philomena.Comments do {:error, %Ecto.Changeset{}} """ - def create_comment(attrs \\ %{}) do - %Comment{} - |> Comment.changeset(attrs) - |> Repo.insert() + def create_comment(image, user, attrs \\ %{}) do + user_id = if user, do: user.id, else: nil + comment = + %Comment{image_id: image.id, user_id: user_id} + |> Comment.creation_changeset(attrs) + + image_query = + Image + |> where(id: ^image.id) + + Multi.new + |> Multi.insert(:comment, comment) + |> Multi.update_all(:image, image_query, inc: [comments_count: 1]) + |> Repo.transaction() + end + + def notify_comment(comment) do + spawn fn -> + image = + comment + |> Repo.preload(:image) + |> Map.fetch!(:image) + + subscriptions = + image + |> Repo.preload(:subscriptions) + |> Map.fetch!(:subscriptions) + + Notifications.notify( + comment, + subscriptions, + %{ + actor_id: image.id, + actor_type: "Image", + actor_child_id: comment.id, + actor_child_type: "Comment", + action: "commented on" + } + ) + end + + comment end @doc """ @@ -101,4 +129,20 @@ defmodule Philomena.Comments do def change_comment(%Comment{} = comment) do Comment.changeset(comment, %{}) end + + def reindex_comment(%Comment{} = comment) do + spawn fn -> + Comment + |> preload(^indexing_preloads()) + |> where(id: ^comment.id) + |> Repo.one() + |> Comment.index_document() + end + + comment + end + + def indexing_preloads do + [:user, image: :tags] + end end diff --git a/lib/philomena/comments/comment.ex b/lib/philomena/comments/comment.ex index 21d731ac..6a05b60c 100644 --- a/lib/philomena/comments/comment.ex +++ b/lib/philomena/comments/comment.ex @@ -31,9 +31,18 @@ defmodule Philomena.Comments.Comment do end @doc false + def creation_changeset(comment, attrs) do + comment + |> cast(attrs, [:body, :anonymous]) + |> validate_required([:body]) + |> validate_length(:body, min: 1, max: 300_000, count: :bytes) + end + def changeset(comment, attrs) do comment - |> cast(attrs, []) - |> validate_required([]) + |> cast(attrs, [:body, :edit_reason]) + |> validate_required([:body]) + |> validate_length(:body, min: 1, max: 300_000, count: :bytes) + |> validate_length(:edit_reason, max: 70, count: :bytes) end end diff --git a/lib/philomena/images/image.ex b/lib/philomena/images/image.ex index b6818834..d82f4fc3 100644 --- a/lib/philomena/images/image.ex +++ b/lib/philomena/images/image.ex @@ -11,6 +11,7 @@ defmodule Philomena.Images.Image do alias Philomena.ImageVotes.ImageVote alias Philomena.ImageFaves.ImageFave alias Philomena.ImageHides.ImageHide + alias Philomena.Images.Subscription alias Philomena.Users.User alias Philomena.Images.Tagging alias Philomena.Galleries @@ -24,6 +25,7 @@ defmodule Philomena.Images.Image do has_many :hides, ImageHide has_many :taggings, Tagging has_many :gallery_interactions, Galleries.Interaction + has_many :subscriptions, Subscription has_many :tags, through: [:taggings, :tag] has_many :upvoters, through: [:upvotes, :user] has_many :downvoters, through: [:downvotes, :user] diff --git a/lib/philomena/notifications.ex b/lib/philomena/notifications.ex index f6c1dc64..4508c40a 100644 --- a/lib/philomena/notifications.ex +++ b/lib/philomena/notifications.ex @@ -70,7 +70,7 @@ defmodule Philomena.Notifications do def update_notification(%Notification{} = notification, attrs) do notification |> Notification.changeset(attrs) - |> Repo.update() + |> Repo.insert_or_update() end @doc """ @@ -197,4 +197,36 @@ defmodule Philomena.Notifications do def change_unread_notification(%UnreadNotification{} = unread_notification) do UnreadNotification.changeset(unread_notification, %{}) end + + def notify(_actor_child, [], _params), do: nil + def notify(actor_child, subscriptions, params) do + # Don't push to the user that created the notification + subscriptions = + case actor_child do + %{user_id: id} -> + subscriptions + |> Enum.reject(& &1.user_id == id) + + _ -> + subscriptions + end + + Repo.transaction(fn -> + notification = + Notification + |> Repo.get_by(actor_id: params.actor_id, actor_type: params.actor_type) + + {:ok, notification} = + (notification || %Notification{}) + |> update_notification(params) + + # Insert the notification to any watchers who do not have it + unreads = + subscriptions + |> Enum.map(&%{user_id: &1.user_id, notification_id: notification.id}) + + UnreadNotification + |> Repo.insert_all(unreads, on_conflict: :nothing) + end) + end end diff --git a/lib/philomena/notifications/notification.ex b/lib/philomena/notifications/notification.ex index ad9c7d02..68805097 100644 --- a/lib/philomena/notifications/notification.ex +++ b/lib/philomena/notifications/notification.ex @@ -20,7 +20,7 @@ defmodule Philomena.Notifications.Notification do @doc false def changeset(notification, attrs) do notification - |> cast(attrs, []) - |> validate_required([]) + |> cast(attrs, [:actor_id, :actor_type, :actor_child_id, :actor_child_type, :action]) + |> validate_required([:actor_id, :actor_type, :action]) end end diff --git a/lib/philomena_web/controllers/image/comment_controller.ex b/lib/philomena_web/controllers/image/comment_controller.ex index 942b8f13..f6caaa5a 100644 --- a/lib/philomena_web/controllers/image/comment_controller.ex +++ b/lib/philomena_web/controllers/image/comment_controller.ex @@ -2,12 +2,40 @@ defmodule PhilomenaWeb.Image.CommentController do use PhilomenaWeb, :controller alias Philomena.{Images.Image, Comments.Comment, Textile.Renderer} + alias Philomena.Comments + alias Philomena.Images alias Philomena.Repo import Ecto.Query + plug PhilomenaWeb.Plugs.CanaryMapPlug, create: :show, edit: :show, update: :show plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true + + # Undo the previous private parameter screwery + plug PhilomenaWeb.Plugs.CanaryMapPlug, create: :create, edit: :edit, update: :update plug :load_and_authorize_resource, model: Comment, only: [:show], preload: [:image, user: [awards: :badge]] + plug PhilomenaWeb.Plugs.FilterBannedUsers when action in [:create, :edit, :update] + + def index(conn, %{"comment_id" => comment_id}) do + comment = + Comment + |> where(image_id: ^conn.assigns.image.id) + |> where(id: ^comment_id) + |> Repo.one!() + + offset = + Comment + |> where(image_id: ^conn.assigns.image.id) + |> where([c], c.created_at > ^comment.created_at) + |> Repo.aggregate(:count, :id) + + %{page_size: page_size} = conn.assigns.pagination + page = div(offset, page_size) + + conn + |> redirect(to: Routes.image_comment_path(conn, :index, conn.assigns.image, page: page)) + end + def index(conn, _params) do comments = Comment @@ -30,4 +58,47 @@ defmodule PhilomenaWeb.Image.CommentController do rendered = Renderer.render_one(conn.assigns.comment) render(conn, "show.html", layout: false, image: conn.assigns.image, comment: conn.assigns.comment, body: rendered) end + + def create(conn, %{"comment" => comment_params}) do + user = conn.assigns.current_user + image = conn.assigns.image + + case Comments.create_comment(image, user, comment_params) do + {:ok, %{comment: comment}} -> + Comments.notify_comment(comment) + Comments.reindex_comment(comment) + Images.reindex_image(conn.assigns.image) + + conn + |> put_flash(:info, "Comment created successfully.") + |> redirect(to: Routes.image_path(conn, :show, image) <> "#comment_#{comment.id}") + + _error -> + conn + |> put_flash(:error, "There was an error posting your comment") + |> redirect(to: Routes.image_path(conn, :show, image)) + end + end + + def edit(conn, _params) do + changeset = + conn.assigns.comment + |> Comments.change_comment() + + render(conn, "edit.html", comment: conn.assigns.comment, changeset: changeset) + end + + def update(conn, %{"comment" => comment_params}) do + case Comments.update_comment(conn.assigns.comment, comment_params) do + {:ok, _comment} -> + conn + |> put_flash(:info, "Comment updated successfully.") + |> redirect(to: Routes.image_path(conn, :show, conn.assigns.image) <> "#comment_#{conn.assigns.comment.id}") + + _error -> + conn + |> put_flash(:error, "There was an error editing your comment") + |> redirect(to: Routes.image_path(conn, :show, conn.assigns.image)) + end + end end diff --git a/lib/philomena_web/controllers/image_controller.ex b/lib/philomena_web/controllers/image_controller.ex index 723fd8bf..75f1bd52 100644 --- a/lib/philomena_web/controllers/image_controller.ex +++ b/lib/philomena_web/controllers/image_controller.ex @@ -3,6 +3,7 @@ defmodule PhilomenaWeb.ImageController do alias Philomena.{Images.Image, Comments.Comment, Textile.Renderer} alias Philomena.Interactions + alias Philomena.Comments alias Philomena.Repo import Ecto.Query @@ -52,6 +53,18 @@ defmodule PhilomenaWeb.ImageController do interactions = Interactions.user_interactions([image], conn.assigns.current_user) - render(conn, "show.html", image: image, comments: comments, description: description, interactions: interactions) + comment_changeset = + %Comment{} + |> Comments.change_comment() + + render( + conn, + "show.html", + image: image, + comments: comments, + comment_changeset: comment_changeset, + description: description, + interactions: interactions + ) end end diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index 8cd4baa8..140efc1a 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -68,7 +68,7 @@ defmodule PhilomenaWeb.Router do resources "/activity", ActivityController, only: [:index] resources "/images", ImageController, only: [:index, :show] do - resources "/comments", Image.CommentController, only: [:index, :show] + resources "/comments", Image.CommentController, only: [:index, :show, :create] end resources "/tags", TagController, only: [:index, :show] resources "/search", SearchController, only: [:index] diff --git a/lib/philomena_web/templates/image/comment/_form.html.slime b/lib/philomena_web/templates/image/comment/_form.html.slime new file mode 100644 index 00000000..e125e1ab --- /dev/null +++ b/lib/philomena_web/templates/image/comment/_form.html.slime @@ -0,0 +1,25 @@ += form_for @changeset, Routes.image_comment_path(@conn, :create, @image), fn f -> + = if @changeset.action do + .alert.alert-danger + p Oops, something went wrong! Please check the errors below. + + .block + .block__header.block__header--js-tabbed + a.selected href="#" data-click-tab="write" + i.fa.fa-pencil> + ' Edit + + a href="#" data-click-tab="preview" + i.fa.fa-eye> + ' Preview + + .block__tab.communication-edit__tab.selected data-tab="write" + .field + = textarea f, :body, class: "input input--wide input--text js-preview-input js-toolbar-input", placeholder: "Please read the site rules before posting and use [spoiler][/spoiler] for above-rating stuff.", required: true + = error_tag f, :body + + .block__tab.communication-edit__tab.hidden data-tab="preview" + ' [Loading preview...] + + .block__content.communication-edit__actions + = submit "Post", class: "button" \ No newline at end of file diff --git a/lib/philomena_web/templates/image/show.html.slime b/lib/philomena_web/templates/image/show.html.slime index 6a0e1850..5744116a 100644 --- a/lib/philomena_web/templates/image/show.html.slime +++ b/lib/philomena_web/templates/image/show.html.slime @@ -19,5 +19,19 @@ em> no source provided yet h4 Comments + = cond do + - @conn.assigns.current_ban -> + .block.block--fixed.block--warning + h4 You've been banned! + p + ' You cannnot post comments or update metadata (or do anything but + ' read, really) until + = pretty_time(@conn.assigns.current_ban.valid_until) + + - @image.commenting_allowed -> + = render PhilomenaWeb.Image.CommentView, "_form.html", image: @image, changeset: @comment_changeset, conn: @conn + + - true -> + #comments data-current-url=Routes.image_comment_path(@conn, :index, @image, page: 1) data-loaded="true" = render PhilomenaWeb.Image.CommentView, "index.html", image: @image, comments: @comments, conn: @conn \ No newline at end of file