Add idempotency_key to the chat_message entity.
authoreugenijm <eugenijm@protonmail.com>
Sat, 31 Oct 2020 02:50:48 +0000 (05:50 +0300)
committereugenijm <eugenijm@protonmail.com>
Sat, 31 Oct 2020 02:50:59 +0000 (05:50 +0300)
CHANGELOG.md
docs/API/chats.md
lib/pleroma/application.ex
lib/pleroma/web/activity_pub/side_effects.ex
lib/pleroma/web/common_api.ex
lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs
test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs
test/pleroma/web/streamer_test.exs

index 11820d313a050b4c48952693445d402c1083e9a3..bb02d7b320cae30ed01391ca93400fa34ea2e9cc 100644 (file)
@@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Pleroma API: Pagination for remote/local packs and emoji.
 - Admin API: (`GET /api/pleroma/admin/users`) added filters user by `unconfirmed` status
 - Admin API: (`GET /api/pleroma/admin/users`) added filters user by `actor_type`
+- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
 
 </details>
 
index aa6119670449c4887cc22cec3c291231b30a8138..9857aac672224e44aed52d73ea826d4eeded1561 100644 (file)
@@ -173,11 +173,14 @@ Returned data:
     "created_at": "2020-04-21T15:06:45.000Z",
     "emojis": [],
     "id": "12",
-    "unread": false
+    "unread": false,
+    "idempotency_key": "75442486-0874-440c-9db1-a7006c25a31f"
   }
 ]
 ```
 
+- idempotency_key: The copy of the `idempotency-key` HTTP request header that can be used for optimistic message sending. Included only during the first few minutes after the message creation.
+
 ### Posting a chat message
 
 Posting a chat message for given Chat id works like this:
index 51e9dda3babf7ebafd5ca84887d477af7912d979..7c4cd9626d04c30fd06d2bb54fedda9d99951603 100644 (file)
@@ -168,7 +168,11 @@ defmodule Pleroma.Application do
       build_cachex("web_resp", limit: 2500),
       build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
       build_cachex("failed_proxy_url", limit: 2500),
-      build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000)
+      build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
+      build_cachex("chat_message_id_idempotency_key",
+        expiration: chat_message_id_idempotency_key_expiration(),
+        limit: 500_000
+      )
     ]
   end
 
@@ -178,6 +182,9 @@ defmodule Pleroma.Application do
   defp idempotency_expiration,
     do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
 
+  defp chat_message_id_idempotency_key_expiration,
+    do: expiration(default: :timer.minutes(2), interval: :timer.seconds(60))
+
   defp seconds_valid_interval,
     do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
 
index 0fff5faf2d4a138978f6ff8c85097574e7f0320d..d552e91fcaf5c43eb26f02159efbf0fe63cc9f14 100644 (file)
@@ -312,6 +312,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
             {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
             {:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id)
 
+            Cachex.put(
+              :chat_message_id_idempotency_key_cache,
+              cm_ref.id,
+              meta[:idempotency_key]
+            )
+
             {
               ["user", "user:pleroma_chat"],
               {user, %{cm_ref | chat: chat, object: object}}
index 60a50b027f5c5981adc3406c0b086bdcff49e039..318ffc5d0077f7bff258fb2f95e161b7ffdd8660 100644 (file)
@@ -45,7 +45,8 @@ defmodule Pleroma.Web.CommonAPI do
          {_, {:ok, %Activity{} = activity, _meta}} <-
            {:common_pipeline,
             Pipeline.common_pipeline(create_activity_data,
-              local: true
+              local: true,
+              idempotency_key: opts[:idempotency_key]
             )} do
       {:ok, activity}
     else
index 6357148d012e9f0f0071c03f2e82298919b72e0c..2c4d3f13554388ea2c9bd1d030d9b43de0c1a04f 100644 (file)
@@ -80,7 +80,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
          %User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
          {:ok, activity} <-
            CommonAPI.post_chat_message(user, recipient, params[:content],
-             media_id: params[:media_id]
+             media_id: params[:media_id],
+             idempotency_key: idempotency_key(conn)
            ),
          message <- Object.normalize(activity, false),
          cm_ref <- MessageReference.for_chat_and_object(chat, message) do
@@ -169,4 +170,11 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
       |> render("show.json", chat: chat)
     end
   end
+
+  defp idempotency_key(conn) do
+    case get_req_header(conn, "idempotency-key") do
+      [key] -> key
+      _ -> nil
+    end
+  end
 end
index d4e08b50db95fdbb93abd03354307caf7942280e..c058fb340eebf6ab2f3956ee541a883fae7328d1 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
   use Pleroma.Web, :view
 
+  alias Pleroma.Maps
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI.Utils
   alias Pleroma.Web.MastodonAPI.StatusView
@@ -37,6 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
           Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object)
         )
     }
+    |> put_idempotency_key()
   end
 
   def render("index.json", opts) do
@@ -47,4 +49,13 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
       Map.put(opts, :as, :chat_message_reference)
     )
   end
+
+  defp put_idempotency_key(data) do
+    with {:ok, idempotency_key} <- Cachex.get(:chat_message_id_idempotency_key_cache, data.id) do
+      data
+      |> Maps.put_if_present(:idempotency_key, idempotency_key)
+    else
+      _ -> data
+    end
+  end
 end
index 6381f9757e5f33ff429d51aa7dc8e38754f0c1a9..fa6b9db65ea5d44a88f1d83a54f02de5643d50f6 100644 (file)
@@ -82,11 +82,13 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
       result =
         conn
         |> put_req_header("content-type", "application/json")
+        |> put_req_header("idempotency-key", "123")
         |> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{"content" => "Hallo!!"})
         |> json_response_and_validate_schema(200)
 
       assert result["content"] == "Hallo!!"
       assert result["chat_id"] == chat.id |> to_string()
+      assert result["idempotency_key"] == "123"
     end
 
     test "it fails if there is no content", %{conn: conn, user: user} do
index f171a1e55be4e5df38376e5703c85637c87a687c..ae825787083603217fe6a3deea8e17892cfa0fca 100644 (file)
@@ -25,7 +25,9 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do
     }
 
     {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
-    {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:")
+
+    {:ok, activity} =
+      CommonAPI.post_chat_message(user, recipient, "kippis :firefox:", idempotency_key: "123")
 
     chat = Chat.get(user.id, recipient.ap_id)
 
@@ -42,6 +44,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do
     assert chat_message[:created_at]
     assert chat_message[:unread] == false
     assert match?([%{shortcode: "firefox"}], chat_message[:emojis])
+    assert chat_message[:idempotency_key] == "123"
 
     clear_config([:rich_media, :enabled], true)
 
index 185724a9fce49d1b197466f6e4c85b3e1b5ee431..395016da2a8eb9f11d962fe37bf2b7aa3a2fe4af 100644 (file)
@@ -255,7 +255,9 @@ defmodule Pleroma.Web.StreamerTest do
     } do
       other_user = insert(:user)
 
-      {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno")
+      {:ok, create_activity} =
+        CommonAPI.post_chat_message(other_user, user, "hey cirno", idempotency_key: "123")
+
       object = Object.normalize(create_activity, false)
       chat = Chat.get(user.id, other_user.ap_id)
       cm_ref = MessageReference.for_chat_and_object(chat, object)