mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
Move conversation controller functionality into context
This commit is contained in:
parent
17a434aa36
commit
23332bec28
6 changed files with 197 additions and 134 deletions
|
@ -9,45 +9,7 @@ defmodule Philomena.Conversations do
|
|||
alias Philomena.Conversations.Conversation
|
||||
alias Philomena.Conversations.Message
|
||||
alias Philomena.Reports
|
||||
|
||||
@doc """
|
||||
Creates a conversation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_conversation(%{field: value})
|
||||
{:ok, %Conversation{}}
|
||||
|
||||
iex> create_conversation(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_conversation(from, attrs \\ %{}) do
|
||||
%Conversation{}
|
||||
|> Conversation.creation_changeset(from, attrs)
|
||||
|> Repo.insert()
|
||||
|> case do
|
||||
{:ok, conversation} ->
|
||||
report_non_approved_message(hd(conversation.messages))
|
||||
{:ok, conversation}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking conversation changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_conversation(conversation)
|
||||
%Ecto.Changeset{source: %Conversation{}}
|
||||
|
||||
"""
|
||||
def change_conversation(%Conversation{} = conversation) do
|
||||
Conversation.changeset(conversation, %{})
|
||||
end
|
||||
alias Philomena.Users
|
||||
|
||||
@doc """
|
||||
Returns the number of unread conversations for the given user.
|
||||
|
@ -75,6 +37,96 @@ defmodule Philomena.Conversations do
|
|||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a `m:Scrivener.Page` of conversations between the partner and the user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_conversations_with("123", %User{}, page_size: 10)
|
||||
%Scrivener.Page{}
|
||||
|
||||
"""
|
||||
def list_conversations_with(partner_id, user, pagination) do
|
||||
query =
|
||||
from c in Conversation,
|
||||
where:
|
||||
(c.from_id == ^partner_id and c.to_id == ^user.id) or
|
||||
(c.to_id == ^partner_id and c.from_id == ^user.id)
|
||||
|
||||
list_conversations(query, user, pagination)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a `m:Scrivener.Page` of conversations sent by or received from the user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_conversations_with("123", %User{}, page_size: 10)
|
||||
%Scrivener.Page{}
|
||||
|
||||
"""
|
||||
def list_conversations(queryable \\ Conversation, user, pagination) do
|
||||
query =
|
||||
from c in queryable,
|
||||
as: :conversations,
|
||||
where:
|
||||
(c.from_id == ^user.id and not c.from_hidden) or
|
||||
(c.to_id == ^user.id and not c.to_hidden),
|
||||
inner_lateral_join:
|
||||
cnt in subquery(
|
||||
from m in Message,
|
||||
where: m.conversation_id == parent_as(:conversations).id,
|
||||
select: %{count: count()}
|
||||
),
|
||||
on: true,
|
||||
order_by: [desc: :last_message_at],
|
||||
preload: [:to, :from],
|
||||
select: %{c | message_count: cnt.count}
|
||||
|
||||
Repo.paginate(query, pagination)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a conversation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_conversation(from, to, %{field: value})
|
||||
{:ok, %Conversation{}}
|
||||
|
||||
iex> create_conversation(from, to, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_conversation(from, attrs \\ %{}) do
|
||||
to = Users.get_user_by_name(attrs["recipient"])
|
||||
|
||||
%Conversation{}
|
||||
|> Conversation.creation_changeset(from, to, attrs)
|
||||
|> Repo.insert()
|
||||
|> case do
|
||||
{:ok, conversation} ->
|
||||
report_non_approved_message(hd(conversation.messages))
|
||||
{:ok, conversation}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking conversation changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_conversation(conversation)
|
||||
%Ecto.Changeset{source: %Conversation{}}
|
||||
|
||||
"""
|
||||
def change_conversation(%Conversation{} = conversation) do
|
||||
Conversation.changeset(conversation, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Marks a conversation as read or unread from the perspective of the given user.
|
||||
|
||||
|
@ -138,6 +190,59 @@ defmodule Philomena.Conversations do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the number of messages in the given conversation.
|
||||
|
||||
## Example
|
||||
|
||||
iex> count_messages(%Conversation{})
|
||||
3
|
||||
|
||||
"""
|
||||
def count_messages(conversation) do
|
||||
Message
|
||||
|> where(conversation_id: ^conversation.id)
|
||||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a `m:Scrivener.Page` of 2-tuples of messages and rendered output
|
||||
within a conversation.
|
||||
|
||||
Messages are ordered by user message preference (`messages_newest_first`).
|
||||
|
||||
When coerced to a list and rendered as Markdown, the result may look like:
|
||||
|
||||
[
|
||||
{%Message{body: "hello *world*"}, "hello <strong>world</strong>"}
|
||||
]
|
||||
|
||||
## Example
|
||||
|
||||
iex> list_messages(%Conversation{}, %User{}, & &1.body, page_size: 10)
|
||||
%Scrivener.Page{}
|
||||
|
||||
"""
|
||||
def list_messages(conversation, user, collection_renderer, pagination) do
|
||||
direction =
|
||||
if user.messages_newest_first do
|
||||
:desc
|
||||
else
|
||||
:asc
|
||||
end
|
||||
|
||||
query =
|
||||
from m in Message,
|
||||
where: m.conversation_id == ^conversation.id,
|
||||
order_by: [{^direction, :created_at}],
|
||||
preload: :from
|
||||
|
||||
messages = Repo.paginate(query, pagination)
|
||||
rendered = collection_renderer.(messages)
|
||||
|
||||
put_in(messages.entries, Enum.zip(messages.entries, rendered))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a message within a conversation.
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ defmodule Philomena.Conversations.Conversation do
|
|||
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Conversations.Message
|
||||
alias Philomena.Repo
|
||||
|
||||
@derive {Phoenix.Param, key: :slug}
|
||||
|
||||
|
@ -20,6 +19,8 @@ defmodule Philomena.Conversations.Conversation do
|
|||
field :from_hidden, :boolean, default: false
|
||||
field :slug, :string
|
||||
field :last_message_at, :utc_datetime
|
||||
|
||||
field :message_count, :integer, virtual: true
|
||||
field :recipient, :string, virtual: true
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
|
@ -43,17 +44,17 @@ defmodule Philomena.Conversations.Conversation do
|
|||
end
|
||||
|
||||
@doc false
|
||||
def creation_changeset(conversation, from, attrs) do
|
||||
def creation_changeset(conversation, from, to, attrs) do
|
||||
conversation
|
||||
|> cast(attrs, [:title, :recipient])
|
||||
|> validate_required([:title, :recipient])
|
||||
|> validate_length(:title, max: 300, count: :bytes)
|
||||
|> cast(attrs, [:title])
|
||||
|> put_assoc(:from, from)
|
||||
|> put_recipient()
|
||||
|> set_slug()
|
||||
|> set_last_message()
|
||||
|> put_assoc(:to, to)
|
||||
|> put_change(:slug, Ecto.UUID.generate())
|
||||
|> cast_assoc(:messages, with: &Message.creation_changeset(&1, &2, from))
|
||||
|> set_last_message()
|
||||
|> validate_length(:messages, is: 1)
|
||||
|> validate_length(:title, max: 300, count: :bytes)
|
||||
|> validate_required([:title, :from, :to])
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
@ -64,21 +65,7 @@ defmodule Philomena.Conversations.Conversation do
|
|||
|> set_last_message()
|
||||
end
|
||||
|
||||
defp set_slug(changeset) do
|
||||
changeset
|
||||
|> change(slug: Ecto.UUID.generate())
|
||||
end
|
||||
|
||||
defp set_last_message(changeset) do
|
||||
change(changeset, last_message_at: DateTime.utc_now(:second))
|
||||
end
|
||||
|
||||
defp put_recipient(changeset) do
|
||||
recipient = changeset |> get_field(:recipient)
|
||||
user = Repo.get_by(User, name: recipient)
|
||||
|
||||
changeset
|
||||
|> put_change(:to, user)
|
||||
|> validate_required(:to)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,6 +55,22 @@ defmodule Philomena.Users do
|
|||
Repo.get_by(User, email: email)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a user by name.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_user_by_name("Administrator")
|
||||
%User{}
|
||||
|
||||
iex> get_user_by_name("nonexistent")
|
||||
nil
|
||||
|
||||
"""
|
||||
def get_user_by_name(name) when is_binary(name) do
|
||||
Repo.get_by(User, name: name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a user by email and password.
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
defmodule PhilomenaWeb.Conversation.MessageController do
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
alias Philomena.Conversations.{Conversation, Message}
|
||||
alias Philomena.Conversations.Conversation
|
||||
alias Philomena.Conversations
|
||||
alias Philomena.Repo
|
||||
import Ecto.Query
|
||||
|
||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||
plug PhilomenaWeb.CanaryMapPlug, create: :show
|
||||
|
@ -15,20 +13,16 @@ defmodule PhilomenaWeb.Conversation.MessageController do
|
|||
id_field: "slug",
|
||||
persisted: true
|
||||
|
||||
@page_size 25
|
||||
|
||||
def create(conn, %{"message" => message_params}) do
|
||||
conversation = conn.assigns.conversation
|
||||
user = conn.assigns.current_user
|
||||
|
||||
case Conversations.create_message(conversation, user, message_params) do
|
||||
{:ok, _message} ->
|
||||
count =
|
||||
Message
|
||||
|> where(conversation_id: ^conversation.id)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
|
||||
page =
|
||||
Float.ceil(count / 25)
|
||||
|> round()
|
||||
count = Conversations.count_messages(conversation)
|
||||
page = div(count + @page_size - 1, @page_size)
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Message successfully sent.")
|
||||
|
|
|
@ -4,8 +4,6 @@ defmodule PhilomenaWeb.ConversationController do
|
|||
alias PhilomenaWeb.NotificationCountPlug
|
||||
alias Philomena.{Conversations, Conversations.Conversation, Conversations.Message}
|
||||
alias PhilomenaWeb.MarkdownRenderer
|
||||
alias Philomena.Repo
|
||||
import Ecto.Query
|
||||
|
||||
plug PhilomenaWeb.FilterBannedUsersPlug when action in [:new, :create]
|
||||
|
||||
|
@ -19,42 +17,17 @@ defmodule PhilomenaWeb.ConversationController do
|
|||
only: :show,
|
||||
preload: [:to, :from]
|
||||
|
||||
def index(conn, %{"with" => partner}) do
|
||||
def index(conn, params) do
|
||||
user = conn.assigns.current_user
|
||||
|
||||
Conversation
|
||||
|> where(
|
||||
[c],
|
||||
(c.from_id == ^user.id and c.to_id == ^partner and not c.from_hidden) or
|
||||
(c.to_id == ^user.id and c.from_id == ^partner and not c.to_hidden)
|
||||
)
|
||||
|> load_conversations(conn)
|
||||
end
|
||||
|
||||
def index(conn, _params) do
|
||||
user = conn.assigns.current_user
|
||||
|
||||
Conversation
|
||||
|> where(
|
||||
[c],
|
||||
(c.from_id == ^user.id and not c.from_hidden) or (c.to_id == ^user.id and not c.to_hidden)
|
||||
)
|
||||
|> load_conversations(conn)
|
||||
end
|
||||
|
||||
defp load_conversations(queryable, conn) do
|
||||
conversations =
|
||||
queryable
|
||||
|> join(
|
||||
:inner_lateral,
|
||||
[c],
|
||||
_ in fragment("SELECT COUNT(*) FROM messages m WHERE m.conversation_id = ?", c.id),
|
||||
on: true
|
||||
)
|
||||
|> order_by(desc: :last_message_at)
|
||||
|> preload([:to, :from])
|
||||
|> select([c, cnt], {c, cnt.count})
|
||||
|> Repo.paginate(conn.assigns.scrivener)
|
||||
case params do
|
||||
%{"with" => partner_id} ->
|
||||
Conversations.list_conversations_with(partner_id, user, conn.assigns.scrivener)
|
||||
|
||||
_ ->
|
||||
Conversations.list_conversations(user, conn.assigns.scrivener)
|
||||
end
|
||||
|
||||
render(conn, "index.html", title: "Conversations", conversations: conversations)
|
||||
end
|
||||
|
@ -62,27 +35,17 @@ defmodule PhilomenaWeb.ConversationController do
|
|||
def show(conn, _params) do
|
||||
conversation = conn.assigns.conversation
|
||||
user = conn.assigns.current_user
|
||||
pref = load_direction(user)
|
||||
|
||||
messages =
|
||||
Message
|
||||
|> where(conversation_id: ^conversation.id)
|
||||
|> order_by([{^pref, :created_at}])
|
||||
|> preload([:from])
|
||||
|> Repo.paginate(conn.assigns.scrivener)
|
||||
Conversations.list_messages(
|
||||
conversation,
|
||||
user,
|
||||
&MarkdownRenderer.render_collection(&1, conn),
|
||||
conn.assigns.scrivener
|
||||
)
|
||||
|
||||
rendered =
|
||||
messages.entries
|
||||
|> MarkdownRenderer.render_collection(conn)
|
||||
|
||||
messages = %{messages | entries: Enum.zip(messages.entries, rendered)}
|
||||
|
||||
changeset =
|
||||
%Message{}
|
||||
|> Conversations.change_message()
|
||||
|
||||
conversation
|
||||
|> Conversations.mark_conversation_read(user)
|
||||
changeset = Conversations.change_message(%Message{})
|
||||
Conversations.mark_conversation_read(conversation, user)
|
||||
|
||||
# Update the conversation ticker in the header
|
||||
conn = NotificationCountPlug.call(conn)
|
||||
|
@ -96,9 +59,10 @@ defmodule PhilomenaWeb.ConversationController do
|
|||
end
|
||||
|
||||
def new(conn, params) do
|
||||
changeset =
|
||||
conversation =
|
||||
%Conversation{recipient: params["recipient"], messages: [%Message{}]}
|
||||
|> Conversations.change_conversation()
|
||||
|
||||
changeset = Conversations.change_conversation(conversation)
|
||||
|
||||
render(conn, "new.html", title: "New Conversation", changeset: changeset)
|
||||
end
|
||||
|
@ -116,7 +80,4 @@ defmodule PhilomenaWeb.ConversationController do
|
|||
render(conn, "new.html", changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
defp load_direction(%{messages_newest_first: false}), do: :asc
|
||||
defp load_direction(_user), do: :desc
|
||||
end
|
||||
|
|
|
@ -20,14 +20,14 @@ h1 My Conversations
|
|||
th.table--communication-list__stats With
|
||||
th.table--communication-list__options Options
|
||||
tbody
|
||||
= for {c, count} <- @conversations do
|
||||
= for c <- @conversations do
|
||||
tr class=conversation_class(@conn.assigns.current_user, c)
|
||||
td.table--communication-list__name
|
||||
=> link c.title, to: ~p"/conversations/#{c}"
|
||||
|
||||
.small-text.hide-mobile
|
||||
=> count
|
||||
= pluralize("message", "messages", count)
|
||||
=> c.message_count
|
||||
= pluralize("message", "messages", c.message_count)
|
||||
' ; started
|
||||
= pretty_time(c.created_at)
|
||||
' , last message
|
||||
|
@ -36,7 +36,7 @@ h1 My Conversations
|
|||
td.table--communication-list__stats
|
||||
= render PhilomenaWeb.UserAttributionView, "_user.html", object: %{user: other_party(@current_user, c)}, conn: @conn
|
||||
td.table--communication-list__options
|
||||
=> link "Last message", to: last_message_path(c, count)
|
||||
=> link "Last message", to: last_message_path(c, c.message_count)
|
||||
' •
|
||||
=> link "Hide", to: ~p"/conversations/#{c}/hide", data: [method: "post"], data: [confirm: "Are you really, really sure?"]
|
||||
|
||||
|
|
Loading…
Reference in a new issue