From 3c21a336562f7094849054b9f332b44c8eec3fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 10 Mar 2024 23:52:27 +0100 Subject: [PATCH] Backup chats and chat messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/backups-chats.add | 1 + lib/pleroma/chat/message_reference.ex | 2 +- lib/pleroma/user/backup.ex | 62 ++++++++++++++++++++++++++- test/pleroma/user/backup_test.exs | 58 ++++++++++++++++++++++++- 4 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 changelog.d/backups-chats.add diff --git a/changelog.d/backups-chats.add b/changelog.d/backups-chats.add new file mode 100644 index 0000000000..c48c589c4b --- /dev/null +++ b/changelog.d/backups-chats.add @@ -0,0 +1 @@ +Backup chats and chat messages \ No newline at end of file diff --git a/lib/pleroma/chat/message_reference.ex b/lib/pleroma/chat/message_reference.ex index ea65a4a35a..af9e352556 100644 --- a/lib/pleroma/chat/message_reference.ex +++ b/lib/pleroma/chat/message_reference.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Chat.MessageReference do import Ecto.Changeset import Ecto.Query - @primary_key {:id, FlakeId.Ecto.Type, autogenerate: true} + @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} schema "chat_message_references" do belongs_to(:object, Object) diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index b7f00bbf7f..3c482f95f6 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -14,6 +14,7 @@ defmodule Pleroma.User.Backup do alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.Chat alias Pleroma.Repo alias Pleroma.User alias Pleroma.User.Backup.State @@ -196,7 +197,14 @@ defp wait_backup(backup, current_processed, task) do end end - @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json'] + @files [ + 'actor.json', + 'outbox.json', + 'likes.json', + 'bookmarks.json', + 'chats.json', + 'chat_messages.json' + ] @spec export(Pleroma.User.Backup.t(), pid()) :: {:ok, String.t()} | :error def export(%__MODULE__{} = backup, caller_pid) do backup = Repo.preload(backup, :user) @@ -207,6 +215,8 @@ def export(%__MODULE__{} = backup, caller_pid) do :ok <- statuses(dir, backup.user, caller_pid), :ok <- likes(dir, backup.user, caller_pid), :ok <- bookmarks(dir, backup.user, caller_pid), + :ok <- chats(dir, backup.user, caller_pid), + :ok <- chat_messages(dir, backup.user, caller_pid), {:ok, zip_path} <- :zip.create(backup.file_name, @files, cwd: dir), {:ok, _} <- File.rm_rf(dir) do {:ok, zip_path} @@ -357,6 +367,56 @@ defp statuses(dir, user, caller_pid) do caller_pid ) end + + defp chats(dir, user, caller_pid) do + Chat.for_user_query(user.id) + |> write( + dir, + "chats", + fn chat -> + {:ok, + %{ + "type" => "Chat", + "id" => "#{Pleroma.Web.Endpoint.url()}/chats/#{chat.id}", + "actor" => user.ap_id, + "to" => [chat.recipient], + "published" => + chat.inserted_at |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601() + }} + end, + caller_pid + ) + end + + def chat_messages(dir, %{id: user_id}, caller_pid) do + chats_subquery = + from(c in Chat, + where: c.user_id == ^user_id, + select: c.id + ) + + from(cr in Chat.MessageReference, + where: cr.chat_id in subquery(chats_subquery), + preload: [:object] + ) + |> write( + dir, + "chat_messages", + fn reference -> + with {:ok, activity} <- Transmogrifier.prepare_outgoing(reference.object.data), + {:ok, activity} <- + {:ok, + Map.put( + activity, + "context", + "#{Pleroma.Web.Endpoint.url()}/chats/#{reference.chat_id}" + )} do + {:ok, Map.delete(activity, "@context")} + end + end, + caller_pid + ) + end end defmodule Pleroma.User.Backup.ProcessorAPI do diff --git a/test/pleroma/user/backup_test.exs b/test/pleroma/user/backup_test.exs index 0ac57e3342..3728678925 100644 --- a/test/pleroma/user/backup_test.exs +++ b/test/pleroma/user/backup_test.exs @@ -12,9 +12,11 @@ defmodule Pleroma.User.BackupTest do import Mox alias Pleroma.Bookmark + alias Pleroma.Chat alias Pleroma.Tests.ObanHelpers alias Pleroma.UnstubbedConfigMock, as: ConfigMock alias Pleroma.Uploaders.S3.ExAwsMock + alias Pleroma.User alias Pleroma.User.Backup alias Pleroma.User.Backup.ProcessorMock alias Pleroma.Web.CommonAPI @@ -165,7 +167,10 @@ test "it removes outdated backups after creating a fresh one" do end test "it creates a zip archive with user data" do - user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"}) + %User{ap_id: ap_id} = + user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"}) + + %User{ap_id: other_ap_id} = other_user = insert(:user) {:ok, %{object: %{data: %{"id" => id1}}} = status1} = CommonAPI.post(user, %{status: "status1"}) @@ -182,6 +187,11 @@ test "it creates a zip archive with user data" do Bookmark.create(user.id, status2.id) Bookmark.create(user.id, status3.id) + {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id) + + {:ok, _message_1} = CommonAPI.post_chat_message(user, other_user, "hey") + {:ok, _message_2} = CommonAPI.post_chat_message(other_user, user, "ho") + assert {:ok, backup} = user |> Backup.new() |> Repo.insert() assert {:ok, path} = Backup.export(backup, self()) assert {:ok, zipfile} = :zip.zip_open(String.to_charlist(path), [:memory]) @@ -261,6 +271,52 @@ test "it creates a zip archive with user data" do "type" => "OrderedCollection" } = Jason.decode!(json) + assert {:ok, {'chats.json', json}} = :zip.zip_get('chats.json', zipfile) + + chat_id = "http://localhost:4001/chats/#{chat.id}" + + assert %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => "chats.json", + "orderedItems" => [ + %{ + "type" => "Chat", + "id" => ^chat_id, + "actor" => ^ap_id, + "to" => [^other_ap_id] + } + ], + "totalItems" => 1, + "type" => "OrderedCollection" + } = Jason.decode!(json) + + assert {:ok, {'chat_messages.json', json}} = :zip.zip_get('chat_messages.json', zipfile) + + chat_id = "http://localhost:4001/chats/#{chat.id}" + + assert %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => "chat_messages.json", + "orderedItems" => [ + %{ + "type" => "ChatMessage", + "actor" => ^ap_id, + "to" => [^other_ap_id], + "context" => ^chat_id, + "content" => "hey" + }, + %{ + "type" => "ChatMessage", + "actor" => ^other_ap_id, + "to" => [^ap_id], + "context" => ^chat_id, + "content" => "ho" + } + ], + "totalItems" => 2, + "type" => "OrderedCollection" + } = Jason.decode!(json) + :zip.zip_close(zipfile) File.rm!(path) end