philomena/lib/philomena/posts.ex

342 lines
7.5 KiB
Elixir
Raw Normal View History

2019-10-01 02:50:34 +02:00
defmodule Philomena.Posts do
@moduledoc """
The Posts context.
"""
import Ecto.Query, warn: false
2019-11-18 04:31:28 +01:00
alias Ecto.Multi
2019-10-01 02:50:34 +02:00
alias Philomena.Repo
alias Philomena.Elasticsearch
2019-11-18 04:31:28 +01:00
alias Philomena.Topics.Topic
2019-11-18 05:02:08 +01:00
alias Philomena.Topics
2022-03-24 17:31:57 +01:00
alias Philomena.UserStatistics
2019-10-01 02:50:34 +02:00
alias Philomena.Posts.Post
alias Philomena.Posts.ElasticsearchIndex, as: PostIndex
2020-12-06 17:42:14 +01:00
alias Philomena.IndexWorker
2019-11-18 04:39:47 +01:00
alias Philomena.Forums.Forum
2019-11-18 04:31:28 +01:00
alias Philomena.Notifications
2020-12-06 17:42:14 +01:00
alias Philomena.NotificationWorker
2019-12-06 16:14:25 +01:00
alias Philomena.Versions
2020-09-07 20:50:34 +02:00
alias Philomena.Reports
alias Philomena.Reports.Report
alias Philomena.Users.User
2019-10-01 02:50:34 +02:00
@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(topic, attributes, params \\ %{}) do
2019-12-06 23:36:08 +01:00
now = DateTime.utc_now()
2019-11-18 04:31:28 +01:00
topic_query =
Topic
|> where(id: ^topic.id)
topic_lock_query =
topic_query
|> lock("FOR UPDATE")
2019-11-18 04:39:47 +01:00
forum_query =
Forum
|> where(id: ^topic.forum_id)
2020-01-11 05:20:19 +01:00
Multi.new()
|> Multi.all(:topic_lock, topic_lock_query)
2019-11-18 04:31:28 +01:00
|> Multi.run(:post, fn repo, _ ->
last_position =
Post
|> where(topic_id: ^topic.id)
|> order_by(desc: :topic_position)
|> select([p], p.topic_position)
|> limit(1)
|> repo.one()
Ecto.build_assoc(topic, :posts, [topic_position: (last_position || -1) + 1] ++ attributes)
|> Post.creation_changeset(params, attributes)
2019-11-18 04:31:28 +01:00
|> repo.insert()
end)
|> Multi.run(:update_topic, fn repo, %{post: %{id: post_id}} ->
{count, nil} =
2020-01-11 05:20:19 +01:00
repo.update_all(topic_query,
inc: [post_count: 1],
set: [last_post_id: post_id, last_replied_to_at: now]
)
2019-11-18 04:31:28 +01:00
{:ok, count}
end)
2019-11-18 04:39:47 +01:00
|> Multi.run(:update_forum, fn repo, %{post: %{id: post_id}} ->
{count, nil} =
repo.update_all(forum_query, inc: [post_count: 1], set: [last_post_id: post_id])
{:ok, count}
end)
|> maybe_create_subscription_on_reply(topic, attributes[:user])
Squashed commit of the following: commit 8ea9cff4af46e24c38020652cedeff72957354fb Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:29:24 2020 -0400 remove remaining serializable aside hiding related commit 99ccf06264db6319ece2a896a104031447447a5f Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:20:40 2020 -0400 interactions: remove serializable commit a63bef06a6962368f69cf83afbc3c44f2467618c Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:16:27 2020 -0400 users: remove serializable commit 8053229f6fab507c29a40f0e22dd9cab7971e34f Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:11:14 2020 -0400 user_links: remove serializable commit 9b058add825b0a876a91a1cf2b1b22dc34066e42 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:09:33 2020 -0400 topics: remove serializable commit cd9ea908c34f72c0120fca1c4d581540db60db98 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:05:23 2020 -0400 tags: remove serializable commit c7563fef8fc905c32a0727a4b104222227a6bafa Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:02:22 2020 -0400 static_pages: remove serializable commit 3da661bdd1aec74e4ac5b69ec21124bc1ebc6fb4 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:00:15 2020 -0400 posts: remove serializable commit 18a50a4e5bed1ab6e4e6c13c3051a21ae7e8fbb0 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:55:55 2020 -0400 poll_votes: remove serializable commit 7d946ef23d7b27877d4bf0fb6a4db4ae64a9ffab Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:51:49 2020 -0400 galleries: remove serializable commit d8c35a0934e5394b092b050e071abdada4bdb640 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:42:43 2020 -0400 conversations: remove serializable commit 079e6dca6c8064867f2c0f90f351ea83c0f12b75 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:38:28 2020 -0400 comments: remove serializable commit 00ae38bad566fb6badeccceac2e394e65ec9428e Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:37:15 2020 -0400 commissions: remove serializable commit b3c4a4b13671ca73c58080b090dd6165552c87d6 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:17:12 2020 -0400 bans: remove serializable commit 8be9fe913ff1f6264b899e96ee38fa52032b8bda Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:02:44 2020 -0400 badges: remove serializable commit 162adda185f705b9749774c4af8c7d8db0d89790 Author: byte[] <byteslice@airmail.cc> Date: Sat Sep 5 23:56:51 2020 -0400 adverts: remove serializable
2020-09-06 07:30:53 +02:00
|> Repo.transaction()
|> case do
{:ok, %{post: post}} = result ->
reindex_post(post)
result
error ->
error
end
2019-11-18 04:31:28 +01:00
end
defp maybe_create_subscription_on_reply(multi, topic, %User{watch_on_reply: true} = user) do
multi
|> Multi.run(:subscribe, fn _repo, _changes ->
Topics.create_subscription(topic, user)
end)
end
defp maybe_create_subscription_on_reply(multi, _topic, _user) do
multi
end
2019-11-18 04:31:28 +01:00
def notify_post(post) do
2020-12-06 17:42:14 +01:00
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Posts", post.id])
end
2019-11-18 04:31:28 +01:00
2022-03-24 17:31:57 +01:00
def report_non_approved(%Post{approved: true}), do: false
2022-03-22 22:23:30 +01:00
def report_non_approved(post) do
Reports.create_system_report(
post.id,
"Post",
"Approval",
"Post contains externally-embedded images and has been flagged for review."
)
end
2020-12-06 17:42:14 +01:00
def perform_notify(post_id) do
post = get_post!(post_id)
topic =
post
|> Repo.preload(:topic)
|> Map.fetch!(:topic)
subscriptions =
topic
|> Repo.preload(:subscriptions)
|> Map.fetch!(:subscriptions)
Notifications.notify(
post,
subscriptions,
%{
actor_id: topic.id,
actor_type: "Topic",
actor_child_id: post.id,
actor_child_type: "Post",
action: "posted a new reply in"
}
)
2019-10-01 02:50:34 +02:00
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{}}
"""
2019-12-06 16:14:25 +01:00
def update_post(%Post{} = post, editor, attrs) do
now = DateTime.utc_now() |> DateTime.truncate(:second)
current_body = post.body
current_reason = post.edit_reason
2020-01-11 05:20:19 +01:00
post_changes = Post.changeset(post, attrs, now)
2019-12-06 16:14:25 +01:00
2020-01-11 05:20:19 +01:00
Multi.new()
2019-12-06 16:14:25 +01:00
|> Multi.update(:post, post_changes)
|> Multi.run(:version, fn _repo, _changes ->
Versions.create_version("Post", post.id, editor.id, %{
"body" => current_body,
"edit_reason" => current_reason
})
end)
Squashed commit of the following: commit 8ea9cff4af46e24c38020652cedeff72957354fb Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:29:24 2020 -0400 remove remaining serializable aside hiding related commit 99ccf06264db6319ece2a896a104031447447a5f Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:20:40 2020 -0400 interactions: remove serializable commit a63bef06a6962368f69cf83afbc3c44f2467618c Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:16:27 2020 -0400 users: remove serializable commit 8053229f6fab507c29a40f0e22dd9cab7971e34f Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:11:14 2020 -0400 user_links: remove serializable commit 9b058add825b0a876a91a1cf2b1b22dc34066e42 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:09:33 2020 -0400 topics: remove serializable commit cd9ea908c34f72c0120fca1c4d581540db60db98 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:05:23 2020 -0400 tags: remove serializable commit c7563fef8fc905c32a0727a4b104222227a6bafa Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:02:22 2020 -0400 static_pages: remove serializable commit 3da661bdd1aec74e4ac5b69ec21124bc1ebc6fb4 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 01:00:15 2020 -0400 posts: remove serializable commit 18a50a4e5bed1ab6e4e6c13c3051a21ae7e8fbb0 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:55:55 2020 -0400 poll_votes: remove serializable commit 7d946ef23d7b27877d4bf0fb6a4db4ae64a9ffab Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:51:49 2020 -0400 galleries: remove serializable commit d8c35a0934e5394b092b050e071abdada4bdb640 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:42:43 2020 -0400 conversations: remove serializable commit 079e6dca6c8064867f2c0f90f351ea83c0f12b75 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:38:28 2020 -0400 comments: remove serializable commit 00ae38bad566fb6badeccceac2e394e65ec9428e Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:37:15 2020 -0400 commissions: remove serializable commit b3c4a4b13671ca73c58080b090dd6165552c87d6 Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:17:12 2020 -0400 bans: remove serializable commit 8be9fe913ff1f6264b899e96ee38fa52032b8bda Author: byte[] <byteslice@airmail.cc> Date: Sun Sep 6 00:02:44 2020 -0400 badges: remove serializable commit 162adda185f705b9749774c4af8c7d8db0d89790 Author: byte[] <byteslice@airmail.cc> Date: Sat Sep 5 23:56:51 2020 -0400 adverts: remove serializable
2020-09-06 07:30:53 +02:00
|> Repo.transaction()
|> case do
{:ok, %{post: post}} = result ->
reindex_post(post)
result
error ->
error
end
2019-10-01 02:50:34 +02:00
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
def hide_post(%Post{} = post, attrs, user) do
reports =
Report
|> where(reportable_type: "Post", reportable_id: ^post.id)
|> select([r], r.id)
2020-09-07 20:50:34 +02:00
|> update(set: [open: false, state: "closed", admin_id: ^user.id])
topics =
Topic
|> where(last_post_id: ^post.id)
|> update(set: [last_post_id: nil])
forums =
Forum
|> where(last_post_id: ^post.id)
|> update(set: [last_post_id: nil])
post = Post.hide_changeset(post, attrs, user)
Multi.new()
|> Multi.update(:post, post)
|> Multi.update_all(:reports, reports, [])
|> Multi.update_all(:topics, topics, [])
|> Multi.update_all(:forums, forums, [])
|> Repo.transaction()
2020-09-07 20:50:34 +02:00
|> case do
{:ok, %{post: post, reports: {_count, reports}}} ->
Reports.reindex_reports(reports)
reindex_post(post)
{:ok, post}
error ->
error
end
end
def unhide_post(%Post{} = post) do
post
|> Post.unhide_changeset()
|> Repo.update()
|> reindex_after_update()
end
def destroy_post(%Post{} = post) do
post
|> Post.destroy_changeset()
|> Repo.update()
|> reindex_after_update()
end
2022-03-22 22:23:30 +01:00
def approve_post(%Post{} = post, user) do
reports =
Report
|> where(reportable_type: "Post", reportable_id: ^post.id)
|> select([r], r.id)
|> update(set: [open: false, state: "closed", admin_id: ^user.id])
post = Post.approve_changeset(post)
Multi.new()
|> Multi.update(:post, post)
|> Multi.update_all(:reports, reports, [])
|> Repo.transaction()
|> case do
{:ok, %{post: post, reports: {_count, reports}}} ->
2022-03-24 17:31:57 +01:00
notify_post(post)
UserStatistics.inc_stat(post.user, :forum_posts)
2022-03-22 22:23:30 +01:00
Reports.reindex_reports(reports)
reindex_post(post)
{:ok, post}
error ->
error
end
end
2019-10-01 02:50:34 +02:00
@doc """
Returns an `%Ecto.Changeset{}` for tracking post changes.
## Examples
iex> change_post(post)
%Ecto.Changeset{source: %Post{}}
"""
def change_post(%Post{} = post) do
Post.changeset(post, %{})
end
2019-11-18 04:31:28 +01:00
def user_name_reindex(old_name, new_name) do
data = PostIndex.user_name_update_by_query(old_name, new_name)
Elasticsearch.update_by_query(Post, data.query, data.set_replacements, data.replacements)
end
defp reindex_after_update({:ok, post}) do
reindex_post(post)
{:ok, post}
end
defp reindex_after_update(result) do
result
end
2019-11-18 04:31:28 +01:00
def reindex_post(%Post{} = post) do
2020-12-06 17:42:14 +01:00
Exq.enqueue(Exq, "indexing", IndexWorker, ["Posts", "id", [post.id]])
2019-11-18 04:31:28 +01:00
post
end
def indexing_preloads do
[:user, topic: :forum]
end
2020-12-06 17:42:14 +01:00
def perform_reindex(column, condition) do
Post
|> preload(^indexing_preloads())
|> where([p], field(p, ^column) in ^condition)
|> Elasticsearch.reindex(Post)
end
2019-10-01 02:50:34 +02:00
end