add commenting and notifications generation

This commit is contained in:
byte[] 2019-11-16 23:59:24 -05:00
parent c9d14359e7
commit 551c7515ae
10 changed files with 234 additions and 24 deletions

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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"

View file

@ -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