import Ecto.Changeset
import Ecto.Query
+ alias Pleroma.Chat
alias Pleroma.Repo
alias Pleroma.User
It is a helper only, to make it easy to display a list of chats with other people, ordered by last bump. The actual messages are retrieved by querying the recipients of the ChatMessages.
"""
+ @type t :: %__MODULE__{}
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
schema "chats" do
|> unique_constraint(:user_id, name: :chats_user_id_recipient_index)
end
+ @spec get_by_user_and_id(User.t(), FlakeId.Ecto.CompatType.t()) ::
+ {:ok, t()} | {:error, :not_found}
+ def get_by_user_and_id(%User{id: user_id}, id) do
+ from(c in __MODULE__,
+ where: c.id == ^id,
+ where: c.user_id == ^user_id
+ )
+ |> Repo.find_resource()
+ end
+
+ @spec get_by_id(FlakeId.Ecto.CompatType.t()) :: t() | nil
def get_by_id(id) do
- __MODULE__
- |> Repo.get(id)
+ Repo.get(__MODULE__, id)
end
+ @spec get(FlakeId.Ecto.CompatType.t(), String.t()) :: t() | nil
def get(user_id, recipient) do
- __MODULE__
- |> Repo.get_by(user_id: user_id, recipient: recipient)
+ Repo.get_by(__MODULE__, user_id: user_id, recipient: recipient)
end
+ @spec get_or_create(FlakeId.Ecto.CompatType.t(), String.t()) ::
+ {:ok, t()} | {:error, Ecto.Changeset.t()}
def get_or_create(user_id, recipient) do
%__MODULE__{}
|> changeset(%{user_id: user_id, recipient: recipient})
)
end
+ @spec bump_or_create(FlakeId.Ecto.CompatType.t(), String.t()) ::
+ {:ok, t()} | {:error, Ecto.Changeset.t()}
def bump_or_create(user_id, recipient) do
%__MODULE__{}
|> changeset(%{user_id: user_id, recipient: recipient})
conflict_target: [:user_id, :recipient]
)
end
+
+ @spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t()
+ def for_user_query(user_id) do
+ from(c in Chat,
+ where: c.user_id == ^user_id,
+ order_by: [desc: c.updated_at]
+ )
+ end
end
"The messages in the chat",
"application/json",
chat_messages_response()
- )
+ ),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
},
security: [
%{
"application/json",
ChatMessage
),
- 400 => Operation.response("Bad Request", "application/json", ApiError)
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 422 => Operation.response("MRF Rejection", "application/json", ApiError)
},
security: [
%{
defmodule Pleroma.Web.PleromaAPI.ChatController do
use Pleroma.Web, :controller
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+
alias Pleroma.Activity
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
}) do
with %MessageReference{} = cm_ref <-
MessageReference.get_by_id(message_id),
- ^chat_id <- cm_ref.chat_id |> to_string(),
+ ^chat_id <- to_string(cm_ref.chat_id),
%Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
{:ok, _} <- remove_or_delete(cm_ref, user) do
conn
end
end
- defp remove_or_delete(cm_ref, _) do
- cm_ref
- |> MessageReference.delete()
- end
+ defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref)
def post_chat_message(
- %{body_params: params, assigns: %{user: %{id: user_id} = user}} = conn,
- %{
- id: id
- }
+ %{body_params: params, assigns: %{user: user}} = conn,
+ %{id: id}
) do
- with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
+ with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
%User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
{:ok, activity} <-
CommonAPI.post_chat_message(user, recipient, params[:content],
conn
|> put_view(MessageReferenceView)
|> render("show.json", chat_message_reference: cm_ref)
+ else
+ {:reject, message} ->
+ conn
+ |> put_status(:unprocessable_entity)
+ |> json(%{error: message})
+
+ {:error, message} ->
+ conn
+ |> put_status(:bad_request)
+ |> json(%{error: message})
end
end
- def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{
- id: chat_id,
- message_id: message_id
- }) do
- with %MessageReference{} = cm_ref <-
- MessageReference.get_by_id(message_id),
- ^chat_id <- cm_ref.chat_id |> to_string(),
+ def mark_message_as_read(
+ %{assigns: %{user: %{id: user_id}}} = conn,
+ %{id: chat_id, message_id: message_id}
+ ) do
+ with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id),
+ ^chat_id <- to_string(cm_ref.chat_id),
%Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
{:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
conn
end
def mark_as_read(
- %{
- body_params: %{last_read_id: last_read_id},
- assigns: %{user: %{id: user_id}}
- } = conn,
+ %{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn,
%{id: id}
) do
- with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
- {_n, _} <-
- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
+ with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
+ {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
conn
|> put_view(ChatView)
|> render("show.json", chat: chat)
end
end
- def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do
- with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do
- cm_refs =
+ def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do
+ with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
+ chat_message_refs =
chat
|> MessageReference.for_chat_query()
|> Pagination.fetch_paginated(params)
conn
+ |> add_link_headers(chat_message_refs)
|> put_view(MessageReferenceView)
- |> render("index.json", chat_message_references: cm_refs)
- else
- _ ->
- conn
- |> put_status(:not_found)
- |> json(%{error: "not found"})
+ |> render("index.json", chat_message_references: chat_message_refs)
end
end
blocked_ap_ids = User.blocked_users_ap_ids(user)
chats =
- from(c in Chat,
- where: c.user_id == ^user_id,
- where: c.recipient not in ^blocked_ap_ids,
- order_by: [desc: c.updated_at]
- )
+ Chat.for_user_query(user_id)
+ |> where([c], c.recipient not in ^blocked_ap_ids)
|> Repo.all()
conn
|> render("index.json", chats: chats)
end
- def create(%{assigns: %{user: user}} = conn, params) do
- with %User{ap_id: recipient} <- User.get_by_id(params[:id]),
+ def create(%{assigns: %{user: user}} = conn, %{id: id}) do
+ with %User{ap_id: recipient} <- User.get_cached_by_id(id),
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
conn
|> put_view(ChatView)
end
end
- def show(%{assigns: %{user: user}} = conn, params) do
- with %Chat{} = chat <- Repo.get_by(Chat, user_id: user.id, id: params[:id]) do
+ def show(%{assigns: %{user: user}} = conn, %{id: id}) do
+ with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
conn
|> put_view(ChatView)
|> render("show.json", chat: chat)
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
- use Pleroma.Web.ConnCase, async: true
+ use Pleroma.Web.ConnCase
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
|> post("/api/v1/pleroma/chats/#{chat.id}/messages")
|> json_response_and_validate_schema(400)
- assert result
+ assert %{"error" => "no_content"} == result
end
test "it works with an attachment", %{conn: conn, user: user} do
assert result["attachment"]
end
+
+ test "gets MRF reason when rejected", %{conn: conn, user: user} do
+ clear_config([:mrf_keyword, :reject], ["GNO"])
+ clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
+
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
+
+ result =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{"content" => "GNO/Linux"})
+ |> json_response_and_validate_schema(422)
+
+ assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} == result
+ end
end
describe "DELETE /api/v1/pleroma/chats/:id/messages/:message_id" do
chat = Chat.get(user.id, recipient.ap_id)
- result =
- conn
- |> get("/api/v1/pleroma/chats/#{chat.id}/messages")
- |> json_response_and_validate_schema(200)
+ response = get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages")
+ result = json_response_and_validate_schema(response, 200)
+
+ [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ")
+ api_endpoint = "/api/v1/pleroma/chats/"
+
+ assert String.match?(
+ next,
+ ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&max_id=.*; rel=\"next\"$)
+ )
+
+ assert String.match?(
+ prev,
+ ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&min_id=.*; rel=\"prev\"$)
+ )
assert length(result) == 20
- result =
- conn
- |> get("/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}")
- |> json_response_and_validate_schema(200)
+ response =
+ get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}")
+
+ result = json_response_and_validate_schema(response, 200)
+ [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ")
+
+ assert String.match?(
+ next,
+ ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&max_id=.*; rel=\"next\"$)
+ )
+
+ assert String.match?(
+ prev,
+ ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&max_id=.*&min_id=.*; rel=\"prev\"$)
+ )
assert length(result) == 10
end
assert length(result) == 3
# Trying to get the chat of a different user
- result =
- conn
- |> assign(:user, other_user)
- |> get("/api/v1/pleroma/chats/#{chat.id}/messages")
-
- assert result |> json_response(404)
+ conn
+ |> assign(:user, other_user)
+ |> get("/api/v1/pleroma/chats/#{chat.id}/messages")
+ |> json_response_and_validate_schema(404)
end
end