forked from AkkomaGang/akkoma
added paginate links to headers for /chats/:id/messages
This commit is contained in:
parent
9d20d29a79
commit
9853c90abb
5 changed files with 90 additions and 69 deletions
|
@ -6,6 +6,7 @@ defmodule Pleroma.Chat do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -16,6 +17,7 @@ defmodule Pleroma.Chat do
|
||||||
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.
|
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}
|
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||||
|
|
||||||
schema "chats" do
|
schema "chats" do
|
||||||
|
@ -39,16 +41,28 @@ def changeset(struct, params) do
|
||||||
|> unique_constraint(:user_id, name: :chats_user_id_recipient_index)
|
|> unique_constraint(:user_id, name: :chats_user_id_recipient_index)
|
||||||
end
|
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
|
def get_by_id(id) do
|
||||||
__MODULE__
|
Repo.get(__MODULE__, id)
|
||||||
|> Repo.get(id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get(FlakeId.Ecto.CompatType.t(), String.t()) :: t() | nil
|
||||||
def get(user_id, recipient) do
|
def get(user_id, recipient) do
|
||||||
__MODULE__
|
Repo.get_by(__MODULE__, user_id: user_id, recipient: recipient)
|
||||||
|> Repo.get_by(user_id: user_id, recipient: recipient)
|
|
||||||
end
|
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
|
def get_or_create(user_id, recipient) do
|
||||||
%__MODULE__{}
|
%__MODULE__{}
|
||||||
|> changeset(%{user_id: user_id, recipient: recipient})
|
|> changeset(%{user_id: user_id, recipient: recipient})
|
||||||
|
@ -60,6 +74,8 @@ def get_or_create(user_id, recipient) do
|
||||||
)
|
)
|
||||||
end
|
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
|
def bump_or_create(user_id, recipient) do
|
||||||
%__MODULE__{}
|
%__MODULE__{}
|
||||||
|> changeset(%{user_id: user_id, recipient: recipient})
|
|> changeset(%{user_id: user_id, recipient: recipient})
|
||||||
|
|
|
@ -158,7 +158,8 @@ def messages_operation do
|
||||||
"The messages in the chat",
|
"The messages in the chat",
|
||||||
"application/json",
|
"application/json",
|
||||||
chat_messages_response()
|
chat_messages_response()
|
||||||
)
|
),
|
||||||
|
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||||
},
|
},
|
||||||
security: [
|
security: [
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -48,13 +48,13 @@ defp param_to_integer(val, default) when is_binary(val) do
|
||||||
|
|
||||||
defp param_to_integer(_, default), do: default
|
defp param_to_integer(_, default), do: default
|
||||||
|
|
||||||
def add_link_headers(conn, activities, extra_params \\ %{})
|
def add_link_headers(conn, entries, extra_params \\ %{})
|
||||||
|
|
||||||
def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _activities, _extra_params),
|
def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _entries, _extra_params),
|
||||||
do: conn
|
do: conn
|
||||||
|
|
||||||
def add_link_headers(conn, activities, extra_params) do
|
def add_link_headers(conn, entries, extra_params) do
|
||||||
case get_pagination_fields(conn, activities, extra_params) do
|
case get_pagination_fields(conn, entries, extra_params) do
|
||||||
%{"next" => next_url, "prev" => prev_url} ->
|
%{"next" => next_url, "prev" => prev_url} ->
|
||||||
put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
|
put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
|
||||||
|
|
||||||
|
@ -78,19 +78,15 @@ defp build_pagination_fields(conn, min_id, max_id, extra_params) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_pagination_fields(conn, activities, extra_params \\ %{}) do
|
def get_pagination_fields(conn, entries, extra_params \\ %{}) do
|
||||||
case List.last(activities) do
|
case List.last(entries) do
|
||||||
%{pagination_id: max_id} when not is_nil(max_id) ->
|
%{pagination_id: max_id} when not is_nil(max_id) ->
|
||||||
%{pagination_id: min_id} =
|
%{pagination_id: min_id} = List.first(entries)
|
||||||
activities
|
|
||||||
|> List.first()
|
|
||||||
|
|
||||||
build_pagination_fields(conn, min_id, max_id, extra_params)
|
build_pagination_fields(conn, min_id, max_id, extra_params)
|
||||||
|
|
||||||
%{id: max_id} ->
|
%{id: max_id} ->
|
||||||
%{id: min_id} =
|
%{id: min_id} = List.first(entries)
|
||||||
activities
|
|
||||||
|> List.first()
|
|
||||||
|
|
||||||
build_pagination_fields(conn, min_id, max_id, extra_params)
|
build_pagination_fields(conn, min_id, max_id, extra_params)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
defmodule Pleroma.Web.PleromaAPI.ChatController do
|
defmodule Pleroma.Web.PleromaAPI.ChatController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Chat
|
alias Pleroma.Chat
|
||||||
alias Pleroma.Chat.MessageReference
|
alias Pleroma.Chat.MessageReference
|
||||||
|
@ -47,7 +49,7 @@ def delete_message(%{assigns: %{user: %{id: user_id} = user}} = conn, %{
|
||||||
}) do
|
}) do
|
||||||
with %MessageReference{} = cm_ref <-
|
with %MessageReference{} = cm_ref <-
|
||||||
MessageReference.get_by_id(message_id),
|
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),
|
%Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
|
||||||
{:ok, _} <- remove_or_delete(cm_ref, user) do
|
{:ok, _} <- remove_or_delete(cm_ref, user) do
|
||||||
conn
|
conn
|
||||||
|
@ -68,18 +70,13 @@ defp remove_or_delete(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp remove_or_delete(cm_ref, _) do
|
defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref)
|
||||||
cm_ref
|
|
||||||
|> MessageReference.delete()
|
|
||||||
end
|
|
||||||
|
|
||||||
def post_chat_message(
|
def post_chat_message(
|
||||||
%{body_params: params, assigns: %{user: %{id: user_id} = user}} = conn,
|
%{body_params: params, assigns: %{user: user}} = conn,
|
||||||
%{
|
%{id: id}
|
||||||
id: id
|
|
||||||
}
|
|
||||||
) do
|
) 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),
|
%User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
|
||||||
{:ok, activity} <-
|
{:ok, activity} <-
|
||||||
CommonAPI.post_chat_message(user, recipient, params[:content],
|
CommonAPI.post_chat_message(user, recipient, params[:content],
|
||||||
|
@ -93,13 +90,12 @@ def post_chat_message(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{
|
def mark_message_as_read(
|
||||||
id: chat_id,
|
%{assigns: %{user: %{id: user_id}}} = conn,
|
||||||
message_id: message_id
|
%{id: chat_id, message_id: message_id}
|
||||||
}) do
|
) do
|
||||||
with %MessageReference{} = cm_ref <-
|
with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id),
|
||||||
MessageReference.get_by_id(message_id),
|
^chat_id <- to_string(cm_ref.chat_id),
|
||||||
^chat_id <- cm_ref.chat_id |> to_string(),
|
|
||||||
%Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
|
%Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
|
||||||
{:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
|
{:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
|
||||||
conn
|
conn
|
||||||
|
@ -109,36 +105,28 @@ def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_as_read(
|
def mark_as_read(
|
||||||
%{
|
%{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn,
|
||||||
body_params: %{last_read_id: last_read_id},
|
|
||||||
assigns: %{user: %{id: user_id}}
|
|
||||||
} = conn,
|
|
||||||
%{id: id}
|
%{id: id}
|
||||||
) do
|
) 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),
|
||||||
{_n, _} <-
|
{_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
|
||||||
MessageReference.set_all_seen_for_chat(chat, last_read_id) do
|
|
||||||
conn
|
conn
|
||||||
|> put_view(ChatView)
|
|> put_view(ChatView)
|
||||||
|> render("show.json", chat: chat)
|
|> render("show.json", chat: chat)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do
|
def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do
|
||||||
with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do
|
with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
|
||||||
cm_refs =
|
chat_message_refs =
|
||||||
chat
|
chat
|
||||||
|> MessageReference.for_chat_query()
|
|> MessageReference.for_chat_query()
|
||||||
|> Pagination.fetch_paginated(params)
|
|> Pagination.fetch_paginated(params)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|> add_link_headers(chat_message_refs)
|
||||||
|> put_view(MessageReferenceView)
|
|> put_view(MessageReferenceView)
|
||||||
|> render("index.json", chat_message_references: cm_refs)
|
|> render("index.json", chat_message_references: chat_message_refs)
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
conn
|
|
||||||
|> put_status(:not_found)
|
|
||||||
|> json(%{error: "not found"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -158,8 +146,8 @@ def index(%{assigns: %{user: %{id: user_id} = user}} = conn, _params) do
|
||||||
|> render("index.json", chats: chats)
|
|> render("index.json", chats: chats)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(%{assigns: %{user: user}} = conn, params) do
|
def create(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
with %User{ap_id: recipient} <- User.get_by_id(params[:id]),
|
with %User{ap_id: recipient} <- User.get_cached_by_id(id),
|
||||||
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
|
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
|
||||||
conn
|
conn
|
||||||
|> put_view(ChatView)
|
|> put_view(ChatView)
|
||||||
|
@ -167,8 +155,8 @@ def create(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def show(%{assigns: %{user: user}} = conn, params) do
|
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
with %Chat{} = chat <- Repo.get_by(Chat, user_id: user.id, id: params[:id]) do
|
with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
|
||||||
conn
|
conn
|
||||||
|> put_view(ChatView)
|
|> put_view(ChatView)
|
||||||
|> render("show.json", chat: chat)
|
|> render("show.json", chat: chat)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
|
defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
|
||||||
use Pleroma.Web.ConnCase, async: true
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
alias Pleroma.Chat
|
alias Pleroma.Chat
|
||||||
alias Pleroma.Chat.MessageReference
|
alias Pleroma.Chat.MessageReference
|
||||||
|
@ -184,17 +184,39 @@ test "it paginates", %{conn: conn, user: user} do
|
||||||
|
|
||||||
chat = Chat.get(user.id, recipient.ap_id)
|
chat = Chat.get(user.id, recipient.ap_id)
|
||||||
|
|
||||||
result =
|
response = get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages")
|
||||||
conn
|
result = json_response_and_validate_schema(response, 200)
|
||||||
|> get("/api/v1/pleroma/chats/#{chat.id}/messages")
|
|
||||||
|> json_response_and_validate_schema(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
|
assert length(result) == 20
|
||||||
|
|
||||||
result =
|
response =
|
||||||
conn
|
get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}")
|
||||||
|> get("/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
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
|
assert length(result) == 10
|
||||||
end
|
end
|
||||||
|
@ -223,12 +245,10 @@ test "it returns the messages for a given chat", %{conn: conn, user: user} do
|
||||||
assert length(result) == 3
|
assert length(result) == 3
|
||||||
|
|
||||||
# Trying to get the chat of a different user
|
# Trying to get the chat of a different user
|
||||||
result =
|
conn
|
||||||
conn
|
|> assign(:user, other_user)
|
||||||
|> assign(:user, other_user)
|
|> get("/api/v1/pleroma/chats/#{chat.id}/messages")
|
||||||
|> get("/api/v1/pleroma/chats/#{chat.id}/messages")
|
|> json_response_and_validate_schema(404)
|
||||||
|
|
||||||
assert result |> json_response(404)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue