From 42d034505a1904fae83856ab96f528fe874e70be Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sun, 17 Mar 2019 15:37:55 +0100 Subject: [PATCH 01/25] Add test for conversation API beforehand --- .../mastodon_api_controller_test.exs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 059d5237d..1560ec79c 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -248,6 +248,57 @@ test "direct timeline", %{conn: conn} do assert status["url"] != direct.data["id"] end + test "Conversations", %{conn: conn} do + user_one = insert(:user) + user_two = insert(:user) + + {:ok, user_two} = User.follow(user_two, user_one) + + {:ok, direct} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}!", + "visibility" => "direct" + }) + + {:ok, _follower_only} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}!", + "visibility" => "private" + }) + + res_conn = + conn + |> assign(:user, user) + |> get("/api/v1/conversations") + + assert response = json_response(res_conn, 200) + + assert %{ + "id" => res_id, + "accounts" => res_accounts, + "last_status" => res_last_status, + "unread" => unread + } = reponse + + assert unread == false + + # Apparently undocumented API endpoint + res_conn = + conn + |> assign(:user, user) + |> get("/api/v1/conversations/#{res_id}/read") + + assert response == json_response(res_conn, 200) + + # (vanilla) Mastodon frontend behaviour + res_conn = + conn + |> assign(:user, user) + |> get("/api/v1/statuses/#{res_last_status.id}/context") + + assert %{ancestors: [], descendants: []} == json_response(res_conn, 200) + end + test "doesn't include DMs from blocked users", %{conn: conn} do blocker = insert(:user) blocked = insert(:user) From f6fab01ba7a08fe0e5147f82d9e3dd294922dc93 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sun, 17 Mar 2019 17:06:28 +0100 Subject: [PATCH 02/25] Web.Router: Add routes for Conversation mastoAPI --- lib/pleroma/web/router.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index befd382ba..6d000b7f5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -243,6 +243,9 @@ defmodule Pleroma.Web.Router do get("/suggestions", MastodonAPIController, :suggestions) + get("/conversations", MastodonAPIController, :conversations) + get("/conversations/:id/read", MastodonAPIController, :get_conversation) + get("/endorsements", MastodonAPIController, :empty_array) get("/pleroma/flavour", MastodonAPIController, :get_flavour) From b5cecebbc14c80d08f1a9f541722218c48dc514f Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 10 Apr 2019 09:32:17 +0200 Subject: [PATCH 03/25] Conversations: Fix specs. --- test/web/mastodon_api/mastodon_api_controller_test.exs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 9e19fb48e..519ad8f4d 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -320,7 +320,7 @@ test "Conversations", %{conn: conn} do res_conn = conn - |> assign(:user, user) + |> assign(:user, user_one) |> get("/api/v1/conversations") assert response = json_response(res_conn, 200) @@ -330,22 +330,22 @@ test "Conversations", %{conn: conn} do "accounts" => res_accounts, "last_status" => res_last_status, "unread" => unread - } = reponse + } = response assert unread == false # Apparently undocumented API endpoint res_conn = conn - |> assign(:user, user) - |> get("/api/v1/conversations/#{res_id}/read") + |> assign(:user, user_one) + |> post("/api/v1/conversations/#{res_id}/read") assert response == json_response(res_conn, 200) # (vanilla) Mastodon frontend behaviour res_conn = conn - |> assign(:user, user) + |> assign(:user, user_one) |> get("/api/v1/statuses/#{res_last_status.id}/context") assert %{ancestors: [], descendants: []} == json_response(res_conn, 200) From d1da6b155ab758ae4eb8fa154997a0a2a179897c Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 10 Apr 2019 09:34:53 +0200 Subject: [PATCH 04/25] Conversation: Add Conversations and Participations. --- lib/conversation.ex | 30 ++++++++++++++++++ lib/conversation/participation.ex | 31 +++++++++++++++++++ .../20190408123347_create_conversations.exs | 26 ++++++++++++++++ test/conversation/participation_test.exs | 22 +++++++++++++ test/conversation_test.exs | 12 +++++++ test/support/factory.ex | 6 ++++ 6 files changed, 127 insertions(+) create mode 100644 lib/conversation.ex create mode 100644 lib/conversation/participation.ex create mode 100644 priv/repo/migrations/20190408123347_create_conversations.exs create mode 100644 test/conversation/participation_test.exs create mode 100644 test/conversation_test.exs diff --git a/lib/conversation.ex b/lib/conversation.ex new file mode 100644 index 000000000..cfb78d925 --- /dev/null +++ b/lib/conversation.ex @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Conversation do + alias Pleroma.Repo + alias Pleroma.Conversation.Participation + use Ecto.Schema + import Ecto.Changeset + + schema "conversations" do + field(:ap_id, :string) + has_many(:participations, Participation) + + timestamps() + end + + def creation_cng(struct, params) do + struct + |> cast(params, [:ap_id]) + |> validate_required([:ap_id]) + |> unique_constraint(:ap_id) + end + + def create_for_ap_id(ap_id) do + %__MODULE__{} + |> creation_cng(%{ap_id: ap_id}) + |> Repo.insert() + end +end diff --git a/lib/conversation/participation.ex b/lib/conversation/participation.ex new file mode 100644 index 000000000..244d37c46 --- /dev/null +++ b/lib/conversation/participation.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Conversation.Participation do + use Ecto.Schema + alias Pleroma.User + alias Pleroma.Conversation + alias Pleroma.Repo + import Ecto.Changeset + + schema "conversation_participations" do + belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:conversation, Conversation) + field(:read, :boolean, default: false) + + timestamps() + end + + def creation_cng(struct, params) do + struct + |> cast(params, [:user_id, :conversation_id]) + |> validate_required([:user_id, :conversation_id]) + end + + def create_for_user_and_conversation(user, conversation) do + %__MODULE__{} + |> creation_cng(%{user_id: user.id, conversation_id: conversation.id}) + |> Repo.insert() + end +end diff --git a/priv/repo/migrations/20190408123347_create_conversations.exs b/priv/repo/migrations/20190408123347_create_conversations.exs new file mode 100644 index 000000000..68bf766bc --- /dev/null +++ b/priv/repo/migrations/20190408123347_create_conversations.exs @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.CreateConversations do + use Ecto.Migration + + def change do + create table(:conversations) do + add(:ap_id, :string, null: false) + timestamps() + end + + create table(:conversation_participations) do + add(:user_id, references(:users, type: :uuid, on_delete: :delete_all)) + add(:conversation_id, references(:conversations, on_delete: :delete_all)) + add(:read, :boolean, default: false) + + timestamps() + end + + create index(:conversation_participations, [:user_id]) + create index(:conversation_participations, [:conversation_id]) + create unique_index(:conversations, [:ap_id]) + end +end diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs new file mode 100644 index 000000000..8dc15a802 --- /dev/null +++ b/test/conversation/participation_test.exs @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Conversation.ParticipationTest do + use Pleroma.DataCase + + import Pleroma.Factory + + alias Pleroma.Conversation.Participation + + test "it creates a participation for a conversation and a user" do + user = insert(:user) + conversation = insert(:conversation) + + {:ok, %Participation{} = participation} = + Participation.create_for_user_and_conversation(user, conversation) + + assert participation.user_id == user.id + assert participation.conversation_id == conversation.id + end +end diff --git a/test/conversation_test.exs b/test/conversation_test.exs new file mode 100644 index 000000000..8fb55d51c --- /dev/null +++ b/test/conversation_test.exs @@ -0,0 +1,12 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ConversationTest do + use Pleroma.DataCase + alias Pleroma.Conversation + + test "it creates a conversation for given ap_id" do + assert {:ok, %Conversation{}} = Conversation.create_for_ap_id("https://some_ap_id") + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex index ea59912cf..af38be46c 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -5,6 +5,12 @@ defmodule Pleroma.Factory do use ExMachina.Ecto, repo: Pleroma.Repo + def conversation_factory do + %Pleroma.Conversation{ + ap_id: sequence(:ap_id, &"https://some_conversation/#{&1}") + } + end + def user_factory do user = %Pleroma.User{ name: sequence(:name, &"Test テスト User #{&1}"), From 64c1c3a4071f3f99a59f38e2dcde499bda3969cf Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 10 Apr 2019 15:12:01 +0200 Subject: [PATCH 05/25] Participations: Add marking as read and unread. --- lib/conversation/participation.ex | 18 ++++++++++++++++++ test/conversation/participation_test.exs | 14 ++++++++++++++ test/support/factory.ex | 11 +++++++++++ 3 files changed, 43 insertions(+) diff --git a/lib/conversation/participation.ex b/lib/conversation/participation.ex index 244d37c46..ab59a529e 100644 --- a/lib/conversation/participation.ex +++ b/lib/conversation/participation.ex @@ -28,4 +28,22 @@ def create_for_user_and_conversation(user, conversation) do |> creation_cng(%{user_id: user.id, conversation_id: conversation.id}) |> Repo.insert() end + + def read_cng(struct, params) do + struct + |> cast(params, [:read]) + |> validate_required([:read]) + end + + def mark_as_read(participation) do + participation + |> read_cng(%{read: true}) + |> Repo.update() + end + + def mark_as_unread(participation) do + participation + |> read_cng(%{read: false}) + |> Repo.update() + end end diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs index 8dc15a802..eae1873ca 100644 --- a/test/conversation/participation_test.exs +++ b/test/conversation/participation_test.exs @@ -19,4 +19,18 @@ test "it creates a participation for a conversation and a user" do assert participation.user_id == user.id assert participation.conversation_id == conversation.id end + + test "it marks a participation as read" do + participation = insert(:participation, %{read: false}) + {:ok, participation} = Participation.mark_as_read(participation) + + assert participation.read + end + + test "it marks a participation as unread" do + participation = insert(:participation, %{read: true}) + {:ok, participation} = Participation.mark_as_unread(participation) + + refute participation.read + end end diff --git a/test/support/factory.ex b/test/support/factory.ex index af38be46c..2a2954ad6 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -5,6 +5,17 @@ defmodule Pleroma.Factory do use ExMachina.Ecto, repo: Pleroma.Repo + def participation_factory do + conversation = insert(:conversation) + user = insert(:user) + + %Pleroma.Conversation.Participation{ + conversation: conversation, + user: user, + read: false + } + end + def conversation_factory do %Pleroma.Conversation{ ap_id: sequence(:ap_id, &"https://some_conversation/#{&1}") From 280172f6f6d74872349e3b4e6f1feaa9c95b3900 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 10 Apr 2019 16:33:45 +0200 Subject: [PATCH 06/25] Conversations: Create or bump on inserting a dm. --- lib/conversation.ex | 42 +++++++++- lib/conversation/participation.ex | 6 +- lib/pleroma/web/activity_pub/activity_pub.ex | 2 + .../20190408123347_create_conversations.exs | 2 +- test/conversation/participation_test.exs | 22 ++++- test/conversation_test.exs | 84 ++++++++++++++++++- 6 files changed, 152 insertions(+), 6 deletions(-) diff --git a/lib/conversation.ex b/lib/conversation.ex index cfb78d925..3d53e91b7 100644 --- a/lib/conversation.ex +++ b/lib/conversation.ex @@ -5,10 +5,12 @@ defmodule Pleroma.Conversation do alias Pleroma.Repo alias Pleroma.Conversation.Participation + alias Pleroma.User use Ecto.Schema import Ecto.Changeset schema "conversations" do + # This is the context ap id. field(:ap_id, :string) has_many(:participations, Participation) @@ -25,6 +27,44 @@ def creation_cng(struct, params) do def create_for_ap_id(ap_id) do %__MODULE__{} |> creation_cng(%{ap_id: ap_id}) - |> Repo.insert() + |> Repo.insert( + on_conflict: [set: [updated_at: NaiveDateTime.utc_now()]], + returning: true, + conflict_target: :ap_id + ) + end + + def get_for_ap_id(ap_id) do + Repo.get_by(__MODULE__, ap_id: ap_id) + end + + @doc """ + This will + 1. Create a conversation if there isn't one already + 2. Create a participation for all the people involved who don't have one already + 3. Bump all relevant participations to 'unread' + """ + def create_or_bump_for(activity) do + with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity), + "Create" <- activity.data["type"], + "Note" <- activity.data["object"]["type"], + ap_id when is_binary(ap_id) <- activity.data["object"]["context"] do + {:ok, conversation} = create_for_ap_id(ap_id) + + local_users = User.get_users_from_set(activity.recipients, true) + + participations = + Enum.map(local_users, fn user -> + {:ok, participation} = + Participation.create_for_user_and_conversation(user, conversation) + + participation + end) + + %{ + conversation + | participations: participations + } + end end end diff --git a/lib/conversation/participation.ex b/lib/conversation/participation.ex index ab59a529e..a58d0ca0d 100644 --- a/lib/conversation/participation.ex +++ b/lib/conversation/participation.ex @@ -26,7 +26,11 @@ def creation_cng(struct, params) do def create_for_user_and_conversation(user, conversation) do %__MODULE__{} |> creation_cng(%{user_id: user.id, conversation_id: conversation.id}) - |> Repo.insert() + |> Repo.insert( + on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]], + returning: true, + conflict_target: [:user_id, :conversation_id] + ) end def read_cng(struct, params) do diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index f217e7bac..880d19a5e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Activity + alias Pleroma.Conversation alias Pleroma.Instances alias Pleroma.Notification alias Pleroma.Object @@ -143,6 +144,7 @@ def insert(map, local \\ true, fake \\ false) when is_map(map) do end) Notification.create_notifications(activity) + Conversation.create_or_bump_for(activity) stream_out(activity) {:ok, activity} else diff --git a/priv/repo/migrations/20190408123347_create_conversations.exs b/priv/repo/migrations/20190408123347_create_conversations.exs index 68bf766bc..0e0af30ae 100644 --- a/priv/repo/migrations/20190408123347_create_conversations.exs +++ b/priv/repo/migrations/20190408123347_create_conversations.exs @@ -19,8 +19,8 @@ def change do timestamps() end - create index(:conversation_participations, [:user_id]) create index(:conversation_participations, [:conversation_id]) + create unique_index(:conversation_participations, [:user_id, :conversation_id]) create unique_index(:conversations, [:ap_id]) end end diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs index eae1873ca..4e7d9dc92 100644 --- a/test/conversation/participation_test.exs +++ b/test/conversation/participation_test.exs @@ -4,9 +4,7 @@ defmodule Pleroma.Conversation.ParticipationTest do use Pleroma.DataCase - import Pleroma.Factory - alias Pleroma.Conversation.Participation test "it creates a participation for a conversation and a user" do @@ -18,6 +16,26 @@ test "it creates a participation for a conversation and a user" do assert participation.user_id == user.id assert participation.conversation_id == conversation.id + + :timer.sleep(1000) + # Creating again returns the same participation + {:ok, %Participation{} = participation_two} = + Participation.create_for_user_and_conversation(user, conversation) + + assert participation.id == participation_two.id + refute participation.updated_at == participation_two.updated_at + end + + test "recreating an existing participations sets it to unread" do + participation = insert(:participation, %{read: true}) + + {:ok, participation} = + Participation.create_for_user_and_conversation( + participation.user, + participation.conversation + ) + + refute participation.read end test "it marks a participation as read" do diff --git a/test/conversation_test.exs b/test/conversation_test.exs index 8fb55d51c..1c9d485ff 100644 --- a/test/conversation_test.exs +++ b/test/conversation_test.exs @@ -5,8 +5,90 @@ defmodule Pleroma.ConversationTest do use Pleroma.DataCase alias Pleroma.Conversation + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory test "it creates a conversation for given ap_id" do - assert {:ok, %Conversation{}} = Conversation.create_for_ap_id("https://some_ap_id") + assert {:ok, %Conversation{} = conversation} = + Conversation.create_for_ap_id("https://some_ap_id") + + # Inserting again returns the same + assert {:ok, conversation_two} = Conversation.create_for_ap_id("https://some_ap_id") + assert conversation_two.id == conversation.id + end + + test "public posts don't create conversations" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey"}) + + context = activity.data["object"]["context"] + + conversation = Conversation.get_for_ap_id(context) + + refute conversation + end + + test "it creates or updates a conversation and participations for a given DM" do + har = insert(:user) + jafnhar = insert(:user) + tridi = insert(:user) + + {:ok, activity} = + CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "direct"}) + + context = activity.data["object"]["context"] + + conversation = + Conversation.get_for_ap_id(context) + |> Repo.preload(:participations) + + assert conversation + [har_participation, jafnhar_participation] = conversation.participations + + assert har_participation.user_id == har.id + assert jafnhar_participation.user_id == jafnhar.id + + {:ok, activity} = + CommonAPI.post(jafnhar, %{ + "status" => "Hey @#{har.nickname}", + "visibility" => "direct", + "in_reply_to_status_id" => activity.id + }) + + context = activity.data["object"]["context"] + + conversation_two = + Conversation.get_for_ap_id(context) + |> Repo.preload(:participations) + + assert conversation_two.id == conversation.id + + [har_participation_two, jafnhar_participation_two] = conversation_two.participations + + assert har_participation_two.user_id == har.id + assert jafnhar_participation_two.user_id == jafnhar.id + + {:ok, activity} = + CommonAPI.post(tridi, %{ + "status" => "Hey @#{har.nickname}", + "visibility" => "direct", + "in_reply_to_status_id" => activity.id + }) + + context = activity.data["object"]["context"] + + conversation_three = + Conversation.get_for_ap_id(context) + |> Repo.preload(:participations) + + assert conversation_three.id == conversation.id + + [har_participation_three, jafnhar_participation_three, tridi_participation] = + conversation_three.participations + + assert har_participation_three.user_id == har.id + assert jafnhar_participation_three.user_id == jafnhar.id + assert tridi_participation.user_id == tridi.id end end From 20d9b9076051d2dea60919ad85aaf88154629dc4 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 10 Apr 2019 17:05:33 +0200 Subject: [PATCH 07/25] Participation: Get for a user. --- lib/conversation.ex | 4 ++-- lib/conversation/participation.ex | 9 +++++++ test/conversation/participation_test.exs | 30 ++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/conversation.ex b/lib/conversation.ex index 3d53e91b7..6eedc72b6 100644 --- a/lib/conversation.ex +++ b/lib/conversation.ex @@ -51,10 +51,10 @@ def create_or_bump_for(activity) do ap_id when is_binary(ap_id) <- activity.data["object"]["context"] do {:ok, conversation} = create_for_ap_id(ap_id) - local_users = User.get_users_from_set(activity.recipients, true) + users = User.get_users_from_set(activity.recipients) participations = - Enum.map(local_users, fn user -> + Enum.map(users, fn user -> {:ok, participation} = Participation.create_for_user_and_conversation(user, conversation) diff --git a/lib/conversation/participation.ex b/lib/conversation/participation.ex index a58d0ca0d..23e6409f1 100644 --- a/lib/conversation/participation.ex +++ b/lib/conversation/participation.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Conversation.Participation do alias Pleroma.Conversation alias Pleroma.Repo import Ecto.Changeset + import Ecto.Query schema "conversation_participations" do belongs_to(:user, User, type: Pleroma.FlakeId) @@ -50,4 +51,12 @@ def mark_as_unread(participation) do |> read_cng(%{read: false}) |> Repo.update() end + + def for_user(user, params \\ %{}) do + from(p in __MODULE__, + where: p.user_id == ^user.id, + order_by: [desc: p.updated_at] + ) + |> Pleroma.Pagination.fetch_paginated(params) + end end diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs index 4e7d9dc92..c52b4ed88 100644 --- a/test/conversation/participation_test.exs +++ b/test/conversation/participation_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Conversation.ParticipationTest do use Pleroma.DataCase import Pleroma.Factory alias Pleroma.Conversation.Participation + alias Pleroma.Web.CommonAPI test "it creates a participation for a conversation and a user" do user = insert(:user) @@ -51,4 +52,33 @@ test "it marks a participation as unread" do refute participation.read end + + test "gets all the participations for a user, ordered by updated at descending" do + user = insert(:user) + {:ok, activity_one} = CommonAPI.post(user, %{"status" => "x", "visibility" => "direct"}) + :timer.sleep(1000) + {:ok, activity_two} = CommonAPI.post(user, %{"status" => "x", "visibility" => "direct"}) + :timer.sleep(1000) + + {:ok, activity_three} = + CommonAPI.post(user, %{ + "status" => "x", + "visibility" => "direct", + "in_reply_to_status_id" => activity_one.id + }) + + assert [participation_one, participation_two] = + Participation.for_user(user) + |> Repo.preload(:conversation) + + assert participation_one.conversation.ap_id == activity_three.data["object"]["context"] + assert participation_two.conversation.ap_id == activity_two.data["object"]["context"] + + # Pagination + assert [participation_one] = + Participation.for_user(user, %{limit: 1}) + |> Repo.preload(:conversation) + + assert participation_one.conversation.ap_id == activity_three.data["object"]["context"] + end end From cf353514feff50c2ccb9a8079ce5e695eb7f8cb6 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 10 Apr 2019 17:28:02 +0200 Subject: [PATCH 08/25] Participations: Add last activity. --- lib/conversation/participation.ex | 27 ++++++++++++++++++++++++ test/conversation/participation_test.exs | 7 ++++++ 2 files changed, 34 insertions(+) diff --git a/lib/conversation/participation.ex b/lib/conversation/participation.ex index 23e6409f1..4183af8a7 100644 --- a/lib/conversation/participation.ex +++ b/lib/conversation/participation.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Conversation.Participation do alias Pleroma.User alias Pleroma.Conversation alias Pleroma.Repo + alias Pleroma.Web.ActivityPub.ActivityPub import Ecto.Changeset import Ecto.Query @@ -14,6 +15,7 @@ defmodule Pleroma.Conversation.Participation do belongs_to(:user, User, type: Pleroma.FlakeId) belongs_to(:conversation, Conversation) field(:read, :boolean, default: false) + field(:last_activity_id, Pleroma.FlakeId, virtual: true) timestamps() end @@ -59,4 +61,29 @@ def for_user(user, params \\ %{}) do ) |> Pleroma.Pagination.fetch_paginated(params) end + + def for_user_with_last_activity_id(user, params \\ %{}) do + for_user(user, params) + |> Repo.preload(:conversation) + |> Enum.map(fn participation -> + # TODO: Don't load all those activities, just get the most recent + # Involves splitting up the query. + activities = + ActivityPub.fetch_activities_for_context(participation.conversation.ap_id, %{ + "user" => user, + "blocking_user" => user + }) + + activity_id = + case activities do + [activity | _] -> activity.id + _ -> nil + end + + %{ + participation + | last_activity_id: activity_id + } + end) + end end diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs index c52b4ed88..5791fa0db 100644 --- a/test/conversation/participation_test.exs +++ b/test/conversation/participation_test.exs @@ -80,5 +80,12 @@ test "gets all the participations for a user, ordered by updated at descending" |> Repo.preload(:conversation) assert participation_one.conversation.ap_id == activity_three.data["object"]["context"] + + # With last_activity_id + assert [participation_one] = + Participation.for_user_with_last_activity_id(user, %{limit: 1}) + |> Repo.preload(:conversation) + + assert participation_one.last_activity_id == activity_three.id end end From de57094fca505e7dbf1a84fef5fb31fae68f2709 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 10 Apr 2019 17:30:25 +0200 Subject: [PATCH 09/25] Participations: Add sort index. --- .../20190410152859_add_participation_updated_at_index.exs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 priv/repo/migrations/20190410152859_add_participation_updated_at_index.exs diff --git a/priv/repo/migrations/20190410152859_add_participation_updated_at_index.exs b/priv/repo/migrations/20190410152859_add_participation_updated_at_index.exs new file mode 100644 index 000000000..1ce688c52 --- /dev/null +++ b/priv/repo/migrations/20190410152859_add_participation_updated_at_index.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.AddParticipationUpdatedAtIndex do + use Ecto.Migration + + def change do + create index(:conversation_participations, ["updated_at desc"]) + end +end From c352a0aba601ae444bf5b479ab3c643728a8b35e Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 10 Apr 2019 17:48:31 +0200 Subject: [PATCH 10/25] Conversations: Make tests run. --- .../mastodon_api/mastodon_api_controller.ex | 36 +++++++++++++++++++ lib/pleroma/web/router.ex | 2 +- .../mastodon_api_controller_test.exs | 24 +++++++------ 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 5462ce8be..57f73dacd 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Ecto.Changeset alias Pleroma.Activity alias Pleroma.Config + alias Pleroma.Conversation.Participation alias Pleroma.Filter alias Pleroma.Notification alias Pleroma.Object @@ -1584,6 +1585,41 @@ def reports(%{assigns: %{user: user}} = conn, params) do end end + def conversations(%{assigns: %{user: user}} = conn, params) do + participations = Participation.for_user_with_last_activity_id(user, params) + + conversations = + Enum.map(participations, fn participation -> + %{ + id: participation.id, + # TODO: Add this. + accounts: [], + unread: !participation.read, + last_status: participation.last_activity_id + } + end) + + conn + |> add_link_headers(:conversations, participations) + |> json(conversations) + end + + def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do + with %Participation{} = participation <- + Repo.get_by(Participation, id: participation_id, user_id: user.id), + {:ok, participation} <- Participation.mark_as_read(participation) do + conn + |> json(%{ + id: participation.id, + # TODO: Add this. + accounts: [], + unread: !participation.read, + # TODO: Add this. + last_status: nil + }) + end + end + def try_render(conn, target, params) when is_binary(target) do res = render(conn, target, params) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 0af743b80..dc5119c50 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -273,7 +273,7 @@ defmodule Pleroma.Web.Router do get("/suggestions", MastodonAPIController, :suggestions) get("/conversations", MastodonAPIController, :conversations) - get("/conversations/:id/read", MastodonAPIController, :get_conversation) + post("/conversations/:id/read", MastodonAPIController, :conversation_read) get("/endorsements", MastodonAPIController, :empty_array) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 519ad8f4d..d1d22edde 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -325,14 +325,17 @@ test "Conversations", %{conn: conn} do assert response = json_response(res_conn, 200) - assert %{ - "id" => res_id, - "accounts" => res_accounts, - "last_status" => res_last_status, - "unread" => unread - } = response + assert [ + %{ + "id" => res_id, + "accounts" => res_accounts, + "last_status" => res_last_status, + "unread" => unread + } + ] = response - assert unread == false + assert unread == true + assert res_last_status == direct.id # Apparently undocumented API endpoint res_conn = @@ -340,15 +343,16 @@ test "Conversations", %{conn: conn} do |> assign(:user, user_one) |> post("/api/v1/conversations/#{res_id}/read") - assert response == json_response(res_conn, 200) + assert response = json_response(res_conn, 200) + assert response["unread"] == false # (vanilla) Mastodon frontend behaviour res_conn = conn |> assign(:user, user_one) - |> get("/api/v1/statuses/#{res_last_status.id}/context") + |> get("/api/v1/statuses/#{res_last_status}/context") - assert %{ancestors: [], descendants: []} == json_response(res_conn, 200) + assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200) end test "doesn't include DMs from blocked users", %{conn: conn} do From d115d2a27e2e7a9df466fc4393416f804cb7e8e2 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 10 Apr 2019 18:17:22 +0200 Subject: [PATCH 11/25] Conversations: Tidying up. --- lib/{ => pleroma}/conversation.ex | 2 +- lib/{ => pleroma}/conversation/participation.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename lib/{ => pleroma}/conversation.ex (100%) rename lib/{ => pleroma}/conversation/participation.ex (100%) diff --git a/lib/conversation.ex b/lib/pleroma/conversation.ex similarity index 100% rename from lib/conversation.ex rename to lib/pleroma/conversation.ex index 6eedc72b6..a77a7cd6e 100644 --- a/lib/conversation.ex +++ b/lib/pleroma/conversation.ex @@ -3,8 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Conversation do - alias Pleroma.Repo alias Pleroma.Conversation.Participation + alias Pleroma.Repo alias Pleroma.User use Ecto.Schema import Ecto.Changeset diff --git a/lib/conversation/participation.ex b/lib/pleroma/conversation/participation.ex similarity index 100% rename from lib/conversation/participation.ex rename to lib/pleroma/conversation/participation.ex index 4183af8a7..1a2ceafeb 100644 --- a/lib/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -4,9 +4,9 @@ defmodule Pleroma.Conversation.Participation do use Ecto.Schema - alias Pleroma.User alias Pleroma.Conversation alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub import Ecto.Changeset import Ecto.Query From 36ec8d969405fc8e58a43100554c358fbeb6b4fc Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Apr 2019 13:20:46 +0200 Subject: [PATCH 12/25] ActivityPub: Fix specs. --- test/web/activity_pub/activity_pub_test.exs | 48 ++++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 17fec05b1..c3911500e 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -126,9 +126,15 @@ test "drops activities beyond a certain limit" do end test "doesn't drop activities with content being null" do + user = insert(:user) + data = %{ - "ok" => true, + "actor" => user.ap_id, + "to" => [], "object" => %{ + "actor" => user.ap_id, + "to" => [], + "type" => "Note", "content" => nil } } @@ -144,8 +150,17 @@ test "returns the activity if one with the same id is already in" do end test "inserts a given map into the activity database, giving it an id if it has none." do + user = insert(:user) + data = %{ - "ok" => true + "actor" => user.ap_id, + "to" => [], + "object" => %{ + "actor" => user.ap_id, + "to" => [], + "type" => "Note", + "content" => "hey" + } } {:ok, %Activity{} = activity} = ActivityPub.insert(data) @@ -155,9 +170,16 @@ test "inserts a given map into the activity database, giving it an id if it has given_id = "bla" data = %{ - "ok" => true, "id" => given_id, - "context" => "blabla" + "actor" => user.ap_id, + "to" => [], + "context" => "blabla", + "object" => %{ + "actor" => user.ap_id, + "to" => [], + "type" => "Note", + "content" => "hey" + } } {:ok, %Activity{} = activity} = ActivityPub.insert(data) @@ -168,10 +190,16 @@ test "inserts a given map into the activity database, giving it an id if it has end test "adds a context when none is there" do + user = insert(:user) + data = %{ - "id" => "some_id", + "actor" => user.ap_id, + "to" => [], "object" => %{ - "id" => "object_id" + "actor" => user.ap_id, + "to" => [], + "type" => "Note", + "content" => "hey" } } @@ -184,10 +212,16 @@ test "adds a context when none is there" do end test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do + user = insert(:user) + data = %{ + "actor" => user.ap_id, + "to" => [], "object" => %{ + "actor" => user.ap_id, + "to" => [], "type" => "Note", - "ok" => true + "content" => "hey" } } From 6f880b162736861189526ef0602f54bae6ff6153 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 11 Apr 2019 13:31:20 +0200 Subject: [PATCH 13/25] Conversation: Fix tests. --- test/conversation_test.exs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/test/conversation_test.exs b/test/conversation_test.exs index 1c9d485ff..4e3e86c8d 100644 --- a/test/conversation_test.exs +++ b/test/conversation_test.exs @@ -44,10 +44,12 @@ test "it creates or updates a conversation and participations for a given DM" do |> Repo.preload(:participations) assert conversation - [har_participation, jafnhar_participation] = conversation.participations - assert har_participation.user_id == har.id - assert jafnhar_participation.user_id == jafnhar.id + assert Enum.find(conversation.participations, fn %{user_id: user_id} -> har.id == user_id end) + + assert Enum.find(conversation.participations, fn %{user_id: user_id} -> + jafnhar.id == user_id + end) {:ok, activity} = CommonAPI.post(jafnhar, %{ @@ -64,10 +66,13 @@ test "it creates or updates a conversation and participations for a given DM" do assert conversation_two.id == conversation.id - [har_participation_two, jafnhar_participation_two] = conversation_two.participations + assert Enum.find(conversation_two.participations, fn %{user_id: user_id} -> + har.id == user_id + end) - assert har_participation_two.user_id == har.id - assert jafnhar_participation_two.user_id == jafnhar.id + assert Enum.find(conversation_two.participations, fn %{user_id: user_id} -> + jafnhar.id == user_id + end) {:ok, activity} = CommonAPI.post(tridi, %{ @@ -84,11 +89,16 @@ test "it creates or updates a conversation and participations for a given DM" do assert conversation_three.id == conversation.id - [har_participation_three, jafnhar_participation_three, tridi_participation] = - conversation_three.participations + assert Enum.find(conversation_three.participations, fn %{user_id: user_id} -> + har.id == user_id + end) - assert har_participation_three.user_id == har.id - assert jafnhar_participation_three.user_id == jafnhar.id - assert tridi_participation.user_id == tridi.id + assert Enum.find(conversation_three.participations, fn %{user_id: user_id} -> + jafnhar.id == user_id + end) + + assert Enum.find(conversation_three.participations, fn %{user_id: user_id} -> + tridi.id == user_id + end) end end From c1ebb38d3adc1d222be832405ec0d7497b61f94a Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 15 Apr 2019 21:45:25 +0200 Subject: [PATCH 14/25] Conversation: Also create participations for remote users. Needed to get the participating user list. --- lib/pleroma/conversation.ex | 2 +- test/conversation_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex index a77a7cd6e..5a2a3fc6d 100644 --- a/lib/pleroma/conversation.ex +++ b/lib/pleroma/conversation.ex @@ -51,7 +51,7 @@ def create_or_bump_for(activity) do ap_id when is_binary(ap_id) <- activity.data["object"]["context"] do {:ok, conversation} = create_for_ap_id(ap_id) - users = User.get_users_from_set(activity.recipients) + users = User.get_users_from_set(activity.recipients, false) participations = Enum.map(users, fn user -> diff --git a/test/conversation_test.exs b/test/conversation_test.exs index 4e3e86c8d..150d55631 100644 --- a/test/conversation_test.exs +++ b/test/conversation_test.exs @@ -31,7 +31,7 @@ test "public posts don't create conversations" do test "it creates or updates a conversation and participations for a given DM" do har = insert(:user) - jafnhar = insert(:user) + jafnhar = insert(:user, local: false) tridi = insert(:user) {:ok, activity} = From 0da985182f26927de0d2d9733816600afb89e79e Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 15 Apr 2019 21:58:58 +0200 Subject: [PATCH 15/25] Conversation: Return full status object, id is a string. --- lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 8 ++++++-- test/web/mastodon_api/mastodon_api_controller_test.exs | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 57f73dacd..3ffb767b9 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1590,12 +1590,16 @@ def conversations(%{assigns: %{user: user}} = conn, params) do conversations = Enum.map(participations, fn participation -> + activity = Activity.get_by_id_with_object(participation.last_activity_id) + + last_status = StatusView.render("status.json", %{activity: activity, for: user}) + %{ - id: participation.id, + id: participation.id |> to_string(), # TODO: Add this. accounts: [], unread: !participation.read, - last_status: participation.last_activity_id + last_status: last_status } end) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index d1d22edde..bd13f870c 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -334,8 +334,9 @@ test "Conversations", %{conn: conn} do } ] = response + assert is_binary(res_id) assert unread == true - assert res_last_status == direct.id + assert res_last_status["id"] == direct.id # Apparently undocumented API endpoint res_conn = @@ -350,7 +351,7 @@ test "Conversations", %{conn: conn} do res_conn = conn |> assign(:user, user_one) - |> get("/api/v1/statuses/#{res_last_status}/context") + |> get("/api/v1/statuses/#{res_last_status["id"]}/context") assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200) end From 76999c73a790232ff0c30fff7a51589fb44651a3 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 15 Apr 2019 22:28:42 +0200 Subject: [PATCH 16/25] Conversation: Add accounts to output. --- lib/pleroma/conversation.ex | 1 + lib/pleroma/conversation/participation.ex | 1 + .../web/mastodon_api/mastodon_api_controller.ex | 9 +++++++-- test/conversation_test.exs | 14 +++++++++++++- .../mastodon_api/mastodon_api_controller_test.exs | 1 + 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex index 5a2a3fc6d..d9c84cb1b 100644 --- a/lib/pleroma/conversation.ex +++ b/lib/pleroma/conversation.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Conversation do # This is the context ap id. field(:ap_id, :string) has_many(:participations, Participation) + has_many(:users, through: [:participations, :user]) timestamps() end diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index 1a2ceafeb..f200c1df5 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -60,6 +60,7 @@ def for_user(user, params \\ %{}) do order_by: [desc: p.updated_at] ) |> Pleroma.Pagination.fetch_paginated(params) + |> Repo.preload(conversation: [:users]) end def for_user_with_last_activity_id(user, params \\ %{}) do diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 3ffb767b9..c7166ff28 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1594,10 +1594,15 @@ def conversations(%{assigns: %{user: user}} = conn, params) do last_status = StatusView.render("status.json", %{activity: activity, for: user}) + accounts = + AccountView.render("accounts.json", %{ + users: participation.conversation.users, + as: :user + }) + %{ id: participation.id |> to_string(), - # TODO: Add this. - accounts: [], + accounts: accounts, unread: !participation.read, last_status: last_status } diff --git a/test/conversation_test.exs b/test/conversation_test.exs index 150d55631..239dda04f 100644 --- a/test/conversation_test.exs +++ b/test/conversation_test.exs @@ -85,7 +85,7 @@ test "it creates or updates a conversation and participations for a given DM" do conversation_three = Conversation.get_for_ap_id(context) - |> Repo.preload(:participations) + |> Repo.preload([:participations, :users]) assert conversation_three.id == conversation.id @@ -100,5 +100,17 @@ test "it creates or updates a conversation and participations for a given DM" do assert Enum.find(conversation_three.participations, fn %{user_id: user_id} -> tridi.id == user_id end) + + assert Enum.find(conversation_three.users, fn %{id: user_id} -> + har.id == user_id + end) + + assert Enum.find(conversation_three.users, fn %{id: user_id} -> + jafnhar.id == user_id + end) + + assert Enum.find(conversation_three.users, fn %{id: user_id} -> + tridi.id == user_id + end) end end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index bd13f870c..4fa5254f3 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -334,6 +334,7 @@ test "Conversations", %{conn: conn} do } ] = response + assert length(res_accounts) == 2 assert is_binary(res_id) assert unread == true assert res_last_status["id"] == direct.id From 24073f829f18d1ebd2cb23dd815c775b39145e42 Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Sun, 21 Apr 2019 00:40:41 +0700 Subject: [PATCH 17/25] Refactor query to return only 1 message instead of 20 --- lib/pleroma/conversation/participation.ex | 13 +---- lib/pleroma/web/activity_pub/activity_pub.ex | 53 ++++++++++++-------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index f200c1df5..61021fb18 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -65,22 +65,13 @@ def for_user(user, params \\ %{}) do def for_user_with_last_activity_id(user, params \\ %{}) do for_user(user, params) - |> Repo.preload(:conversation) |> Enum.map(fn participation -> - # TODO: Don't load all those activities, just get the most recent - # Involves splitting up the query. - activities = - ActivityPub.fetch_activities_for_context(participation.conversation.ap_id, %{ + activity_id = + ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{ "user" => user, "blocking_user" => user }) - activity_id = - case activities do - [activity | _] -> activity.id - _ -> nil - end - %{ participation | last_activity_id: activity_id diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 880d19a5e..577e6a59e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -459,35 +459,44 @@ def flag( end end - def fetch_activities_for_context(context, opts \\ %{}) do + defp fetch_activities_for_context_query(context, opts) do public = ["https://www.w3.org/ns/activitystreams#Public"] recipients = if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public - query = from(activity in Activity) - - query = - query - |> restrict_blocked(opts) - |> restrict_recipients(recipients, opts["user"]) - - query = - from( - activity in query, - where: - fragment( - "?->>'type' = ? and ?->>'context' = ?", - activity.data, - "Create", - activity.data, - ^context - ), - order_by: [desc: :id] + from(activity in Activity) + |> restrict_blocked(opts) + |> restrict_recipients(recipients, opts["user"]) + |> where( + [activity], + fragment( + "?->>'type' = ? and ?->>'context' = ?", + activity.data, + "Create", + activity.data, + ^context ) - |> Activity.with_preloaded_object() + ) + |> order_by([activity], desc: activity.id) + end - Repo.all(query) + @spec fetch_activities_for_context(String.t(), keyword() | map()) :: [Activity.t()] + def fetch_activities_for_context(context, opts \\ %{}) do + context + |> fetch_activities_for_context_query(opts) + |> Activity.with_preloaded_object() + |> Repo.all() + end + + @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) :: + Pleroma.FlakeId.t() | nil + def fetch_latest_activity_id_for_context(context, opts \\ %{}) do + context + |> fetch_activities_for_context_query(opts) + |> limit(1) + |> select([a], a.id) + |> Repo.one() end def fetch_public_activities(opts \\ %{}) do From 2662bea4e0d945edf7a24a44edf3ed39b2e64de9 Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Sun, 21 Apr 2019 20:26:13 +0700 Subject: [PATCH 18/25] Add accounts and last_status to conversation read response --- .../mastodon_api/mastodon_api_controller.ex | 24 +++++++++++++++---- .../mastodon_api_controller_test.exs | 2 ++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index c7166ff28..86cacb0b0 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1617,14 +1617,30 @@ def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_ with %Participation{} = participation <- Repo.get_by(Participation, id: participation_id, user_id: user.id), {:ok, participation} <- Participation.mark_as_read(participation) do + participation = Repo.preload(participation, conversation: :users) + + accounts = + AccountView.render("accounts.json", %{ + users: participation.conversation.users, + as: :user + }) + + last_activity_id = + ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{ + "user" => user, + "blocking_user" => user + }) + + activity = Activity.get_by_id_with_object(last_activity_id) + + last_status = StatusView.render("status.json", %{activity: activity, for: user}) + conn |> json(%{ id: participation.id, - # TODO: Add this. - accounts: [], + accounts: accounts, unread: !participation.read, - # TODO: Add this. - last_status: nil + last_status: last_status }) end end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 4fa5254f3..cf77dff78 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -346,6 +346,8 @@ test "Conversations", %{conn: conn} do |> post("/api/v1/conversations/#{res_id}/read") assert response = json_response(res_conn, 200) + assert length(response["accounts"]) == 2 + assert response["last_status"]["id"] == direct.id assert response["unread"] == false # (vanilla) Mastodon frontend behaviour From e56afefef9c0f256561c6ecab79e5520fdeb6d5e Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Sun, 21 Apr 2019 23:14:27 +0700 Subject: [PATCH 19/25] Refactor conversation function in MastodonAPIController to use a View --- .../mastodon_api/mastodon_api_controller.ex | 44 +++---------------- .../mastodon_api/views/conversation_view.ex | 38 ++++++++++++++++ 2 files changed, 43 insertions(+), 39 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/views/conversation_view.ex diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 86cacb0b0..d5b6a943f 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -22,6 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AppView + alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.FilterView alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Web.MastodonAPI.MastodonAPI @@ -1590,22 +1591,7 @@ def conversations(%{assigns: %{user: user}} = conn, params) do conversations = Enum.map(participations, fn participation -> - activity = Activity.get_by_id_with_object(participation.last_activity_id) - - last_status = StatusView.render("status.json", %{activity: activity, for: user}) - - accounts = - AccountView.render("accounts.json", %{ - users: participation.conversation.users, - as: :user - }) - - %{ - id: participation.id |> to_string(), - accounts: accounts, - unread: !participation.read, - last_status: last_status - } + ConversationView.render("participation.json", %{participation: participation, user: user}) end) conn @@ -1617,31 +1603,11 @@ def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_ with %Participation{} = participation <- Repo.get_by(Participation, id: participation_id, user_id: user.id), {:ok, participation} <- Participation.mark_as_read(participation) do - participation = Repo.preload(participation, conversation: :users) - - accounts = - AccountView.render("accounts.json", %{ - users: participation.conversation.users, - as: :user - }) - - last_activity_id = - ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{ - "user" => user, - "blocking_user" => user - }) - - activity = Activity.get_by_id_with_object(last_activity_id) - - last_status = StatusView.render("status.json", %{activity: activity, for: user}) + participation_view = + ConversationView.render("participation.json", %{participation: participation, user: user}) conn - |> json(%{ - id: participation.id, - accounts: accounts, - unread: !participation.read, - last_status: last_status - }) + |> json(participation_view) end end diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex new file mode 100644 index 000000000..d841a840c --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -0,0 +1,38 @@ +defmodule Pleroma.Web.MastodonAPI.ConversationView do + use Pleroma.Web, :view + + alias Pleroma.Activity + alias Pleroma.Repo + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.MastodonAPI.AccountView + + def render("participation.json", %{participation: participation, user: user}) do + participation = Repo.preload(participation, conversation: :users) + + last_activity_id = + with nil <- participation.last_activity_id do + ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{ + "user" => user, + "blocking_user" => user + }) + end + + activity = Activity.get_by_id_with_object(last_activity_id) + + last_status = StatusView.render("status.json", %{activity: activity, for: user}) + + accounts = + AccountView.render("accounts.json", %{ + users: participation.conversation.users, + as: :user + }) + + %{ + id: participation.id |> to_string(), + accounts: accounts, + unread: !participation.read, + last_status: last_status + } + end +end From eeb093631cd50583feb4864a016d0dc1e4c58f5e Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Sun, 21 Apr 2019 23:19:36 +0700 Subject: [PATCH 20/25] Fix Credo warning --- lib/pleroma/web/mastodon_api/views/conversation_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index d841a840c..eb61baa03 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -3,9 +3,9 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do alias Pleroma.Activity alias Pleroma.Repo + alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.MastodonAPI.AccountView def render("participation.json", %{participation: participation, user: user}) do participation = Repo.preload(participation, conversation: :users) From 4908e0eeee2ecb58b204198c20720d52548b6f4a Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Sun, 21 Apr 2019 23:24:33 +0700 Subject: [PATCH 21/25] Fix Credo warning --- lib/pleroma/web/mastodon_api/views/conversation_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index eb61baa03..8e8f7cf31 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -3,8 +3,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do alias Pleroma.Activity alias Pleroma.Repo - alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.StatusView def render("participation.json", %{participation: participation, user: user}) do From 8af55728e47f9f62d237704cd5a33fba5f946fa2 Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Mon, 29 Apr 2019 03:44:04 +0700 Subject: [PATCH 22/25] Fix tests --- lib/pleroma/conversation.ex | 5 +++-- test/conversation/participation_test.exs | 20 +++++++++----------- test/conversation_test.exs | 12 ++++++++---- test/web/activity_pub/activity_pub_test.exs | 5 +++-- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex index d9c84cb1b..e6a4ccc85 100644 --- a/lib/pleroma/conversation.ex +++ b/lib/pleroma/conversation.ex @@ -47,9 +47,10 @@ def get_for_ap_id(ap_id) do """ def create_or_bump_for(activity) do with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity), + object <- Pleroma.Object.normalize(activity), "Create" <- activity.data["type"], - "Note" <- activity.data["object"]["type"], - ap_id when is_binary(ap_id) <- activity.data["object"]["context"] do + "Note" <- object.data["type"], + ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do {:ok, conversation} = create_for_ap_id(ap_id) users = User.get_users_from_set(activity.recipients, false) diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs index 5791fa0db..568953b07 100644 --- a/test/conversation/participation_test.exs +++ b/test/conversation/participation_test.exs @@ -67,24 +67,22 @@ test "gets all the participations for a user, ordered by updated at descending" "in_reply_to_status_id" => activity_one.id }) - assert [participation_one, participation_two] = - Participation.for_user(user) - |> Repo.preload(:conversation) + assert [participation_one, participation_two] = Participation.for_user(user) - assert participation_one.conversation.ap_id == activity_three.data["object"]["context"] - assert participation_two.conversation.ap_id == activity_two.data["object"]["context"] + object2 = Pleroma.Object.normalize(activity_two) + object3 = Pleroma.Object.normalize(activity_three) + + assert participation_one.conversation.ap_id == object3.data["context"] + assert participation_two.conversation.ap_id == object2.data["context"] # Pagination - assert [participation_one] = - Participation.for_user(user, %{limit: 1}) - |> Repo.preload(:conversation) + assert [participation_one] = Participation.for_user(user, %{"limit" => 1}) - assert participation_one.conversation.ap_id == activity_three.data["object"]["context"] + assert participation_one.conversation.ap_id == object3.data["context"] # With last_activity_id assert [participation_one] = - Participation.for_user_with_last_activity_id(user, %{limit: 1}) - |> Repo.preload(:conversation) + Participation.for_user_with_last_activity_id(user, %{"limit" => 1}) assert participation_one.last_activity_id == activity_three.id end diff --git a/test/conversation_test.exs b/test/conversation_test.exs index 239dda04f..763183d6b 100644 --- a/test/conversation_test.exs +++ b/test/conversation_test.exs @@ -22,7 +22,8 @@ test "public posts don't create conversations" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey"}) - context = activity.data["object"]["context"] + object = Pleroma.Object.normalize(activity) + context = object.data["context"] conversation = Conversation.get_for_ap_id(context) @@ -37,7 +38,8 @@ test "it creates or updates a conversation and participations for a given DM" do {:ok, activity} = CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "direct"}) - context = activity.data["object"]["context"] + object = Pleroma.Object.normalize(activity) + context = object.data["context"] conversation = Conversation.get_for_ap_id(context) @@ -58,7 +60,8 @@ test "it creates or updates a conversation and participations for a given DM" do "in_reply_to_status_id" => activity.id }) - context = activity.data["object"]["context"] + object = Pleroma.Object.normalize(activity) + context = object.data["context"] conversation_two = Conversation.get_for_ap_id(context) @@ -81,7 +84,8 @@ test "it creates or updates a conversation and participations for a given DM" do "in_reply_to_status_id" => activity.id }) - context = activity.data["object"]["context"] + object = Pleroma.Object.normalize(activity) + context = object.data["context"] conversation_three = Conversation.get_for_ap_id(context) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 15276ba7b..047270a2a 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -208,11 +208,12 @@ test "adds a context when none is there" do } {:ok, %Activity{} = activity} = ActivityPub.insert(data) + object = Pleroma.Object.normalize(activity) assert is_binary(activity.data["context"]) - assert is_binary(activity.data["object"]["context"]) + assert is_binary(object.data["context"]) assert activity.data["context_id"] - assert activity.data["object"]["context_id"] + assert object.data["context_id"] end test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do From 81d1aa424d65b364ec8f2aee45247e7d95e3f255 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 3 May 2019 13:39:14 +0200 Subject: [PATCH 23/25] Streamer: Stream out Conversations/Participations. --- lib/pleroma/conversation.ex | 11 +++++--- lib/pleroma/web/activity_pub/activity_pub.ex | 22 ++++++++++++++- lib/pleroma/web/streamer.ex | 29 ++++++++++++++++++++ mix.exs | 2 +- mix.lock | 2 +- test/conversation_test.exs | 17 ++++++++++++ test/web/activity_pub/activity_pub_test.exs | 22 +++++++++++++++ 7 files changed, 98 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex index e6a4ccc85..6e26c5fd4 100644 --- a/lib/pleroma/conversation.ex +++ b/lib/pleroma/conversation.ex @@ -63,10 +63,13 @@ def create_or_bump_for(activity) do participation end) - %{ - conversation - | participations: participations - } + {:ok, + %{ + conversation + | participations: participations + }} + else + e -> {:error, e} end end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 28754e864..6c737d0a4 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -142,8 +142,14 @@ def insert(map, local \\ true, fake \\ false) when is_map(map) do end) Notification.create_notifications(activity) - Conversation.create_or_bump_for(activity) + + participations = + activity + |> Conversation.create_or_bump_for() + |> get_participations() + stream_out(activity) + stream_out_participations(participations) {:ok, activity} else %Activity{} = activity -> @@ -166,6 +172,19 @@ def insert(map, local \\ true, fake \\ false) when is_map(map) do end end + defp get_participations({:ok, %{participations: participations}}), do: participations + defp get_participations(_), do: [] + + def stream_out_participations(participations) do + participations = + participations + |> Repo.preload(:user) + + Enum.each(participations, fn participation -> + Pleroma.Web.Streamer.stream("participation", participation) + end) + end + def stream_out(activity) do public = "https://www.w3.org/ns/activitystreams#Public" @@ -197,6 +216,7 @@ def stream_out(activity) do end end else + # TODO: Write test, replace with visibility test if !Enum.member?(activity.data["cc"] || [], public) && !Enum.member?( activity.data["to"], diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 72eaf2084..b8f6663a1 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.Streamer do use GenServer require Logger + alias Pleroma.Conversation.Participation alias Pleroma.Activity alias Pleroma.Notification alias Pleroma.Object @@ -71,6 +72,15 @@ def handle_cast(%{action: :stream, topic: "direct", item: item}, topics) do {:noreply, topics} end + def handle_cast(%{action: :stream, topic: "participation", item: participation}, topics) do + user_topic = "direct:#{participation.user_id}" + Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") + + push_to_socket(topics, user_topic, participation) + + {:noreply, topics} + end + def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do # filter the recipient list if the activity is not public, see #270. recipient_lists = @@ -192,6 +202,19 @@ defp represent_update(%Activity{} = activity) do |> Jason.encode!() end + def represent_conversation(%Participation{} = participation) do + %{ + event: "conversation", + payload: + Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ + participation: participation, + user: participation.user + }) + |> Jason.encode!() + } + |> Jason.encode!() + end + def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do Enum.each(topics[topic] || [], fn socket -> # Get the current user so we have up-to-date blocks etc. @@ -214,6 +237,12 @@ def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = ite end) end + def push_to_socket(topics, topic, %Participation{} = participation) do + Enum.each(topics[topic] || [], fn socket -> + send(socket.transport_pid, {:text, represent_conversation(participation)}) + end) + end + def push_to_socket(topics, topic, %Activity{ data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} }) do diff --git a/mix.exs b/mix.exs index 9ded9931c..ad8f1123a 100644 --- a/mix.exs +++ b/mix.exs @@ -87,7 +87,7 @@ defp deps do {:bbcode, "~> 0.1"}, {:ex_machina, "~> 2.3", only: :test}, {:credo, "~> 0.9.3", only: [:dev, :test]}, - {:mock, "~> 0.3.1", only: :test}, + {:mock, "~> 0.3.3", only: :test}, {:crypt, git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"}, {:cors_plug, "~> 1.5"}, diff --git a/mix.lock b/mix.lock index 08221eadc..bb298a68b 100644 --- a/mix.lock +++ b/mix.lock @@ -43,7 +43,7 @@ "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"}, - "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, + "mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, diff --git a/test/conversation_test.exs b/test/conversation_test.exs index 763183d6b..f3300e7d1 100644 --- a/test/conversation_test.exs +++ b/test/conversation_test.exs @@ -117,4 +117,21 @@ test "it creates or updates a conversation and participations for a given DM" do tridi.id == user_id end) end + + test "create_or_bump_for returns the conversation with participations" do + har = insert(:user) + jafnhar = insert(:user, local: false) + + {:ok, activity} = + CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "direct"}) + + {:ok, conversation} = Conversation.create_or_bump_for(activity) + + assert length(conversation.participations) == 2 + + {:ok, activity} = + CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "public"}) + + assert {:error, _} = Conversation.create_or_bump_for(activity) + end end diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 047270a2a..1e056b7ee 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -22,6 +22,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do :ok end + describe "streaming out participations" do + test "it streams them out" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + + {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity) + + participations = + conversation.participations + |> Repo.preload(:user) + + with_mock Pleroma.Web.Streamer, + stream: fn _, _ -> nil end do + ActivityPub.stream_out_participations(conversation.participations) + + Enum.each(participations, fn participation -> + assert called(Pleroma.Web.Streamer.stream("participation", participation)) + end) + end + end + end + describe "fetching restricted by visibility" do test "it restricts by the appropriate visibility" do user = insert(:user) From a0c755cc4ac3af7ddb7e8fe91fcfc1bae9750e9b Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 3 May 2019 13:40:43 +0200 Subject: [PATCH 24/25] MastodonApi: Bump api level. --- lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index aa3f46482..754946624 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -159,7 +159,7 @@ def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do end end - @mastodon_api_level "2.5.0" + @mastodon_api_level "2.6.5" def masto_instance(conn, _params) do instance = Config.get(:instance) From c42ded13a2caa02f3f5aa00accce69b183546f9e Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 3 May 2019 13:53:17 +0200 Subject: [PATCH 25/25] Credo fixes. --- lib/pleroma/web/streamer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index b8f6663a1..133decfc4 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.Streamer do use GenServer require Logger - alias Pleroma.Conversation.Participation alias Pleroma.Activity + alias Pleroma.Conversation.Participation alias Pleroma.Notification alias Pleroma.Object alias Pleroma.User