diff --git a/lib/philomena/conversations.ex b/lib/philomena/conversations.ex index 82a2e495..1de8fe1c 100644 --- a/lib/philomena/conversations.ex +++ b/lib/philomena/conversations.ex @@ -6,24 +6,9 @@ defmodule Philomena.Conversations do import Ecto.Query, warn: false alias Ecto.Multi alias Philomena.Repo - alias Philomena.Reports alias Philomena.Conversations.Conversation - - @doc """ - Gets a single conversation. - - Raises `Ecto.NoResultsError` if the Conversation does not exist. - - ## Examples - - iex> get_conversation!(123) - %Conversation{} - - iex> get_conversation!(456) - ** (Ecto.NoResultsError) - - """ - def get_conversation!(id), do: Repo.get!(Conversation, id) + alias Philomena.Conversations.Message + alias Philomena.Reports @doc """ Creates a conversation. @@ -41,40 +26,14 @@ defmodule Philomena.Conversations do %Conversation{} |> Conversation.creation_changeset(from, attrs) |> Repo.insert() - end + |> case do + {:ok, conversation} -> + report_non_approved_message(hd(conversation.messages)) + {:ok, conversation} - @doc """ - Updates a conversation. - - ## Examples - - iex> update_conversation(conversation, %{field: new_value}) - {:ok, %Conversation{}} - - iex> update_conversation(conversation, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_conversation(%Conversation{} = conversation, attrs) do - conversation - |> Conversation.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a Conversation. - - ## Examples - - iex> delete_conversation(conversation) - {:ok, %Conversation{}} - - iex> delete_conversation(conversation) - {:error, %Ecto.Changeset{}} - - """ - def delete_conversation(%Conversation{} = conversation) do - Repo.delete(conversation) + error -> + error + end end @doc """ @@ -90,6 +49,20 @@ defmodule Philomena.Conversations do Conversation.changeset(conversation, %{}) end + @doc """ + Returns the number of unread conversations for the given user. + + Conversations hidden by the given user are not counted. + + ## Examples + + iex> count_unread_conversations(user1) + 0 + + iex> count_unread_conversations(user2) + 7 + + """ def count_unread_conversations(user) do Conversation |> where( @@ -99,187 +72,171 @@ defmodule Philomena.Conversations do not ((c.to_id == ^user.id and c.to_hidden == true) or (c.from_id == ^user.id and c.from_hidden == true)) ) - |> Repo.aggregate(:count, :id) + |> Repo.aggregate(:count) end - def mark_conversation_read(conversation, user, read \\ true) - - def mark_conversation_read( - %Conversation{to_id: user_id, from_id: user_id} = conversation, - %{id: user_id}, - read - ) do - conversation - |> Conversation.read_changeset(%{to_read: read, from_read: read}) - |> Repo.update() - end - - def mark_conversation_read(%Conversation{to_id: user_id} = conversation, %{id: user_id}, read) do - conversation - |> Conversation.read_changeset(%{to_read: read}) - |> Repo.update() - end - - def mark_conversation_read(%Conversation{from_id: user_id} = conversation, %{id: user_id}, read) do - conversation - |> Conversation.read_changeset(%{from_read: read}) - |> Repo.update() - end - - def mark_conversation_read(_conversation, _user, _read), do: {:ok, nil} - - def mark_conversation_hidden(conversation, user, hidden \\ true) - - def mark_conversation_hidden( - %Conversation{to_id: user_id} = conversation, - %{id: user_id}, - hidden - ) do - conversation - |> Conversation.hidden_changeset(%{to_hidden: hidden}) - |> Repo.update() - end - - def mark_conversation_hidden( - %Conversation{from_id: user_id} = conversation, - %{id: user_id}, - hidden - ) do - conversation - |> Conversation.hidden_changeset(%{from_hidden: hidden}) - |> Repo.update() - end - - def mark_conversation_hidden(_conversation, _user, _read), do: {:ok, nil} - - alias Philomena.Conversations.Message - @doc """ - Gets a single message. - - Raises `Ecto.NoResultsError` if the Message does not exist. + Marks a conversation as read or unread from the perspective of the given user. ## Examples - iex> get_message!(123) - %Message{} + iex> mark_conversation_read(conversation, user, true) + {:ok, %Conversation{}} - iex> get_message!(456) - ** (Ecto.NoResultsError) + iex> mark_conversation_read(conversation, user, false) + {:ok, %Conversation{}} + + iex> mark_conversation_read(conversation, %User{}, true) + {:error, %Ecto.Changeset{}} """ - def get_message!(id), do: Repo.get!(Message, id) + def mark_conversation_read(%Conversation{} = conversation, user, read \\ true) do + changes = + %{} + |> put_conditional(:to_read, read, conversation.to_id == user.id) + |> put_conditional(:from_read, read, conversation.from_id == user.id) + + conversation + |> Conversation.read_changeset(changes) + |> Repo.update() + end @doc """ - Creates a message. + Marks a conversation as hidden or visible from the perspective of the given user. + + Hidden conversations are not shown in the list of conversations for the user, and + are not counted when retrieving the number of unread conversations. ## Examples - iex> create_message(%{field: value}) + iex> mark_conversation_hidden(conversation, user, true) + {:ok, %Conversation{}} + + iex> mark_conversation_hidden(conversation, user, false) + {:ok, %Conversation{}} + + iex> mark_conversation_hidden(conversation, %User{}, true) + {:error, %Ecto.Changeset{}} + + """ + def mark_conversation_hidden(%Conversation{} = conversation, user, hidden \\ true) do + changes = + %{} + |> put_conditional(:to_hidden, hidden, conversation.to_id == user.id) + |> put_conditional(:from_hidden, hidden, conversation.from_id == user.id) + + conversation + |> Conversation.hidden_changeset(changes) + |> Repo.update() + end + + defp put_conditional(map, key, value, condition) do + if condition do + Map.put(map, key, value) + else + map + end + end + + @doc """ + Creates a message within a conversation. + + ## Examples + + iex> create_message(%Conversation{}, %User{}, %{field: value}) {:ok, %Message{}} - iex> create_message(%{field: bad_value}) + iex> create_message(%Conversation{}, %User{}, %{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_message(conversation, user, attrs \\ %{}) do - message = - Ecto.build_assoc(conversation, :messages) + message_changeset = + conversation + |> Ecto.build_assoc(:messages) |> Message.creation_changeset(attrs, user) - show_as_read = - case message do - %{changes: %{approved: true}} -> false - _ -> true - end - - conversation_query = - Conversation - |> where(id: ^conversation.id) - - now = DateTime.utc_now() + conversation_changeset = + Conversation.new_message_changeset(conversation) Multi.new() - |> Multi.insert(:message, message) - |> Multi.update_all(:conversation, conversation_query, - set: [from_read: show_as_read, to_read: show_as_read, last_message_at: now] - ) - |> Repo.transaction() - end - - def approve_conversation_message(message, user) do - reports_query = Reports.close_report_query({"Conversation", message.conversation_id}, user) - - message_query = - message - |> Message.approve_changeset() - - conversation_query = - Conversation - |> where(id: ^message.conversation_id) - - Multi.new() - |> Multi.update(:message, message_query) - |> Multi.update_all(:conversation, conversation_query, set: [to_read: false]) - |> Multi.update_all(:reports, reports_query, []) + |> Multi.insert(:message, message_changeset) + |> Multi.update(:conversation, conversation_changeset) |> Repo.transaction() |> case do - {:ok, %{reports: {_count, reports}} = result} -> - Reports.reindex_reports(reports) + {:ok, %{message: message}} -> + report_non_approved_message(message) + {:ok, message} - {:ok, result} - - error -> - error + _error -> + {:error, message_changeset} end end - def report_non_approved(id) do - Reports.create_system_report( - {"Conversation", id}, - "Approval", - "PM contains externally-embedded images and has been flagged for review." - ) - end - - def set_as_read(conversation) do - conversation - |> Conversation.to_read_changeset() - |> Repo.update() - end - @doc """ - Updates a message. + Approves a previously-posted message which was not approved at post time. ## Examples - iex> update_message(message, %{field: new_value}) + iex> approve_message(%Message{}, %User{}) {:ok, %Message{}} - iex> update_message(message, %{field: bad_value}) + iex> approve_message(%Message{}, %User{}) {:error, %Ecto.Changeset{}} """ - def update_message(%Message{} = message, attrs) do - message - |> Message.changeset(attrs) - |> Repo.update() + def approve_message(message, approving_user) do + message_changeset = Message.approve_changeset(message) + + conversation_update_query = + from c in Conversation, + where: c.id == ^message.conversation_id, + update: [set: [from_read: false, to_read: false]] + + reports_query = + Reports.close_report_query({"Conversation", message.conversation_id}, approving_user) + + Multi.new() + |> Multi.update(:message, message_changeset) + |> Multi.update_all(:conversation, conversation_update_query, []) + |> Multi.update_all(:reports, reports_query, []) + |> Repo.transaction() + |> case do + {:ok, %{reports: {_count, reports}, message: message}} -> + Reports.reindex_reports(reports) + + message + + _error -> + {:error, message_changeset} + end end @doc """ - Deletes a Message. + Generates a system report for an unapproved message. + + This is called by `create_conversation/2` and `create_message/3`, so it normally does not + need to be called explicitly. ## Examples - iex> delete_message(message) - {:ok, %Message{}} + iex> report_non_approved_message(%Message{approved: false}) + {:ok, %Report{}} - iex> delete_message(message) - {:error, %Ecto.Changeset{}} + iex> report_non_approved_message(%Message{approved: true}) + {:ok, nil} """ - def delete_message(%Message{} = message) do - Repo.delete(message) + def report_non_approved_message(message) do + if message.approved do + {:ok, nil} + else + Reports.create_system_report( + {"Conversation", message.conversation_id}, + "Approval", + "PM contains externally-embedded images and has been flagged for review." + ) + end end @doc """ diff --git a/lib/philomena/conversations/conversation.ex b/lib/philomena/conversations/conversation.ex index 77c5981d..d48aa0b9 100644 --- a/lib/philomena/conversations/conversation.ex +++ b/lib/philomena/conversations/conversation.ex @@ -32,19 +32,14 @@ defmodule Philomena.Conversations.Conversation do |> validate_required([]) end + @doc false def read_changeset(conversation, attrs) do - conversation - |> cast(attrs, [:from_read, :to_read]) - end - - def to_read_changeset(conversation) do - change(conversation) - |> put_change(:to_read, true) + cast(conversation, attrs, [:from_read, :to_read]) end + @doc false def hidden_changeset(conversation, attrs) do - conversation - |> cast(attrs, [:from_hidden, :to_hidden]) + cast(conversation, attrs, [:from_hidden, :to_hidden]) end @doc false @@ -61,6 +56,14 @@ defmodule Philomena.Conversations.Conversation do |> validate_length(:messages, is: 1) end + @doc false + def new_message_changeset(conversation) do + conversation + |> change(from_read: false) + |> change(to_read: false) + |> set_last_message() + end + defp set_slug(changeset) do changeset |> change(slug: Ecto.UUID.generate()) diff --git a/lib/philomena/conversations/message.ex b/lib/philomena/conversations/message.ex index a9e6fefd..4dced3af 100644 --- a/lib/philomena/conversations/message.ex +++ b/lib/philomena/conversations/message.ex @@ -33,6 +33,7 @@ defmodule Philomena.Conversations.Message do |> Approval.maybe_put_approval(user) end + @doc false def approve_changeset(message) do change(message, approved: true) end diff --git a/lib/philomena_web/controllers/conversation/message/approve_controller.ex b/lib/philomena_web/controllers/conversation/message/approve_controller.ex index 1693f432..fde13b1a 100644 --- a/lib/philomena_web/controllers/conversation/message/approve_controller.ex +++ b/lib/philomena_web/controllers/conversation/message/approve_controller.ex @@ -16,7 +16,7 @@ defmodule PhilomenaWeb.Conversation.Message.ApproveController do message = conn.assigns.message {:ok, _message} = - Conversations.approve_conversation_message(message, conn.assigns.current_user) + Conversations.approve_message(message, conn.assigns.current_user) conn |> put_flash(:info, "Conversation message approved.") diff --git a/lib/philomena_web/controllers/conversation/message_controller.ex b/lib/philomena_web/controllers/conversation/message_controller.ex index 8bd44940..2d6d2ba2 100644 --- a/lib/philomena_web/controllers/conversation/message_controller.ex +++ b/lib/philomena_web/controllers/conversation/message_controller.ex @@ -20,11 +20,7 @@ defmodule PhilomenaWeb.Conversation.MessageController do user = conn.assigns.current_user case Conversations.create_message(conversation, user, message_params) do - {:ok, %{message: message}} -> - if not message.approved do - Conversations.report_non_approved(message.conversation_id) - end - + {:ok, _message} -> count = Message |> where(conversation_id: ^conversation.id) diff --git a/lib/philomena_web/controllers/conversation_controller.ex b/lib/philomena_web/controllers/conversation_controller.ex index 12784b42..f893fa1f 100644 --- a/lib/philomena_web/controllers/conversation_controller.ex +++ b/lib/philomena_web/controllers/conversation_controller.ex @@ -108,18 +108,12 @@ defmodule PhilomenaWeb.ConversationController do case Conversations.create_conversation(user, conversation_params) do {:ok, conversation} -> - if not hd(conversation.messages).approved do - Conversations.report_non_approved(conversation.id) - Conversations.set_as_read(conversation) - end - conn |> put_flash(:info, "Conversation successfully created.") |> redirect(to: ~p"/conversations/#{conversation}") {:error, changeset} -> - conn - |> render("new.html", changeset: changeset) + render(conn, "new.html", changeset: changeset) end end