From 6e4f52f8a2e510273149acbaf629521d1b4aec2e Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 16 Oct 2019 16:16:39 +0200 Subject: [PATCH 001/114] Introduce new ingestion pipeline structure, implement internal Likes with it. --- lib/pleroma/web/activity_pub/activity_pub.ex | 35 ++++++++++++ lib/pleroma/web/activity_pub/builder.ex | 43 ++++++++++++++ .../web/activity_pub/object_validator.ex | 57 +++++++++++++++++++ lib/pleroma/web/activity_pub/side_effects.ex | 28 +++++++++ lib/pleroma/web/common_api/common_api.ex | 29 ++++++++-- .../controllers/status_controller.ex | 6 +- test/notification_test.exs | 8 +-- test/object_test.exs | 3 +- test/tasks/database_test.exs | 2 +- test/user_test.exs | 4 +- .../activity_pub/activity_validator_test.exs | 21 +++++++ test/web/activity_pub/side_effects_test.exs | 32 +++++++++++ test/web/activity_pub/transmogrifier_test.exs | 2 +- .../activity_pub/views/object_view_test.exs | 2 +- test/web/common_api/common_api_test.exs | 11 ++-- .../notification_controller_test.exs | 2 +- .../controllers/status_controller_test.exs | 16 +++--- .../views/notification_view_test.exs | 2 +- test/web/ostatus/ostatus_controller_test.exs | 4 +- .../controllers/account_controller_test.exs | 16 +++--- test/web/push/impl_test.exs | 2 +- test/web/streamer/streamer_test.exs | 6 +- 22 files changed, 284 insertions(+), 47 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/builder.ex create mode 100644 lib/pleroma/web/activity_pub/object_validator.ex create mode 100644 lib/pleroma/web/activity_pub/side_effects.ex create mode 100644 test/web/activity_pub/activity_validator_test.exs create mode 100644 test/web/activity_pub/side_effects_test.exs diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 364452b5d..f4fc45926 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -18,6 +18,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.SideEffects alias Pleroma.Web.Streamer alias Pleroma.Web.WebFinger alias Pleroma.Workers.BackgroundWorker @@ -123,6 +125,38 @@ def increase_poll_votes_if_vote(%{ def increase_poll_votes_if_vote(_create_data), do: :noop + @spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()} + def common_pipeline(object, meta) do + with {_, {:ok, validated_object, meta}} <- + {:validate_object, ObjectValidator.validate(object, meta)}, + {_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)}, + {_, {:ok, %Activity{} = activity, meta}} <- + {:persist_object, persist(mrfd_object, meta)}, + {_, {:ok, %Activity{} = activity, meta}} <- + {:execute_side_effects, SideEffects.handle(activity, meta)} do + {:ok, activity, meta} + else + e -> {:error, e} + end + end + + # TODO rewrite in with style + @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} + def persist(object, meta) do + local = Keyword.get(meta, :local) + {recipients, _, _} = get_recipients(object) + + {:ok, activity} = + Repo.insert(%Activity{ + data: object, + local: local, + recipients: recipients, + actor: object["actor"] + }) + + {:ok, activity, meta} + end + def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do with nil <- Activity.normalize(map), map <- lazy_put_activity_defaults(map, fake), @@ -130,6 +164,7 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when {_, true} <- {:remote_limit_error, check_remote_limit(map)}, {:ok, map} <- MRF.filter(map), {recipients, _, _} = get_recipients(map), + # ??? {:fake, false, map, recipients} <- {:fake, fake, map, recipients}, :ok <- Containment.contain_child(map), {:ok, map, object} <- insert_full_object(map) do diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex new file mode 100644 index 000000000..1787f1510 --- /dev/null +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -0,0 +1,43 @@ +defmodule Pleroma.Web.ActivityPub.Builder do + @moduledoc """ + This module builds the objects. Meant to be used for creating local objects. + + This module encodes our addressing policies and general shape of our objects. + """ + + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.User + alias Pleroma.Object + + @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()} + def like(actor, object) do + object_actor = User.get_cached_by_ap_id(object.data["actor"]) + + # Address the actor of the object, and our actor's follower collection if the post is public. + to = + if Visibility.is_public?(object) do + [actor.follower_address, object.data["actor"]] + else + [object.data["actor"]] + end + + # CC everyone who's been addressed in the object, except ourself and the object actor's + # follower collection + cc = + (object.data["to"] ++ (object.data["cc"] || [])) + |> List.delete(actor.ap_id) + |> List.delete(object_actor.follower_address) + + {:ok, + %{ + "id" => Utils.generate_activity_id(), + "actor" => actor.ap_id, + "type" => "Like", + "object" => object.data["id"], + "to" => to, + "cc" => cc, + "context" => object.data["context"] + }, []} + end +end diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex new file mode 100644 index 000000000..8ecad0dec --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -0,0 +1,57 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidator do + @moduledoc """ + This module is responsible for validating an object (which can be an activity) + and checking if it is both well formed and also compatible with our view of + the system. + """ + + alias Pleroma.User + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Utils + + def validate_id(object, meta) do + with {_, true} <- {:id_presence, Map.has_key?(object, "id")} do + {:ok, object, meta} + else + e -> {:error, e} + end + end + + def validate_actor(object, meta) do + with {_, %User{}} <- {:actor_validation, User.get_cached_by_ap_id(object["actor"])} do + {:ok, object, meta} + else + e -> {:error, e} + end + end + + def common_validations(object, meta) do + with {_, {:ok, object, meta}} <- {:validate_id, validate_id(object, meta)}, + {_, {:ok, object, meta}} <- {:validate_actor, validate_actor(object, meta)} do + {:ok, object, meta} + else + e -> {:error, e} + end + end + + @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} + def validate(object, meta) + + def validate(%{"type" => "Like"} = object, meta) do + with {:ok, object, meta} <- common_validations(object, meta), + {_, %Object{} = liked_object} <- {:find_liked_object, Object.normalize(object["object"])}, + {_, nil} <- {:existing_like, Utils.get_existing_like(object["actor"], liked_object)} do + {:ok, object, meta} + else + e -> {:error, e} + end + end + + def validate(object, meta) do + common_validations(object, meta) + end +end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex new file mode 100644 index 000000000..6d3e77a62 --- /dev/null +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -0,0 +1,28 @@ +defmodule Pleroma.Web.ActivityPub.SideEffects do + @moduledoc """ + This module looks at an inserted object and executes the side effects that it + implies. For example, a `Like` activity will increase the like count on the + liked object, a `Follow` activity will add the user to the follower + collection, and so on. + """ + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Object + alias Pleroma.Notification + + def handle(object, meta \\ []) + + # Tasks this handles: + # - Add like to object + # - Set up notification + def handle(%{data: %{"type" => "Like"}} = object, meta) do + liked_object = Object.get_by_ap_id(object.data["object"]) + Utils.add_like_to_object(object, liked_object) + Notification.create_notifications(object) + {:ok, object, meta} + end + + # Nothing to do + def handle(object, meta) do + {:ok, object, meta} + end +end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 386408d51..466beb724 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.ThreadMute alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility @@ -17,6 +18,7 @@ defmodule Pleroma.Web.CommonAPI do import Pleroma.Web.CommonAPI.Utils require Pleroma.Constants + require Logger def follow(follower, followed) do timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout]) @@ -98,16 +100,31 @@ def unrepeat(id_or_ap_id, user) do end end - def favorite(id_or_ap_id, user) do - with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - object <- Object.normalize(activity), - nil <- Utils.get_existing_like(user.ap_id, object) do - ActivityPub.like(user, object) + @spec favorite(User.t(), binary()) :: {:ok, Activity.t()} | {:error, any()} + def favorite(%User{} = user, id) do + with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)}, + {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)}, + {_, {:ok, %Activity{} = activity, _meta}} <- + {:common_pipeline, + ActivityPub.common_pipeline(like_object, Keyword.put(meta, :local, true))} do + {:ok, activity} else - _ -> {:error, dgettext("errors", "Could not favorite")} + e -> + Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}") + {:error, dgettext("errors", "Could not favorite")} end end + # def favorite(id_or_ap_id, user) do + # with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), + # object <- Object.normalize(activity), + # nil <- Utils.get_existing_like(user.ap_id, object) do + # ActivityPub.like(user, object) + # else + # _ -> {:error, dgettext("errors", "Could not favorite")} + # end + # end + def unfavorite(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id) do object = Object.normalize(activity) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index e5d016f63..4b4482aa8 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -201,9 +201,9 @@ def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do end @doc "POST /api/v1/statuses/:id/favourite" - def favourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do + def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do + with {:ok, _fav} <- CommonAPI.favorite(user, activity_id), + %Activity{} = activity <- Activity.get_by_id(activity_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end diff --git a/test/notification_test.exs b/test/notification_test.exs index 54c0f9877..940913aa6 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -431,7 +431,7 @@ test "it does not send notification to mentioned users in likes" do "status" => "hey @#{other_user.nickname}!" }) - {:ok, activity_two, _} = CommonAPI.favorite(activity_one.id, third_user) + {:ok, activity_two} = CommonAPI.favorite(third_user, activity_one.id) assert other_user not in Notification.get_notified_from_activity(activity_two) end @@ -461,7 +461,7 @@ test "liking an activity results in 1 notification, then 0 if the activity is de assert Enum.empty?(Notification.for_user(user)) - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + {:ok, _} = CommonAPI.favorite(other_user, activity.id) assert length(Notification.for_user(user)) == 1 @@ -478,7 +478,7 @@ test "liking an activity results in 1 notification, then 0 if the activity is un assert Enum.empty?(Notification.for_user(user)) - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + {:ok, _} = CommonAPI.favorite(other_user, activity.id) assert length(Notification.for_user(user)) == 1 @@ -533,7 +533,7 @@ test "liking an activity which is already deleted does not generate a notificati assert Enum.empty?(Notification.for_user(user)) - {:error, _} = CommonAPI.favorite(activity.id, other_user) + {:error, _} = CommonAPI.favorite(other_user, activity.id) assert Enum.empty?(Notification.for_user(user)) end diff --git a/test/object_test.exs b/test/object_test.exs index dd228c32f..353bc388d 100644 --- a/test/object_test.exs +++ b/test/object_test.exs @@ -182,7 +182,8 @@ test "preserves internal fields on refetch", %{mock_modified: mock_modified} do user = insert(:user) activity = Activity.get_create_by_object_ap_id(object.data["id"]) - {:ok, _activity, object} = CommonAPI.favorite(activity.id, user) + {:ok, activity} = CommonAPI.favorite(user, activity.id) + object = Object.get_by_ap_id(activity.data["object"]) assert object.data["like_count"] == 1 diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs index b63dcac00..c0a313863 100644 --- a/test/tasks/database_test.exs +++ b/test/tasks/database_test.exs @@ -102,7 +102,7 @@ test "it turns OrderedCollection likes into empty arrays" do {:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"}) {:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"}) - CommonAPI.favorite(id, user2) + CommonAPI.favorite(user2, id) likes = %{ "first" => diff --git a/test/user_test.exs b/test/user_test.exs index 019e7b400..49c1eb02a 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1059,8 +1059,8 @@ test "it deletes a user, all follow relationships and all activities", %{user: u object_two = insert(:note, user: follower) activity_two = insert(:note_activity, user: follower, note: object_two) - {:ok, like, _} = CommonAPI.favorite(activity_two.id, user) - {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower) + {:ok, like} = CommonAPI.favorite(user, activity_two.id) + {:ok, like_two} = CommonAPI.favorite(follower, activity.id) {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user) {:ok, job} = User.delete(user) diff --git a/test/web/activity_pub/activity_validator_test.exs b/test/web/activity_pub/activity_validator_test.exs new file mode 100644 index 000000000..cb0895a81 --- /dev/null +++ b/test/web/activity_pub/activity_validator_test.exs @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do + use Pleroma.DataCase + + import Pleroma.Factory + + describe "likes" do + test "it is well formed" do + _required_fields = [ + "id", + "actor", + "object" + ] + + _user = insert(:user) + end + end +end diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs new file mode 100644 index 000000000..e505ab4dd --- /dev/null +++ b/test/web/activity_pub/side_effects_test.exs @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.SideEffectsTest do + use Pleroma.DataCase + alias Pleroma.Object + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.SideEffects + + import Pleroma.Factory + describe "like objects" do + setup do + user = insert(:user) + {:ok, post} = CommonAPI.post(user, %{"status" => "hey"}) + + {:ok, like_data, _meta} = Builder.like(user, post.object) + {:ok, like, _meta} = ActivityPub.persist(like_data, []) + + %{like: like, user: user} + end + + test "add the like to the original object", %{like: like, user: user} do + {:ok, like, _} = SideEffects.handle(like) + object = Object.get_by_ap_id(like.data["object"]) + assert object.data["like_count"] == 1 + assert user.ap_id in object.data["likes"] + end + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 6c35a6f4d..28edc5508 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1187,7 +1187,7 @@ test "it translates ostatus IDs to external URLs" do user = insert(:user) - {:ok, activity, _} = CommonAPI.favorite(referent_activity.id, user) + {:ok, activity} = CommonAPI.favorite(user, referent_activity.id) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) assert modified["object"] == "http://gs.example.org:4040/index.php/notice/29" diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs index 13447dc29..998247c5c 100644 --- a/test/web/activity_pub/views/object_view_test.exs +++ b/test/web/activity_pub/views/object_view_test.exs @@ -41,7 +41,7 @@ test "renders a like activity" do object = Object.normalize(note) user = insert(:user) - {:ok, like_activity, _} = CommonAPI.favorite(note.id, user) + {:ok, like_activity} = CommonAPI.favorite(user, note.id) result = ObjectView.render("object.json", %{object: like_activity}) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 83df44c36..d46a361c5 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -251,9 +251,12 @@ test "favoriting a status" do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + {:ok, post_activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) - {:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user) + {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id) + assert data["type"] == "Like" + assert data["actor"] == user.ap_id + assert data["object"] == post_activity.data["object"] end test "retweeting a status twice returns an error" do @@ -270,8 +273,8 @@ test "favoriting a status twice returns an error" do other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) - {:ok, %Activity{}, _object} = CommonAPI.favorite(activity.id, user) - {:error, _} = CommonAPI.favorite(activity.id, user) + {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id) + {:error, _} = CommonAPI.favorite(user, activity.id) end end diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index e4137e92c..6eadccb8e 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -143,7 +143,7 @@ test "filters notifications using exclude_types", %{conn: conn} do {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) - {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user) + {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id) {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user) {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index 2de2725e0..1414d9fed 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -589,7 +589,7 @@ test "reblogged status for another user", %{conn: conn} do user1 = insert(:user) user2 = insert(:user) user3 = insert(:user) - CommonAPI.favorite(activity.id, user2) + {:ok, _} = CommonAPI.favorite(user2, activity.id) {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id) {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) @@ -695,7 +695,7 @@ test "unfavorites a status and returns it", %{conn: conn} do activity = insert(:note_activity) user = insert(:user) - {:ok, _, _} = CommonAPI.favorite(activity.id, user) + {:ok, _} = CommonAPI.favorite(user, activity.id) conn = conn @@ -1047,7 +1047,7 @@ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{c test "returns users who have favorited the status", %{conn: conn, activity: activity} do other_user = insert(:user) - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + {:ok, _} = CommonAPI.favorite(other_user, activity.id) response = conn @@ -1078,7 +1078,7 @@ test "does not return users who have favorited the status but are blocked", %{ other_user = insert(:user) {:ok, user} = User.block(user, other_user) - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + {:ok, _} = CommonAPI.favorite(other_user, activity.id) response = conn @@ -1091,7 +1091,7 @@ test "does not return users who have favorited the status but are blocked", %{ test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do other_user = insert(:user) - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + {:ok, _} = CommonAPI.favorite(other_user, activity.id) response = conn @@ -1112,7 +1112,7 @@ test "requires authentification for private posts", %{conn: conn, user: user} do "visibility" => "direct" }) - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + {:ok, _} = CommonAPI.favorite(other_user, activity.id) conn |> assign(:user, nil) @@ -1269,7 +1269,7 @@ test "returns the favorites of a user", %{conn: conn} do {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"}) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"}) - {:ok, _, _} = CommonAPI.favorite(activity.id, user) + {:ok, _} = CommonAPI.favorite(user, activity.id) first_conn = conn @@ -1289,7 +1289,7 @@ test "returns the favorites of a user", %{conn: conn} do "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful." }) - {:ok, _, _} = CommonAPI.favorite(second_activity.id, user) + {:ok, _} = CommonAPI.favorite(user, second_activity.id) last_like = status["id"] diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index c9043a69a..d06809268 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -42,7 +42,7 @@ test "Favourite notification" do user = insert(:user) another_user = insert(:user) {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) - {:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user) + {:ok, favorite_activity} = CommonAPI.favorite(another_user, create_activity.id) {:ok, [notification]} = Notification.create_notifications(favorite_activity) create_activity = Activity.get_by_id(create_activity.id) diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index b1af918d8..7aee16e2c 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -271,7 +271,7 @@ test "only gets a notice in AS2 format for Create messages", %{conn: conn} do user = insert(:user) - {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) + {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id) url = "/notice/#{like_activity.id}" assert like_activity.data["type"] == "Like" @@ -298,7 +298,7 @@ test "render html for redirect for html format", %{conn: conn} do user = insert(:user) - {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) + {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id) assert like_activity.data["type"] == "Like" diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs index 3b4665afd..6a6135d02 100644 --- a/test/web/pleroma_api/controllers/account_controller_test.exs +++ b/test/web/pleroma_api/controllers/account_controller_test.exs @@ -165,7 +165,7 @@ test "returns list of statuses favorited by specified user", %{ user: user } do [activity | _] = insert_pair(:note_activity) - CommonAPI.favorite(activity.id, user) + CommonAPI.favorite(user, activity.id) response = conn @@ -184,7 +184,7 @@ test "returns favorites for specified user_id when user is not logged in", %{ user: user } do activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) + CommonAPI.favorite(user, activity.id) response = conn @@ -205,7 +205,7 @@ test "returns favorited DM only when user is logged in and he is one of recipien "visibility" => "direct" }) - CommonAPI.favorite(direct.id, user) + CommonAPI.favorite(user, direct.id) response = conn @@ -236,7 +236,7 @@ test "does not return others' favorited DM when user is not one of recipients", "visibility" => "direct" }) - CommonAPI.favorite(direct.id, user) + CommonAPI.favorite(user, direct.id) response = conn @@ -255,7 +255,7 @@ test "paginates favorites using since_id and max_id", %{ activities = insert_list(10, :note_activity) Enum.each(activities, fn activity -> - CommonAPI.favorite(activity.id, user) + CommonAPI.favorite(user, activity.id) end) third_activity = Enum.at(activities, 2) @@ -283,7 +283,7 @@ test "limits favorites using limit parameter", %{ 7 |> insert_list(:note_activity) |> Enum.each(fn activity -> - CommonAPI.favorite(activity.id, user) + CommonAPI.favorite(user, activity.id) end) response = @@ -321,7 +321,7 @@ test "returns 403 error when user has hidden own favorites", %{ } do user = insert(:user, %{info: %{hide_favorites: true}}) activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) + CommonAPI.favorite(user, activity.id) conn = conn @@ -334,7 +334,7 @@ test "returns 403 error when user has hidden own favorites", %{ test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do user = insert(:user) activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) + CommonAPI.favorite(user, activity.id) conn = conn diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index 2f6ce4bd2..36c69c7c9 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -152,7 +152,7 @@ test "renders body for like activity" do "Lorem ipsum dolor sit amet, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis." }) - {:ok, activity, _} = CommonAPI.favorite(activity.id, user) + {:ok, activity} = CommonAPI.favorite(user, activity.id) object = Object.normalize(activity) assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post" diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index d33eb1e42..b363935a2 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -68,7 +68,7 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl ) {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) - {:ok, notif, _} = CommonAPI.favorite(activity.id, blocked) + {:ok, notif} = CommonAPI.favorite(blocked, activity.id) Streamer.stream("user:notification", notif) Task.await(task) @@ -87,7 +87,7 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) {:ok, activity} = CommonAPI.add_mute(user, activity) - {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) + {:ok, notif} = CommonAPI.favorite(user2, activity.id) Streamer.stream("user:notification", notif) Task.await(task) end @@ -105,7 +105,7 @@ test "it doesn't send notify to the 'user:notification' stream' when a domain is {:ok, user} = User.block_domain(user, "hecking-lewd-place.com") {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) - {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) + {:ok, notif} = CommonAPI.favorite(user2, activity.id) Streamer.stream("user:notification", notif) Task.await(task) From 081e8206ab75e336a76b621508b3999170159ec6 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 16 Oct 2019 17:03:21 +0200 Subject: [PATCH 002/114] Transmogrifier: Use new ingestion pipeline for Likes. --- lib/pleroma/object/containment.ex | 12 +++++++ .../web/activity_pub/object_validator.ex | 5 +-- .../web/activity_pub/transmogrifier.ex | 31 +++++++++++++++---- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index f077a9f32..edbe92381 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -32,6 +32,18 @@ def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) get_actor(%{"actor" => actor}) end + def get_object(%{"object" => id}) when is_binary(id) do + id + end + + def get_object(%{"object" => %{"id" => id}}) when is_binary(id) do + id + end + + def get_object(_) do + nil + end + @doc """ Checks that an imported AP object's actor matches the domain it came from. """ diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 8ecad0dec..0048cc4ec 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -31,7 +31,7 @@ def validate_actor(object, meta) do def common_validations(object, meta) do with {_, {:ok, object, meta}} <- {:validate_id, validate_id(object, meta)}, - {_, {:ok, object, meta}} <- {:validate_actor, validate_actor(object, meta)} do + {_, {:ok, object, meta}} <- {:validate_actor, validate_actor(object, meta)} do {:ok, object, meta} else e -> {:error, e} @@ -43,7 +43,8 @@ def validate(object, meta) def validate(%{"type" => "Like"} = object, meta) do with {:ok, object, meta} <- common_validations(object, meta), - {_, %Object{} = liked_object} <- {:find_liked_object, Object.normalize(object["object"])}, + {_, %Object{} = liked_object} <- + {:find_liked_object, Object.normalize(object["object"])}, {_, nil} <- {:existing_like, Utils.get_existing_like(object["actor"], liked_object)} do {:ok, object, meta} else diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index b56343beb..3e982adcb 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -563,19 +563,38 @@ def handle_incoming( end def handle_incoming( - %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data, + %{"type" => "Like", "object" => _object_id, "actor" => _actor, "id" => _id} = data, _options ) do - with actor <- Containment.get_actor(data), - {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_obj_helper(object_id), - {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do + with data <- Map.take(data, ["type", "object", "actor", "context", "id"]), + actor <- Containment.get_actor(data), + object <- Containment.get_object(data), + data <- data |> Map.put("actor", actor) |> Map.put("object", object), + _user <- User.get_or_fetch_by_ap_id(actor), + object <- Object.normalize(object), + data <- Map.put_new(data, "context", object.data["context"]), + {_, {:ok, activity, _meta}} <- + {:common_pipeline, ActivityPub.common_pipeline(data, local: false)} do {:ok, activity} else - _e -> :error + e -> {:error, e} end end + # def handle_incoming( + # %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data, + # _options + # ) do + # with actor <- Containment.get_actor(data), + # {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), + # {:ok, object} <- get_obj_helper(object_id), + # {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do + # {:ok, activity} + # else + # _e -> :error + # end + # end + def handle_incoming( %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data, _options From 66452f518faa1f079f02006943b0c2cdc830b47f Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 17 Oct 2019 18:36:52 +0200 Subject: [PATCH 003/114] ObjectValidator: Rewrite LikeValidator with Ecto. --- .../web/activity_pub/object_validator.ex | 42 ++-------- .../object_validators/like_validator.ex | 69 ++++++++++++++++ .../object_validators/types/object.ex | 25 ++++++ lib/pleroma/web/common_api/common_api.ex | 10 --- .../activity_pub/activity_validator_test.exs | 21 ----- .../activity_pub/object_validator_test.exs | 80 +++++++++++++++++++ test/web/activity_pub/side_effects_test.exs | 1 + 7 files changed, 183 insertions(+), 65 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/like_validator.ex create mode 100644 lib/pleroma/web/activity_pub/object_validators/types/object.ex delete mode 100644 test/web/activity_pub/activity_validator_test.exs create mode 100644 test/web/activity_pub/object_validator_test.exs diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 0048cc4ec..adcb53c65 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -9,50 +9,24 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do the system. """ - alias Pleroma.User - alias Pleroma.Object - alias Pleroma.Web.ActivityPub.Utils - - def validate_id(object, meta) do - with {_, true} <- {:id_presence, Map.has_key?(object, "id")} do - {:ok, object, meta} - else - e -> {:error, e} - end - end - - def validate_actor(object, meta) do - with {_, %User{}} <- {:actor_validation, User.get_cached_by_ap_id(object["actor"])} do - {:ok, object, meta} - else - e -> {:error, e} - end - end - - def common_validations(object, meta) do - with {_, {:ok, object, meta}} <- {:validate_id, validate_id(object, meta)}, - {_, {:ok, object, meta}} <- {:validate_actor, validate_actor(object, meta)} do - {:ok, object, meta} - else - e -> {:error, e} - end - end + alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) def validate(%{"type" => "Like"} = object, meta) do - with {:ok, object, meta} <- common_validations(object, meta), - {_, %Object{} = liked_object} <- - {:find_liked_object, Object.normalize(object["object"])}, - {_, nil} <- {:existing_like, Utils.get_existing_like(object["actor"], liked_object)} do + with {_, %{valid?: true, changes: object}} <- + {:validate_object, LikeValidator.cast_and_validate(object)} do + object = stringify_keys(object) {:ok, object, meta} else e -> {:error, e} end end - def validate(object, meta) do - common_validations(object, meta) + defp stringify_keys(object) do + object + |> Enum.map(fn {key, val} -> {to_string(key), val} end) + |> Enum.into(%{}) end end diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex new file mode 100644 index 000000000..d5a2f7202 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -0,0 +1,69 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do + use Ecto.Schema + import Ecto.Changeset + + alias Pleroma.Web.ActivityPub.ObjectValidators.Types + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.User + alias Pleroma.Object + + @primary_key false + + embedded_schema do + field(:id, :string, primary_key: true) + field(:type, :string) + field(:object, Types.ObjectID) + field(:actor, Types.ObjectID) + field(:context, :string) + field(:to, {:array, :string}) + field(:cc, {:array, :string}) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + %__MODULE__{} + |> cast(data, [:id, :type, :object, :actor, :context, :to, :cc]) + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Like"]) + |> validate_required([:id, :type, :object, :actor, :context]) + |> validate_change(:actor, &actor_valid?/2) + |> validate_change(:object, &object_valid?/2) + |> validate_existing_like() + end + + def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do + if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do + cng + |> add_error(:actor, "already liked this object") + |> add_error(:object, "already liked by this actor") + else + cng + end + end + + def validate_existing_like(cng), do: cng + + def actor_valid?(field_name, actor) do + if User.get_cached_by_ap_id(actor) do + [] + else + [{field_name, "can't find user"}] + end + end + + def object_valid?(field_name, object) do + if Object.get_cached_by_ap_id(object) do + [] + else + [{field_name, "can't find object"}] + end + end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object.ex b/lib/pleroma/web/activity_pub/object_validators/types/object.ex new file mode 100644 index 000000000..92fc13ba8 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/types/object.ex @@ -0,0 +1,25 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do + use Ecto.Type + + def type, do: :string + + def cast(object) when is_binary(object) do + {:ok, object} + end + + def cast(%{"id" => object}) when is_binary(object) do + {:ok, object} + end + + def cast(_) do + :error + end + + def dump(data) do + {:ok, data} + end + + def load(data) do + {:ok, data} + end +end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 466beb724..e0b22a314 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -115,16 +115,6 @@ def favorite(%User{} = user, id) do end end - # def favorite(id_or_ap_id, user) do - # with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - # object <- Object.normalize(activity), - # nil <- Utils.get_existing_like(user.ap_id, object) do - # ActivityPub.like(user, object) - # else - # _ -> {:error, dgettext("errors", "Could not favorite")} - # end - # end - def unfavorite(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id) do object = Object.normalize(activity) diff --git a/test/web/activity_pub/activity_validator_test.exs b/test/web/activity_pub/activity_validator_test.exs deleted file mode 100644 index cb0895a81..000000000 --- a/test/web/activity_pub/activity_validator_test.exs +++ /dev/null @@ -1,21 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do - use Pleroma.DataCase - - import Pleroma.Factory - - describe "likes" do - test "it is well formed" do - _required_fields = [ - "id", - "actor", - "object" - ] - - _user = insert(:user) - end - end -end diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs new file mode 100644 index 000000000..374a7c0df --- /dev/null +++ b/test/web/activity_pub/object_validator_test.exs @@ -0,0 +1,80 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do + use Pleroma.DataCase + + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + alias Pleroma.Web.ActivityPub.Utils + import Pleroma.Factory + + describe "likes" do + setup do + user = insert(:user) + {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"}) + + valid_like = %{ + "type" => "Like", + "id" => Utils.generate_activity_id(), + "object" => post_activity.data["object"], + "actor" => user.ap_id, + "context" => "a context" + } + + %{valid_like: valid_like, user: user, post_activity: post_activity} + end + + test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do + {:ok, object, _meta} = ObjectValidator.validate(valid_like, []) + + assert "id" in Map.keys(object) + end + + test "is valid for a valid object", %{valid_like: valid_like} do + assert LikeValidator.cast_and_validate(valid_like).valid? + end + + test "it errors when the actor is missing or not known", %{valid_like: valid_like} do + without_actor = Map.delete(valid_like, "actor") + + refute LikeValidator.cast_and_validate(without_actor).valid? + + with_invalid_actor = Map.put(valid_like, "actor", "invalidactor") + + refute LikeValidator.cast_and_validate(with_invalid_actor).valid? + end + + test "it errors when the object is missing or not known", %{valid_like: valid_like} do + without_object = Map.delete(valid_like, "object") + + refute LikeValidator.cast_and_validate(without_object).valid? + + with_invalid_object = Map.put(valid_like, "object", "invalidobject") + + refute LikeValidator.cast_and_validate(with_invalid_object).valid? + end + + test "it errors when the actor has already like the object", %{ + valid_like: valid_like, + user: user, + post_activity: post_activity + } do + _like = CommonAPI.favorite(user, post_activity.id) + + refute LikeValidator.cast_and_validate(valid_like).valid? + end + + test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do + wrapped_like = + valid_like + |> Map.put("actor", %{"id" => valid_like["actor"]}) + |> Map.put("object", %{"id" => valid_like["object"]}) + + validated = LikeValidator.cast_and_validate(wrapped_like) + + assert validated.valid? + + assert {:actor, valid_like["actor"]} in validated.changes + assert {:object, valid_like["object"]} in validated.changes + end + end +end diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index e505ab4dd..9d99e05a0 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do alias Pleroma.Web.ActivityPub.SideEffects import Pleroma.Factory + describe "like objects" do setup do user = insert(:user) From 203d61b95012fd2cb8a8618f6f51a3748c940cc1 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 17 Oct 2019 19:35:31 +0200 Subject: [PATCH 004/114] Transmogrifier: Make proper use of the LikeValidator. --- .../web/activity_pub/object_validator.ex | 10 ++- .../object_validators/like_validator.ex | 2 +- .../web/activity_pub/transmogrifier.ex | 79 +++++++++++++------ .../activity_pub/object_validator_test.exs | 2 + test/web/activity_pub/transmogrifier_test.exs | 4 +- 5 files changed, 68 insertions(+), 29 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index adcb53c65..33e67dbb9 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -10,6 +10,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do """ alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + alias Pleroma.User + alias Pleroma.Object @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) @@ -24,9 +26,15 @@ def validate(%{"type" => "Like"} = object, meta) do end end - defp stringify_keys(object) do + def stringify_keys(object) do object |> Enum.map(fn {key, val} -> {to_string(key), val} end) |> Enum.into(%{}) end + + def fetch_actor_and_object(object) do + User.get_or_fetch_by_ap_id(object["actor"]) + Object.normalize(object["object"]) + :ok + end end diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index d5a2f7202..e6a5aaca8 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -33,7 +33,7 @@ def cast_data(data) do def validate_data(data_cng) do data_cng |> validate_inclusion(:type, ["Like"]) - |> validate_required([:id, :type, :object, :actor, :context]) + |> validate_required([:id, :type, :object, :actor, :context, :to, :cc]) |> validate_change(:actor, &actor_valid?/2) |> validate_change(:object, &object_valid?/2) |> validate_existing_like() diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 3e982adcb..591d7aa94 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -16,6 +16,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Federator alias Pleroma.Workers.TransmogrifierWorker + alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator import Ecto.Query @@ -562,39 +564,21 @@ def handle_incoming( end end - def handle_incoming( - %{"type" => "Like", "object" => _object_id, "actor" => _actor, "id" => _id} = data, - _options - ) do - with data <- Map.take(data, ["type", "object", "actor", "context", "id"]), - actor <- Containment.get_actor(data), - object <- Containment.get_object(data), - data <- data |> Map.put("actor", actor) |> Map.put("object", object), - _user <- User.get_or_fetch_by_ap_id(actor), - object <- Object.normalize(object), - data <- Map.put_new(data, "context", object.data["context"]), + def handle_incoming(%{"type" => "Like"} = data, _options) do + with {_, %{changes: cast_data}} <- {:casting_data, LikeValidator.cast_data(data)}, + cast_data <- ObjectValidator.stringify_keys(cast_data), + :ok <- ObjectValidator.fetch_actor_and_object(cast_data), + {_, {:ok, cast_data}} <- {:maybe_add_context, maybe_add_context_from_object(cast_data)}, + {_, {:ok, cast_data}} <- + {:maybe_add_recipients, maybe_add_recipients_from_object(cast_data)}, {_, {:ok, activity, _meta}} <- - {:common_pipeline, ActivityPub.common_pipeline(data, local: false)} do + {:common_pipeline, ActivityPub.common_pipeline(cast_data, local: false)} do {:ok, activity} else e -> {:error, e} end end - # def handle_incoming( - # %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data, - # _options - # ) do - # with actor <- Containment.get_actor(data), - # {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), - # {:ok, object} <- get_obj_helper(object_id), - # {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do - # {:ok, activity} - # else - # _e -> :error - # end - # end - def handle_incoming( %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data, _options @@ -1156,4 +1140,47 @@ def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do def maybe_fix_user_url(data), do: data def maybe_fix_user_object(data), do: maybe_fix_user_url(data) + + defp maybe_add_context_from_object(%{"context" => context} = data) when is_binary(context), + do: {:ok, data} + + defp maybe_add_context_from_object(%{"object" => object} = data) when is_binary(object) do + if object = Object.normalize(object) do + data = + data + |> Map.put("context", object.data["context"]) + + {:ok, data} + else + {:error, "No context on referenced object"} + end + end + + defp maybe_add_context_from_object(_) do + {:error, "No referenced object"} + end + + defp maybe_add_recipients_from_object(%{"object" => object} = data) do + to = data["to"] || [] + cc = data["cc"] || [] + + if to == [] && cc == [] do + if object = Object.normalize(object) do + data = + data + |> Map.put("to", [object.data["actor"]]) + |> Map.put("cc", cc) + + {:ok, data} + else + {:error, "No actor on referenced object"} + end + else + {:ok, data} + end + end + + defp maybe_add_recipients_from_object(_) do + {:error, "No referenced object"} + end end diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 374a7c0df..2292db6d7 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -13,6 +13,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"}) valid_like = %{ + "to" => [user.ap_id], + "cc" => [], "type" => "Like", "id" => Utils.generate_activity_id(), "object" => post_activity.data["object"], diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 28edc5508..e5d4dcd64 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -333,7 +333,9 @@ test "it works for incoming likes" do |> Poison.decode!() |> Map.put("object", activity.data["object"]) - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data) + + refute Enum.empty?(activity.recipients) assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["type"] == "Like" From 4ec299ea9c1cf45c42e98d7b33f33a72f5e7a9c0 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 18 Oct 2019 12:11:25 +0200 Subject: [PATCH 005/114] CommonAPI tests: Capture logs. --- test/web/common_api/common_api_test.exs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index d46a361c5..63d7ea79f 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -13,6 +13,7 @@ defmodule Pleroma.Web.CommonAPITest do alias Pleroma.Web.CommonAPI import Pleroma.Factory + import ExUnit.CaptureLog require Pleroma.Constants @@ -274,7 +275,9 @@ test "favoriting a status twice returns an error" do {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id) - {:error, _} = CommonAPI.favorite(user, activity.id) + assert capture_log(fn -> + assert {:error, _} = CommonAPI.favorite(user, activity.id) + end) =~ "[error]" end end From 15bbc34c079018f1c988fe9d445bec50e85bbeaf Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 18 Oct 2019 12:44:53 +0200 Subject: [PATCH 006/114] Tests: Capture log. --- test/notification_test.exs | 6 +++++- test/web/common_api/common_api_test.exs | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/test/notification_test.exs b/test/notification_test.exs index 940913aa6..480c9415b 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -14,6 +14,8 @@ defmodule Pleroma.NotificationTest do alias Pleroma.Web.CommonAPI alias Pleroma.Web.Streamer + import ExUnit.CaptureLog + describe "create_notifications" do test "notifies someone when they are directly addressed" do user = insert(:user) @@ -533,7 +535,9 @@ test "liking an activity which is already deleted does not generate a notificati assert Enum.empty?(Notification.for_user(user)) - {:error, _} = CommonAPI.favorite(other_user, activity.id) + assert capture_log(fn -> + {:error, _} = CommonAPI.favorite(other_user, activity.id) + end) =~ "[error]" assert Enum.empty?(Notification.for_user(user)) end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 63d7ea79f..8195b1910 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -275,9 +275,10 @@ test "favoriting a status twice returns an error" do {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id) + assert capture_log(fn -> - assert {:error, _} = CommonAPI.favorite(user, activity.id) - end) =~ "[error]" + assert {:error, _} = CommonAPI.favorite(user, activity.id) + end) =~ "[error]" end end From f1381d68e740daf4c341359a2b5837bc2bd3a051 Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 19 Oct 2019 14:46:14 +0200 Subject: [PATCH 007/114] StatusControllerTest: Capture log. --- .../controllers/status_controller_test.exs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index 1414d9fed..2bbd8a151 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do alias Pleroma.Web.CommonAPI import Pleroma.Factory + import ExUnit.CaptureLog describe "posting statuses" do setup do @@ -681,12 +682,14 @@ test "favs a status and returns it", %{conn: conn} do test "returns 400 error for a wrong id", %{conn: conn} do user = insert(:user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/1/favourite") + assert capture_log(fn -> + conn = + conn + |> assign(:user, user) + |> post("/api/v1/statuses/1/favourite") - assert json_response(conn, 400) == %{"error" => "Could not favorite"} + assert json_response(conn, 400) == %{"error" => "Could not favorite"} + end) =~ "[error]" end end From d4270397dcb2aebde8ed14fd89998ab57aaae545 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 22 Oct 2019 13:42:59 +0300 Subject: [PATCH 008/114] Marker: added unread_count field --- lib/pleroma/marker.ex | 5 +- .../web/mastodon_api/views/marker_view.ex | 1 + .../20191021113356_add_unread_to_marker.exs | 49 +++++++++++++++++++ .../controllers/marker_controller_test.exs | 7 ++- .../mastodon_api/views/marker_view_test.exs | 4 +- 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 priv/repo/migrations/20191021113356_add_unread_to_marker.exs diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index 7f87c86c3..c4d554980 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Marker do field(:last_read_id, :string, default: "") field(:timeline, :string, default: "") field(:lock_version, :integer, default: 0) + field(:unread_count, :integer, default: 0) belongs_to(:user, User, type: FlakeId.Ecto.CompatType) timestamps() @@ -38,7 +39,7 @@ def upsert(%User{} = user, attrs) do Multi.insert(multi, timeline, marker, returning: true, - on_conflict: {:replace, [:last_read_id]}, + on_conflict: {:replace, [:last_read_id, :unread_count]}, conflict_target: [:user_id, :timeline] ) end) @@ -55,7 +56,7 @@ defp get_marker(user, timeline) do @doc false defp changeset(marker, attrs) do marker - |> cast(attrs, [:last_read_id]) + |> cast(attrs, [:last_read_id, :unread_count]) |> validate_required([:user_id, :timeline, :last_read_id]) |> validate_inclusion(:timeline, @timelines) end diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex index 38fbeed5f..1501c2a30 100644 --- a/lib/pleroma/web/mastodon_api/views/marker_view.ex +++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex @@ -10,6 +10,7 @@ def render("markers.json", %{markers: markers}) do Map.put_new(acc, m.timeline, %{ last_read_id: m.last_read_id, version: m.lock_version, + unread_count: m.unread_count, updated_at: NaiveDateTime.to_iso8601(m.updated_at) }) end) diff --git a/priv/repo/migrations/20191021113356_add_unread_to_marker.exs b/priv/repo/migrations/20191021113356_add_unread_to_marker.exs new file mode 100644 index 000000000..32789b7f9 --- /dev/null +++ b/priv/repo/migrations/20191021113356_add_unread_to_marker.exs @@ -0,0 +1,49 @@ +defmodule Pleroma.Repo.Migrations.AddUnreadToMarker do + use Ecto.Migration + import Ecto.Query + alias Pleroma.Repo + alias Pleroma.Notification + + def up do + alter table(:markers) do + add_if_not_exists(:unread_count, :integer, default: 0) + end + + flush() + + update_markers() + end + + def down do + alter table(:markers) do + remove_if_exists(:unread_count, :integer) + end + end + + def update_markers do + from(q in Notification, + select: %{ + timeline: "notifications", + user_id: q.user_id, + unread_count: fragment("COUNT(*) FILTER (WHERE seen = false) as unread_count"), + last_read_id: fragment("(MAX(id) FILTER (WHERE seen = true)::text) as last_read_id ") + }, + group_by: [q.user_id] + ) + |> Repo.all() + |> Enum.reduce(Ecto.Multi.new(), fn attrs, multi -> + marker = + Pleroma.Marker + |> struct(attrs) + |> Ecto.Changeset.change() + + multi + |> Ecto.Multi.insert(attrs[:user_id], marker, + returning: true, + on_conflict: {:replace, [:last_read_id, :unread_count]}, + conflict_target: [:user_id, :timeline] + ) + end) + |> Pleroma.Repo.transaction() + end +end diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 1fcad873d..5e7b4001f 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -15,7 +15,7 @@ test "gets markers with correct scopes", %{conn: conn} do {:ok, %{"notifications" => marker}} = Pleroma.Marker.upsert( user, - %{"notifications" => %{"last_read_id" => "69420"}} + %{"notifications" => %{"last_read_id" => "69420", "unread_count" => 7}} ) response = @@ -28,6 +28,7 @@ test "gets markers with correct scopes", %{conn: conn} do assert response == %{ "notifications" => %{ "last_read_id" => "69420", + "unread_count" => 7, "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), "version" => 0 } @@ -70,7 +71,8 @@ test "creates a marker with correct scopes", %{conn: conn} do "notifications" => %{ "last_read_id" => "69420", "updated_at" => _, - "version" => 0 + "version" => 0, + "unread_count" => 0 } } = response end @@ -98,6 +100,7 @@ test "updates exist marker", %{conn: conn} do assert response == %{ "notifications" => %{ "last_read_id" => "69888", + "unread_count" => 0, "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), "version" => 0 } diff --git a/test/web/mastodon_api/views/marker_view_test.exs b/test/web/mastodon_api/views/marker_view_test.exs index 8a5c89d56..3ce794617 100644 --- a/test/web/mastodon_api/views/marker_view_test.exs +++ b/test/web/mastodon_api/views/marker_view_test.exs @@ -8,17 +8,19 @@ defmodule Pleroma.Web.MastodonAPI.MarkerViewTest do import Pleroma.Factory test "returns markers" do - marker1 = insert(:marker, timeline: "notifications", last_read_id: "17") + marker1 = insert(:marker, timeline: "notifications", last_read_id: "17", unread_count: 5) marker2 = insert(:marker, timeline: "home", last_read_id: "42") assert MarkerView.render("markers.json", %{markers: [marker1, marker2]}) == %{ "home" => %{ last_read_id: "42", + unread_count: 0, updated_at: NaiveDateTime.to_iso8601(marker2.updated_at), version: 0 }, "notifications" => %{ last_read_id: "17", + unread_count: 5, updated_at: NaiveDateTime.to_iso8601(marker1.updated_at), version: 0 } From 9a4afbd2a0486238bfaf4047d91376d32635514a Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 22 Oct 2019 16:13:22 +0300 Subject: [PATCH 009/114] added update unread_count for notifications --- lib/pleroma/marker.ex | 36 ++++++++++++++++++++++++++ lib/pleroma/notification.ex | 50 ++++++++++++++++++++++++------------- test/notification_test.exs | 7 ++++++ 3 files changed, 76 insertions(+), 17 deletions(-) diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index c4d554980..4b8198690 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Marker do alias Ecto.Multi alias Pleroma.Repo alias Pleroma.User + alias __MODULE__ @timelines ["notifications"] @@ -46,6 +47,41 @@ def upsert(%User{} = user, attrs) do |> Repo.transaction() end + @spec multi_set_unread_count(Multi.t(), User.t(), String.t()) :: Multi.t() + def multi_set_unread_count(multi, %User{} = user, "notifications") do + multi + |> Multi.run(:counters, fn _repo, _changes -> + query = + from(q in Pleroma.Notification, + where: q.user_id == ^user.id, + select: %{ + timeline: "notifications", + user_id: ^user.id, + unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END ) as unread_count") + } + ) + + {:ok, Repo.one(query)} + end) + |> Multi.insert( + :marker, + fn %{counters: attrs} -> + Marker + |> struct(attrs) + |> Ecto.Changeset.change() + end, + returning: true, + on_conflict: {:replace, [:last_read_id, :unread_count]}, + conflict_target: [:user_id, :timeline] + ) + end + + def set_unread_count(%User{} = user, timeline) do + Multi.new() + |> multi_set_unread_count(user, timeline) + |> Repo.transaction() + end + defp get_marker(user, timeline) do case Repo.find_resource(get_query(user, timeline)) do {:ok, marker} -> %__MODULE__{marker | user: user} diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index e5da1492b..d339fdf64 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -5,7 +5,9 @@ defmodule Pleroma.Notification do use Ecto.Schema + alias Ecto.Multi alias Pleroma.Activity + alias Pleroma.Marker alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Pagination @@ -151,25 +153,23 @@ def for_user_since(user, date) do |> Repo.all() end - def set_read_up_to(%{id: user_id} = _user, id) do + def set_read_up_to(%{id: user_id} = user, id) do query = from( n in Notification, where: n.user_id == ^user_id, where: n.id <= ^id, where: n.seen == false, - update: [ - set: [ - seen: true, - updated_at: ^NaiveDateTime.utc_now() - ] - ], # Ideally we would preload object and activities here # but Ecto does not support preloads in update_all select: n.id ) - {_, notification_ids} = Repo.update_all(query, []) + {:ok, %{ids: {_, notification_ids}}} = + Multi.new() + |> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()]) + |> Marker.multi_set_unread_count(user, "notifications") + |> Repo.transaction() Notification |> where([n], n.id in ^notification_ids) @@ -186,11 +186,18 @@ def set_read_up_to(%{id: user_id} = _user, id) do |> Repo.all() end + @spec read_one(User.t(), String.t()) :: + {:ok, Notification.t()} | {:error, Ecto.Changeset.t()} | nil def read_one(%User{} = user, notification_id) do with {:ok, %Notification{} = notification} <- get(user, notification_id) do - notification - |> changeset(%{seen: true}) - |> Repo.update() + Multi.new() + |> Multi.update(:update, changeset(notification, %{seen: true})) + |> Marker.multi_set_unread_count(user, "notifications") + |> Repo.transaction() + |> case do + {:ok, %{update: notification}} -> {:ok, notification} + {:error, :update, changeset, _} -> {:error, changeset} + end end end @@ -243,8 +250,11 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = act object = Object.normalize(activity) unless object && object.data["type"] == "Answer" do - users = get_notified_from_activity(activity) - notifications = Enum.map(users, fn user -> create_notification(activity, user) end) + notifications = + activity + |> get_notified_from_activity() + |> Enum.map(&create_notification(activity, &1)) + {:ok, notifications} else {:ok, []} @@ -253,8 +263,11 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = act def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity) when type in ["Like", "Announce", "Follow"] do - users = get_notified_from_activity(activity) - notifications = Enum.map(users, fn user -> create_notification(activity, user) end) + notifications = + activity + |> get_notified_from_activity + |> Enum.map(&create_notification(activity, &1)) + {:ok, notifications} end @@ -263,8 +276,11 @@ def create_notifications(_), do: {:ok, []} # TODO move to sql, too. def create_notification(%Activity{} = activity, %User{} = user) do unless skip?(activity, user) do - notification = %Notification{user_id: user.id, activity: activity} - {:ok, notification} = Repo.insert(notification) + {:ok, %{notification: notification}} = + Multi.new() + |> Multi.insert(:notification, %Notification{user_id: user.id, activity: activity}) + |> Marker.multi_set_unread_count(user, "notifications") + |> Repo.transaction() ["user", "user:notification"] |> Streamer.stream(notification) diff --git a/test/notification_test.exs b/test/notification_test.exs index 96316f8dd..558ac358c 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -310,6 +310,13 @@ test "it sets all notifications as read up to a specified notification ID" do assert n1.seen == true assert n2.seen == true assert n3.seen == false + + assert %Pleroma.Marker{unread_count: 1} = + Pleroma.Repo.get_by( + Pleroma.Marker, + user_id: other_user.id, + timeline: "notifications" + ) end end From 97d5c79aa07bfe836cd676424ce1b5a298c72b60 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 23 Oct 2019 11:52:27 +0200 Subject: [PATCH 010/114] Add Pipeline module, test for federation. --- lib/pleroma/web/activity_pub/activity_pub.ex | 19 +--- lib/pleroma/web/activity_pub/pipeline.ex | 41 +++++++++ .../web/activity_pub/transmogrifier.ex | 7 +- lib/pleroma/web/common_api/common_api.ex | 3 +- test/web/activity_pub/pipeline_test.exs | 87 +++++++++++++++++++ 5 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/pipeline.ex create mode 100644 test/web/activity_pub/pipeline_test.exs diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index f4fc45926..0789ec31c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -18,8 +18,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.ActivityPub.ObjectValidator - alias Pleroma.Web.ActivityPub.SideEffects alias Pleroma.Web.Streamer alias Pleroma.Web.WebFinger alias Pleroma.Workers.BackgroundWorker @@ -125,25 +123,10 @@ def increase_poll_votes_if_vote(%{ def increase_poll_votes_if_vote(_create_data), do: :noop - @spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()} - def common_pipeline(object, meta) do - with {_, {:ok, validated_object, meta}} <- - {:validate_object, ObjectValidator.validate(object, meta)}, - {_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)}, - {_, {:ok, %Activity{} = activity, meta}} <- - {:persist_object, persist(mrfd_object, meta)}, - {_, {:ok, %Activity{} = activity, meta}} <- - {:execute_side_effects, SideEffects.handle(activity, meta)} do - {:ok, activity, meta} - else - e -> {:error, e} - end - end - # TODO rewrite in with style @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} def persist(object, meta) do - local = Keyword.get(meta, :local) + local = Keyword.fetch!(meta, :local) {recipients, _, _} = get_recipients(object) {:ok, activity} = diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex new file mode 100644 index 000000000..cb3571917 --- /dev/null +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Pipeline do + alias Pleroma.Activity + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.MRF + alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.SideEffects + alias Pleroma.Web.Federator + + @spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()} + def common_pipeline(object, meta) do + with {_, {:ok, validated_object, meta}} <- + {:validate_object, ObjectValidator.validate(object, meta)}, + {_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)}, + {_, {:ok, %Activity{} = activity, meta}} <- + {:persist_object, ActivityPub.persist(mrfd_object, meta)}, + {_, {:ok, %Activity{} = activity, meta}} <- + {:execute_side_effects, SideEffects.handle(activity, meta)}, + {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do + {:ok, activity, meta} + else + e -> {:error, e} + end + end + + defp maybe_federate(activity, meta) do + with {:ok, local} <- Keyword.fetch(meta, :local) do + if local do + Federator.publish(activity) + {:ok, :federated} + else + {:ok, :not_federated} + end + else + _e -> {:error, "local not set in meta"} + end + end +end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 591d7aa94..4dd884ce9 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -12,12 +12,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Federator alias Pleroma.Workers.TransmogrifierWorker - alias Pleroma.Web.ActivityPub.ObjectValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator import Ecto.Query @@ -572,7 +573,7 @@ def handle_incoming(%{"type" => "Like"} = data, _options) do {_, {:ok, cast_data}} <- {:maybe_add_recipients, maybe_add_recipients_from_object(cast_data)}, {_, {:ok, activity, _meta}} <- - {:common_pipeline, ActivityPub.common_pipeline(cast_data, local: false)} do + {:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do {:ok, activity} else e -> {:error, e} diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index e0b22a314..535a48dcc 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility @@ -106,7 +107,7 @@ def favorite(%User{} = user, id) do {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)}, {_, {:ok, %Activity{} = activity, _meta}} <- {:common_pipeline, - ActivityPub.common_pipeline(like_object, Keyword.put(meta, :local, true))} do + Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do {:ok, activity} else e -> diff --git a/test/web/activity_pub/pipeline_test.exs b/test/web/activity_pub/pipeline_test.exs new file mode 100644 index 000000000..318d306af --- /dev/null +++ b/test/web/activity_pub/pipeline_test.exs @@ -0,0 +1,87 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.PipelineTest do + use Pleroma.DataCase + + import Mock + import Pleroma.Factory + + describe "common_pipeline/2" do + test "it goes through validation, filtering, persisting, side effects and federation for local activities" do + activity = insert(:note_activity) + meta = [local: true] + + with_mocks([ + {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]}, + { + Pleroma.Web.ActivityPub.MRF, + [], + [filter: fn o -> {:ok, o} end] + }, + { + Pleroma.Web.ActivityPub.ActivityPub, + [], + [persist: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.ActivityPub.SideEffects, + [], + [handle: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.Federator, + [], + [publish: fn _o -> :ok end] + } + ]) do + assert {:ok, ^activity, ^meta} = + Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) + + assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity)) + assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta)) + assert_called(Pleroma.Web.Federator.publish(activity)) + end + end + + test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do + activity = insert(:note_activity) + meta = [local: false] + + with_mocks([ + {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]}, + { + Pleroma.Web.ActivityPub.MRF, + [], + [filter: fn o -> {:ok, o} end] + }, + { + Pleroma.Web.ActivityPub.ActivityPub, + [], + [persist: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.ActivityPub.SideEffects, + [], + [handle: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.Federator, + [], + [] + } + ]) do + assert {:ok, ^activity, ^meta} = + Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) + + assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity)) + assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta)) + end + end + end +end From 1adafa096653c4538e4162a2dffba982ee6c6d8e Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 23 Oct 2019 12:18:05 +0200 Subject: [PATCH 011/114] Credo fixes. --- lib/pleroma/web/activity_pub/builder.ex | 4 ++-- lib/pleroma/web/activity_pub/object_validator.ex | 4 ++-- .../web/activity_pub/object_validators/like_validator.ex | 8 ++++++-- lib/pleroma/web/activity_pub/side_effects.ex | 4 ++-- test/web/activity_pub/object_validator_test.exs | 3 ++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 1787f1510..429a510b8 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -5,10 +5,10 @@ defmodule Pleroma.Web.ActivityPub.Builder do This module encodes our addressing policies and general shape of our objects. """ + alias Pleroma.Object + alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility - alias Pleroma.User - alias Pleroma.Object @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()} def like(actor, object) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 33e67dbb9..27a8dd852 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -9,9 +9,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do the system. """ - alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator - alias Pleroma.User alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} def validate(object, meta) diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index e6a5aaca8..5fa486653 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -1,11 +1,15 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do use Ecto.Schema import Ecto.Changeset + alias Pleroma.Object + alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.Types alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.User - alias Pleroma.Object @primary_key false diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 6d3e77a62..666a4e310 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do liked object, a `Follow` activity will add the user to the follower collection, and so on. """ - alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Object alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Utils def handle(object, meta \\ []) diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index 2292db6d7..3c5c3696e 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -1,10 +1,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do use Pleroma.DataCase - alias Pleroma.Web.CommonAPI alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.CommonAPI + import Pleroma.Factory describe "likes" do From 25077812bfc8a7a94c3fa953b2924003296470c2 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 23 Oct 2019 12:25:20 +0200 Subject: [PATCH 012/114] SideEffectsTest: Fix test. --- test/web/activity_pub/side_effects_test.exs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 9d99e05a0..b34e45a7f 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -4,11 +4,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do use Pleroma.DataCase + alias Pleroma.Object - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.SideEffects + alias Pleroma.Web.CommonAPI import Pleroma.Factory @@ -18,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, post} = CommonAPI.post(user, %{"status" => "hey"}) {:ok, like_data, _meta} = Builder.like(user, post.object) - {:ok, like, _meta} = ActivityPub.persist(like_data, []) + {:ok, like, _meta} = ActivityPub.persist(like_data, [local: true]) %{like: like, user: user} end From aa64b3108ba6aa4294e541e86da323ba1e1a7243 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 23 Oct 2019 11:54:52 +0300 Subject: [PATCH 013/114] fix migrate --- lib/pleroma/marker.ex | 5 +++-- .../20191021113356_add_unread_to_marker.exs | 18 +++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index 4b8198690..098fe3bbd 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -56,8 +56,9 @@ def multi_set_unread_count(multi, %User{} = user, "notifications") do where: q.user_id == ^user.id, select: %{ timeline: "notifications", - user_id: ^user.id, - unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END ) as unread_count") + user_id: type(^user.id, :string), + unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), + last_read_id: type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) } ) diff --git a/priv/repo/migrations/20191021113356_add_unread_to_marker.exs b/priv/repo/migrations/20191021113356_add_unread_to_marker.exs index 32789b7f9..964c7fb98 100644 --- a/priv/repo/migrations/20191021113356_add_unread_to_marker.exs +++ b/priv/repo/migrations/20191021113356_add_unread_to_marker.exs @@ -25,25 +25,21 @@ def update_markers do select: %{ timeline: "notifications", user_id: q.user_id, - unread_count: fragment("COUNT(*) FILTER (WHERE seen = false) as unread_count"), - last_read_id: fragment("(MAX(id) FILTER (WHERE seen = true)::text) as last_read_id ") + unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), + last_read_id: type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) }, group_by: [q.user_id] ) |> Repo.all() - |> Enum.reduce(Ecto.Multi.new(), fn attrs, multi -> - marker = - Pleroma.Marker - |> struct(attrs) - |> Ecto.Changeset.change() - - multi - |> Ecto.Multi.insert(attrs[:user_id], marker, + |> Enum.each(fn attrs -> + Pleroma.Marker + |> struct(attrs) + |> Ecto.Changeset.change() + |> Pleroma.Repo.insert( returning: true, on_conflict: {:replace, [:last_read_id, :unread_count]}, conflict_target: [:user_id, :timeline] ) end) - |> Pleroma.Repo.transaction() end end From d3fb9e02cc0ce7dc462e587e639e117aaef5fbc5 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 23 Oct 2019 22:48:04 +0300 Subject: [PATCH 014/114] add tests --- lib/pleroma/marker.ex | 9 +++------ .../20191021113356_add_unread_to_marker.exs | 3 ++- test/marker_test.exs | 15 +++++++++++++++ test/notification_test.exs | 3 +++ 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index 098fe3bbd..5f6a47f38 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -58,7 +58,8 @@ def multi_set_unread_count(multi, %User{} = user, "notifications") do timeline: "notifications", user_id: type(^user.id, :string), unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), - last_read_id: type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) + last_read_id: + type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) } ) @@ -77,11 +78,7 @@ def multi_set_unread_count(multi, %User{} = user, "notifications") do ) end - def set_unread_count(%User{} = user, timeline) do - Multi.new() - |> multi_set_unread_count(user, timeline) - |> Repo.transaction() - end + def multi_set_unread_count(multi, _, _), do: multi defp get_marker(user, timeline) do case Repo.find_resource(get_query(user, timeline)) do diff --git a/priv/repo/migrations/20191021113356_add_unread_to_marker.exs b/priv/repo/migrations/20191021113356_add_unread_to_marker.exs index 964c7fb98..c15e2ff13 100644 --- a/priv/repo/migrations/20191021113356_add_unread_to_marker.exs +++ b/priv/repo/migrations/20191021113356_add_unread_to_marker.exs @@ -26,7 +26,8 @@ def update_markers do timeline: "notifications", user_id: q.user_id, unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), - last_read_id: type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) + last_read_id: + type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) }, group_by: [q.user_id] ) diff --git a/test/marker_test.exs b/test/marker_test.exs index 04bd67fe6..1900ed08b 100644 --- a/test/marker_test.exs +++ b/test/marker_test.exs @@ -8,6 +8,21 @@ defmodule Pleroma.MarkerTest do import Pleroma.Factory + describe "multi_set_unread_count/3" do + test "returns multi" do + user = insert(:user) + + assert %Ecto.Multi{ + operations: [marker: {:run, _}, counters: {:run, _}] + } = + Marker.multi_set_unread_count( + Ecto.Multi.new(), + user, + "notifications" + ) + end + end + describe "get_markers/2" do test "returns user markers" do user = insert(:user) diff --git a/test/notification_test.exs b/test/notification_test.exs index 558ac358c..1e8a9ca98 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -31,6 +31,9 @@ test "notifies someone when they are directly addressed" do assert notified_ids == [other_user.id, third_user.id] assert notification.activity_id == activity.id assert other_notification.activity_id == activity.id + + assert [%Pleroma.Marker{unread_count: 2}] = + Pleroma.Marker.get_markers(other_user, ["notifications"]) end test "it creates a notification for subscribed users" do From 922e3d082c38ccd108710e21d4bda8e65b551f9c Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 24 Oct 2019 09:50:41 +0300 Subject: [PATCH 015/114] add test --- lib/pleroma/marker.ex | 14 +------------- lib/pleroma/notification.ex | 14 ++++++++++++++ test/marker_test.exs | 6 ++++++ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index 5f6a47f38..a7ea542dd 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -51,19 +51,7 @@ def upsert(%User{} = user, attrs) do def multi_set_unread_count(multi, %User{} = user, "notifications") do multi |> Multi.run(:counters, fn _repo, _changes -> - query = - from(q in Pleroma.Notification, - where: q.user_id == ^user.id, - select: %{ - timeline: "notifications", - user_id: type(^user.id, :string), - unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), - last_read_id: - type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) - } - ) - - {:ok, Repo.one(query)} + {:ok, Repo.one(Pleroma.Notification.notifications_info_query(user))} end) |> Multi.insert( :marker, diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index af56cc667..373f9b06a 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -36,6 +36,20 @@ def changeset(%Notification{} = notification, attrs) do |> cast(attrs, [:seen]) end + @spec notifications_info_query(User.t()) :: Ecto.Queryable.t() + def notifications_info_query(user) do + from(q in Pleroma.Notification, + where: q.user_id == ^user.id, + select: %{ + timeline: "notifications", + user_id: type(^user.id, :string), + unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), + last_read_id: + type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) + } + ) + end + def for_user_query(user, opts \\ []) do Notification |> where(user_id: ^user.id) diff --git a/test/marker_test.exs b/test/marker_test.exs index 1900ed08b..5d03db48e 100644 --- a/test/marker_test.exs +++ b/test/marker_test.exs @@ -21,6 +21,12 @@ test "returns multi" do "notifications" ) end + + test "return empty multi" do + user = insert(:user) + multi = Ecto.Multi.new() + assert Marker.multi_set_unread_count(multi, user, "home") == multi + end end describe "get_markers/2" do From 1b82eb6d4102bc2d7acec0a905e7714c95eadc94 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 30 Oct 2019 23:22:38 +0300 Subject: [PATCH 016/114] move sql (update_markers) from migrate to mix task --- lib/mix/tasks/pleroma/marker.ex | 36 +++++++++++++++ .../20191021113356_add_unread_to_marker.exs | 46 ------------------- .../20191030202008_add_unread_to_marker.exs | 18 ++++++++ 3 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 lib/mix/tasks/pleroma/marker.ex delete mode 100644 priv/repo/migrations/20191021113356_add_unread_to_marker.exs create mode 100644 priv/repo/migrations/20191030202008_add_unread_to_marker.exs diff --git a/lib/mix/tasks/pleroma/marker.ex b/lib/mix/tasks/pleroma/marker.ex new file mode 100644 index 000000000..1d5be11de --- /dev/null +++ b/lib/mix/tasks/pleroma/marker.ex @@ -0,0 +1,36 @@ +defmodule Mix.Tasks.Pleroma.Marker do + use Mix.Task + import Mix.Pleroma + + import Ecto.Query + alias Pleroma.Repo + alias Pleroma.Notification + + def run(["update_markers"]) do + start_pleroma() + + from(q in Notification, + select: %{ + timeline: "notifications", + user_id: q.user_id, + unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), + last_read_id: + type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) + }, + group_by: [q.user_id] + ) + |> Repo.all() + |> Enum.each(fn attrs -> + Pleroma.Marker + |> struct(attrs) + |> Ecto.Changeset.change() + |> Pleroma.Repo.insert( + returning: true, + on_conflict: {:replace, [:last_read_id, :unread_count]}, + conflict_target: [:user_id, :timeline] + ) + end) + + shell_info("Done") + end +end diff --git a/priv/repo/migrations/20191021113356_add_unread_to_marker.exs b/priv/repo/migrations/20191021113356_add_unread_to_marker.exs deleted file mode 100644 index c15e2ff13..000000000 --- a/priv/repo/migrations/20191021113356_add_unread_to_marker.exs +++ /dev/null @@ -1,46 +0,0 @@ -defmodule Pleroma.Repo.Migrations.AddUnreadToMarker do - use Ecto.Migration - import Ecto.Query - alias Pleroma.Repo - alias Pleroma.Notification - - def up do - alter table(:markers) do - add_if_not_exists(:unread_count, :integer, default: 0) - end - - flush() - - update_markers() - end - - def down do - alter table(:markers) do - remove_if_exists(:unread_count, :integer) - end - end - - def update_markers do - from(q in Notification, - select: %{ - timeline: "notifications", - user_id: q.user_id, - unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), - last_read_id: - type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) - }, - group_by: [q.user_id] - ) - |> Repo.all() - |> Enum.each(fn attrs -> - Pleroma.Marker - |> struct(attrs) - |> Ecto.Changeset.change() - |> Pleroma.Repo.insert( - returning: true, - on_conflict: {:replace, [:last_read_id, :unread_count]}, - conflict_target: [:user_id, :timeline] - ) - end) - end -end diff --git a/priv/repo/migrations/20191030202008_add_unread_to_marker.exs b/priv/repo/migrations/20191030202008_add_unread_to_marker.exs new file mode 100644 index 000000000..f81339c9f --- /dev/null +++ b/priv/repo/migrations/20191030202008_add_unread_to_marker.exs @@ -0,0 +1,18 @@ +defmodule Pleroma.Repo.Migrations.AddUnreadToMarker do + use Ecto.Migration + import Ecto.Query + alias Pleroma.Repo + alias Pleroma.Notification + + def up do + alter table(:markers) do + add_if_not_exists(:unread_count, :integer, default: 0) + end + end + + def down do + alter table(:markers) do + remove_if_exists(:unread_count, :integer) + end + end +end From 209319c8d289564653f73cbf15fb6449d91cf3ca Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 30 Oct 2019 23:49:05 +0300 Subject: [PATCH 017/114] update marker api --- .../web/mastodon_api/views/marker_view.ex | 6 ++++-- .../controllers/marker_controller_test.exs | 20 +++++++++---------- .../mastodon_api/views/marker_view_test.exs | 8 ++++---- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex index 1501c2a30..81545cff0 100644 --- a/lib/pleroma/web/mastodon_api/views/marker_view.ex +++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex @@ -10,8 +10,10 @@ def render("markers.json", %{markers: markers}) do Map.put_new(acc, m.timeline, %{ last_read_id: m.last_read_id, version: m.lock_version, - unread_count: m.unread_count, - updated_at: NaiveDateTime.to_iso8601(m.updated_at) + updated_at: NaiveDateTime.to_iso8601(m.updated_at), + pleroma: %{ + unread_count: m.unread_count + } }) end) end diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 5e7b4001f..e0aacccb4 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -26,13 +26,13 @@ test "gets markers with correct scopes", %{conn: conn} do |> json_response(200) assert response == %{ - "notifications" => %{ - "last_read_id" => "69420", - "unread_count" => 7, - "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), - "version" => 0 - } - } + "notifications" => %{ + "last_read_id" => "69420", + "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), + "version" => 0, + "pleroma" => %{ "unread_count" => 7 } + } + } end test "gets markers with missed scopes", %{conn: conn} do @@ -72,7 +72,7 @@ test "creates a marker with correct scopes", %{conn: conn} do "last_read_id" => "69420", "updated_at" => _, "version" => 0, - "unread_count" => 0 + "pleroma" => %{ "unread_count" => 0 } } } = response end @@ -100,9 +100,9 @@ test "updates exist marker", %{conn: conn} do assert response == %{ "notifications" => %{ "last_read_id" => "69888", - "unread_count" => 0, "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), - "version" => 0 + "version" => 0, + "pleroma" => %{ "unread_count" => 0 } } } end diff --git a/test/web/mastodon_api/views/marker_view_test.exs b/test/web/mastodon_api/views/marker_view_test.exs index 3ce794617..f172e5023 100644 --- a/test/web/mastodon_api/views/marker_view_test.exs +++ b/test/web/mastodon_api/views/marker_view_test.exs @@ -14,15 +14,15 @@ test "returns markers" do assert MarkerView.render("markers.json", %{markers: [marker1, marker2]}) == %{ "home" => %{ last_read_id: "42", - unread_count: 0, updated_at: NaiveDateTime.to_iso8601(marker2.updated_at), - version: 0 + version: 0, + pleroma: %{unread_count: 0} }, "notifications" => %{ last_read_id: "17", - unread_count: 5, updated_at: NaiveDateTime.to_iso8601(marker1.updated_at), - version: 0 + version: 0, + pleroma: %{unread_count: 5} } } end From 58da7f66202f3f2e90d4954649c3e5a0e3a7dc32 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 31 Oct 2019 17:34:17 +0300 Subject: [PATCH 018/114] updated docs\changelog --- CHANGELOG.md | 1 + docs/API/differences_in_mastoapi_responses.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51e5424c6..c14c8c0e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`) - Mastodon API: `pleroma.thread_muted` to the Status entity - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message +- Mastodon API: Add `pleroma.unread_count` to the Marker entity ### Added diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index aca0f5e0e..d18b976b6 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -155,3 +155,9 @@ Has theses additionnal parameters (which are the same as in Pleroma-API): * `captcha_solution`: optional, contains provider-specific captcha solution, * `captcha_token`: optional, contains provider-specific captcha token * `token`: invite token required when the registerations aren't public. + +## Markers + +Has these additional fields under the `pleroma` object: + +- `unread_count`: contains number unread notifications From 1b3a942a84b8b612e07e3bf34801137741926911 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 31 Oct 2019 17:36:59 +0300 Subject: [PATCH 019/114] fix format --- lib/mix/tasks/pleroma/marker.ex | 10 +++++----- .../controllers/marker_controller_test.exs | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/mix/tasks/pleroma/marker.ex b/lib/mix/tasks/pleroma/marker.ex index 1d5be11de..bebef0d6a 100644 --- a/lib/mix/tasks/pleroma/marker.ex +++ b/lib/mix/tasks/pleroma/marker.ex @@ -1,10 +1,10 @@ defmodule Mix.Tasks.Pleroma.Marker do use Mix.Task import Mix.Pleroma - import Ecto.Query - alias Pleroma.Repo + alias Pleroma.Notification + alias Pleroma.Repo def run(["update_markers"]) do start_pleroma() @@ -15,7 +15,7 @@ def run(["update_markers"]) do user_id: q.user_id, unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), last_read_id: - type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) + type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) }, group_by: [q.user_id] ) @@ -26,8 +26,8 @@ def run(["update_markers"]) do |> Ecto.Changeset.change() |> Pleroma.Repo.insert( returning: true, - on_conflict: {:replace, [:last_read_id, :unread_count]}, - conflict_target: [:user_id, :timeline] + on_conflict: {:replace, [:last_read_id, :unread_count]}, + conflict_target: [:user_id, :timeline] ) end) diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index e0aacccb4..8bcfcb7e1 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -26,13 +26,13 @@ test "gets markers with correct scopes", %{conn: conn} do |> json_response(200) assert response == %{ - "notifications" => %{ - "last_read_id" => "69420", - "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), - "version" => 0, - "pleroma" => %{ "unread_count" => 7 } - } - } + "notifications" => %{ + "last_read_id" => "69420", + "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), + "version" => 0, + "pleroma" => %{"unread_count" => 7} + } + } end test "gets markers with missed scopes", %{conn: conn} do @@ -72,7 +72,7 @@ test "creates a marker with correct scopes", %{conn: conn} do "last_read_id" => "69420", "updated_at" => _, "version" => 0, - "pleroma" => %{ "unread_count" => 0 } + "pleroma" => %{"unread_count" => 0} } } = response end @@ -102,7 +102,7 @@ test "updates exist marker", %{conn: conn} do "last_read_id" => "69888", "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), "version" => 0, - "pleroma" => %{ "unread_count" => 0 } + "pleroma" => %{"unread_count" => 0} } } end From 57995fa8cf26c9d5cd31969b59dbafb9f8c8fdc7 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Sat, 2 Nov 2019 21:19:01 +0300 Subject: [PATCH 020/114] fix migrate update migrate --- lib/mix/tasks/pleroma/marker.ex | 36 ------------------- .../20191030202008_add_unread_to_marker.exs | 32 ++++++++++++++++- 2 files changed, 31 insertions(+), 37 deletions(-) delete mode 100644 lib/mix/tasks/pleroma/marker.ex diff --git a/lib/mix/tasks/pleroma/marker.ex b/lib/mix/tasks/pleroma/marker.ex deleted file mode 100644 index bebef0d6a..000000000 --- a/lib/mix/tasks/pleroma/marker.ex +++ /dev/null @@ -1,36 +0,0 @@ -defmodule Mix.Tasks.Pleroma.Marker do - use Mix.Task - import Mix.Pleroma - import Ecto.Query - - alias Pleroma.Notification - alias Pleroma.Repo - - def run(["update_markers"]) do - start_pleroma() - - from(q in Notification, - select: %{ - timeline: "notifications", - user_id: q.user_id, - unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), - last_read_id: - type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) - }, - group_by: [q.user_id] - ) - |> Repo.all() - |> Enum.each(fn attrs -> - Pleroma.Marker - |> struct(attrs) - |> Ecto.Changeset.change() - |> Pleroma.Repo.insert( - returning: true, - on_conflict: {:replace, [:last_read_id, :unread_count]}, - conflict_target: [:user_id, :timeline] - ) - end) - - shell_info("Done") - end -end diff --git a/priv/repo/migrations/20191030202008_add_unread_to_marker.exs b/priv/repo/migrations/20191030202008_add_unread_to_marker.exs index f81339c9f..2b3abc682 100644 --- a/priv/repo/migrations/20191030202008_add_unread_to_marker.exs +++ b/priv/repo/migrations/20191030202008_add_unread_to_marker.exs @@ -2,12 +2,15 @@ defmodule Pleroma.Repo.Migrations.AddUnreadToMarker do use Ecto.Migration import Ecto.Query alias Pleroma.Repo - alias Pleroma.Notification def up do alter table(:markers) do add_if_not_exists(:unread_count, :integer, default: 0) end + + flush() + + update_markers() end def down do @@ -15,4 +18,31 @@ def down do remove_if_exists(:unread_count, :integer) end end + + def update_markers do + now = NaiveDateTime.utc_now() + + markers_attrs = + from(q in "notifications", + select: %{ + timeline: "notifications", + user_id: q.user_id, + unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), + last_read_id: + type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) + }, + group_by: [q.user_id] + ) + |> Repo.all() + |> Enum.map(fn attrs -> + attrs + |> Map.put_new(:inserted_at, now) + |> Map.put_new(:updated_at, now) + end) + + Repo.insert_all("markers", markers_attrs, + on_conflict: {:replace, [:last_read_id, :unread_count]}, + conflict_target: [:user_id, :timeline] + ) + end end From 3d1b445cbf001f76af614441c241dcc299e76af7 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 Nov 2019 15:02:09 +0100 Subject: [PATCH 021/114] Object Validators: Extract common validations. --- .../web/activity_pub/object_validator.ex | 7 ++-- .../object_validators/common_validations.ex | 32 +++++++++++++++++++ .../object_validators/like_validator.ex | 26 +++------------ .../web/activity_pub/transmogrifier.ex | 7 ++-- test/web/activity_pub/side_effects_test.exs | 2 +- 5 files changed, 47 insertions(+), 27 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/common_validations.ex diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 27a8dd852..539be1143 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -17,9 +17,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do def validate(object, meta) def validate(%{"type" => "Like"} = object, meta) do - with {_, %{valid?: true, changes: object}} <- - {:validate_object, LikeValidator.cast_and_validate(object)} do - object = stringify_keys(object) + with {_, {:ok, object}} <- + {:validate_object, + object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert)} do + object = stringify_keys(object |> Map.from_struct()) {:ok, object, meta} else e -> {:error, e} diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex new file mode 100644 index 000000000..db0e2072d --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do + import Ecto.Changeset + + alias Pleroma.Object + alias Pleroma.User + + def validate_actor_presence(cng, field_name \\ :actor) do + cng + |> validate_change(field_name, fn field_name, actor -> + if User.get_cached_by_ap_id(actor) do + [] + else + [{field_name, "can't find user"}] + end + end) + end + + def validate_object_presence(cng, field_name \\ :object) do + cng + |> validate_change(field_name, fn field_name, actor -> + if Object.get_cached_by_ap_id(actor) do + [] + else + [{field_name, "can't find user"}] + end + end) + end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index 5fa486653..ccbc7d071 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -4,13 +4,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do use Ecto.Schema - import Ecto.Changeset - alias Pleroma.Object - alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.Types alias Pleroma.Web.ActivityPub.Utils + import Ecto.Changeset + import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + @primary_key false embedded_schema do @@ -38,8 +38,8 @@ def validate_data(data_cng) do data_cng |> validate_inclusion(:type, ["Like"]) |> validate_required([:id, :type, :object, :actor, :context, :to, :cc]) - |> validate_change(:actor, &actor_valid?/2) - |> validate_change(:object, &object_valid?/2) + |> validate_actor_presence() + |> validate_object_presence() |> validate_existing_like() end @@ -54,20 +54,4 @@ def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do end def validate_existing_like(cng), do: cng - - def actor_valid?(field_name, actor) do - if User.get_cached_by_ap_id(actor) do - [] - else - [{field_name, "can't find user"}] - end - end - - def object_valid?(field_name, object) do - if Object.get_cached_by_ap_id(object) do - [] - else - [{field_name, "can't find object"}] - end - end end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 4dd884ce9..9a0c37e13 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -566,8 +566,11 @@ def handle_incoming( end def handle_incoming(%{"type" => "Like"} = data, _options) do - with {_, %{changes: cast_data}} <- {:casting_data, LikeValidator.cast_data(data)}, - cast_data <- ObjectValidator.stringify_keys(cast_data), + with {_, {:ok, cast_data_sym}} <- + {:casting_data, + data |> LikeValidator.cast_data() |> Ecto.Changeset.apply_action(:insert)}, + {_, cast_data} <- + {:stringify_keys, ObjectValidator.stringify_keys(cast_data_sym |> Map.from_struct())}, :ok <- ObjectValidator.fetch_actor_and_object(cast_data), {_, {:ok, cast_data}} <- {:maybe_add_context, maybe_add_context_from_object(cast_data)}, {_, {:ok, cast_data}} <- diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index b34e45a7f..ef91954ae 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, post} = CommonAPI.post(user, %{"status" => "hey"}) {:ok, like_data, _meta} = Builder.like(user, post.object) - {:ok, like, _meta} = ActivityPub.persist(like_data, [local: true]) + {:ok, like, _meta} = ActivityPub.persist(like_data, local: true) %{like: like, user: user} end From faced6236b9e2ce9675cf743068f16098b744562 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 Nov 2019 15:02:31 +0100 Subject: [PATCH 022/114] NoteValidator: Add very basic validator for Note objects. --- .../object_validators/note_validator.ex | 64 +++++++++++++++++++ .../object_validators/note_validator_test.exs | 35 ++++++++++ 2 files changed, 99 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/object_validators/note_validator.ex create mode 100644 test/web/activity_pub/object_validators/note_validator_test.exs diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex new file mode 100644 index 000000000..c660f30f0 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do + use Ecto.Schema + + alias Pleroma.Web.ActivityPub.ObjectValidators.Types + + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field(:id, :string, primary_key: true) + field(:to, {:array, :string}, default: []) + field(:cc, {:array, :string}, default: []) + field(:bto, {:array, :string}, default: []) + field(:bcc, {:array, :string}, default: []) + # TODO: Write type + field(:tag, {:array, :map}, default: []) + field(:type, :string) + field(:content, :string) + field(:context, :string) + field(:actor, Types.ObjectID) + field(:attributedTo, Types.ObjectID) + field(:summary, :string) + # TODO: Write type + field(:published, :string) + # TODO: Write type + field(:emoji, :map, default: %{}) + field(:sensitive, :boolean, default: false) + # TODO: Write type + field(:attachment, {:array, :map}, default: []) + field(:replies_count, :integer, default: 0) + field(:like_count, :integer, default: 0) + field(:announcement_count, :integer, default: 0) + field(:inRepyTo, :string) + + field(:likes, {:array, :string}, default: []) + field(:announcements, {:array, :string}, default: []) + + # see if needed + field(:conversation, :string) + field(:context_id, :string) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + %__MODULE__{} + |> cast(data, __schema__(:fields)) + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Note"]) + |> validate_required([:id, :actor, :to, :cc, :type, :content, :context]) + end +end diff --git a/test/web/activity_pub/object_validators/note_validator_test.exs b/test/web/activity_pub/object_validators/note_validator_test.exs new file mode 100644 index 000000000..2bcd75e25 --- /dev/null +++ b/test/web/activity_pub/object_validators/note_validator_test.exs @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do + use Pleroma.DataCase + + alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator + alias Pleroma.Web.ActivityPub.Utils + + import Pleroma.Factory + + describe "Notes" do + setup do + user = insert(:user) + + note = %{ + "id" => Utils.generate_activity_id(), + "type" => "Note", + "actor" => user.ap_id, + "to" => [user.follower_address], + "cc" => [], + "content" => "Hellow this is content.", + "context" => "xxx", + "summary" => "a post" + } + + %{user: user, note: note} + end + + test "a basic note validates", %{note: note} do + %{valid?: true} = NoteValidator.cast_and_validate(note) + end + end +end From ddbfc995ac40db9bd1da137b03e5acf6d050ddc5 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 11 Nov 2019 17:06:41 +0300 Subject: [PATCH 023/114] clean sql query --- lib/pleroma/marker.ex | 2 +- lib/pleroma/notification.ex | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index a7ea542dd..d5ca27bf2 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -56,7 +56,7 @@ def multi_set_unread_count(multi, %User{} = user, "notifications") do |> Multi.insert( :marker, fn %{counters: attrs} -> - Marker + %Marker{timeline: "notifications", user_id: user.id} |> struct(attrs) |> Ecto.Changeset.change() end, diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 373f9b06a..158903c4b 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -41,8 +41,6 @@ def notifications_info_query(user) do from(q in Pleroma.Notification, where: q.user_id == ^user.id, select: %{ - timeline: "notifications", - user_id: type(^user.id, :string), unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), last_read_id: type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) From b5b62f42b2864dc8b95c8ba7d650321ebcc332ad Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 12 Nov 2019 15:59:34 +0300 Subject: [PATCH 024/114] update Marker.multi_set_unread_count --- lib/pleroma/marker.ex | 7 ++++++- lib/pleroma/notification.ex | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index d5ca27bf2..a32546094 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Marker do import Ecto.Query alias Ecto.Multi + alias Pleroma.Notification alias Pleroma.Repo alias Pleroma.User alias __MODULE__ @@ -51,7 +52,11 @@ def upsert(%User{} = user, attrs) do def multi_set_unread_count(multi, %User{} = user, "notifications") do multi |> Multi.run(:counters, fn _repo, _changes -> - {:ok, Repo.one(Pleroma.Notification.notifications_info_query(user))} + {:ok, + %{ + unread_count: Repo.aggregate(Notification.unread_count_query(user), :count, :id), + last_read_id: Repo.one(Notification.last_read_query(user)) + }} end) |> Multi.insert( :marker, diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 158903c4b..1cc6a4735 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -36,15 +36,22 @@ def changeset(%Notification{} = notification, attrs) do |> cast(attrs, [:seen]) end - @spec notifications_info_query(User.t()) :: Ecto.Queryable.t() - def notifications_info_query(user) do + @spec unread_count_query(User.t()) :: Ecto.Queryable.t() + def unread_count_query(user) do from(q in Pleroma.Notification, where: q.user_id == ^user.id, - select: %{ - unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), - last_read_id: - type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) - } + where: q.seen == false + ) + end + + @spec last_read_query(User.t()) :: Ecto.Queryable.t() + def last_read_query(user) do + from(q in Pleroma.Notification, + where: q.user_id == ^user.id, + where: q.seen == true, + select: type(q.id, :string), + limit: 1, + order_by: [desc: :id] ) end From b9041c209787dc279d4dc5194d65dff73684cdb9 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 15 Nov 2019 22:10:41 +0300 Subject: [PATCH 025/114] added recount unread notifications to markers --- lib/pleroma/marker.ex | 29 +++++++++++++++++-- .../controllers/marker_controller.ex | 8 ++++- test/marker_test.exs | 14 +++++++++ .../controllers/marker_controller_test.exs | 3 +- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index a32546094..2d217a0b7 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Marker do alias __MODULE__ @timelines ["notifications"] + @type t :: %__MODULE__{} schema "markers" do field(:last_read_id, :string, default: "") @@ -26,8 +27,18 @@ defmodule Pleroma.Marker do timestamps() end - def get_markers(user, timelines \\ []) do - Repo.all(get_query(user, timelines)) + @doc """ + Gets markers by user and timeline. + + opts: + `recount_unread` - run force recount unread notifications for `true` value + """ + @spec get_markers(User.t(), list(String), map()) :: list(t()) + def get_markers(user, timelines \\ [], opts \\ %{}) do + user + |> get_query(timelines) + |> recount_unread_notifications(opts[:recount_unread]) + |> Repo.all() end def upsert(%User{} = user, attrs) do @@ -99,4 +110,18 @@ defp get_query(user, timelines) do |> by_user_id(user.id) |> by_timeline(timelines) end + + defp recount_unread_notifications(query, true) do + from( + q in query, + left_join: n in "notifications", + on: n.user_id == q.user_id and n.seen == false, + group_by: [:id], + select_merge: %{ + unread_count: fragment("count(?)", n.id) + } + ) + end + + defp recount_unread_notifications(query, _), do: query end diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index ce025624d..6649ffbda 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -18,7 +18,13 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do # GET /api/v1/markers def index(%{assigns: %{user: user}} = conn, params) do - markers = Pleroma.Marker.get_markers(user, params["timeline"]) + markers = + Pleroma.Marker.get_markers( + user, + params["timeline"], + %{recount_unread: true} + ) + render(conn, "markers.json", %{markers: markers}) end diff --git a/test/marker_test.exs b/test/marker_test.exs index 5d03db48e..7b1d2218a 100644 --- a/test/marker_test.exs +++ b/test/marker_test.exs @@ -36,6 +36,20 @@ test "returns user markers" do insert(:marker, timeline: "home", user: user) assert Marker.get_markers(user, ["notifications"]) == [refresh_record(marker)] end + + test "returns user markers with recount unread notifications" do + user = insert(:user) + marker = insert(:marker, user: user) + insert(:notification, user: user) + insert(:notification, user: user) + insert(:marker, timeline: "home", user: user) + + assert Marker.get_markers( + user, + ["notifications"], + %{recount_unread: true} + ) == [%Marker{refresh_record(marker) | unread_count: 2}] + end end describe "upsert/2" do diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 8bcfcb7e1..64bf79bb1 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -11,11 +11,12 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do test "gets markers with correct scopes", %{conn: conn} do user = insert(:user) token = insert(:oauth_token, user: user, scopes: ["read:statuses"]) + insert_list(7, :notification, user: user) {:ok, %{"notifications" => marker}} = Pleroma.Marker.upsert( user, - %{"notifications" => %{"last_read_id" => "69420", "unread_count" => 7}} + %{"notifications" => %{"last_read_id" => "69420"}} ) response = From 1993d7096d673d8a8151fedd7bcac909d584d13d Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 5 Dec 2019 12:33:06 +0100 Subject: [PATCH 026/114] Validators: Add a type for the datetime used in AP. --- .../object_validators/note_validator.ex | 3 +- .../object_validators/types/date_time.ex | 34 +++++++++++++++++++ .../types/date_time_test.exs | 32 +++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/types/date_time.ex create mode 100644 test/web/activity_pub/object_validators/types/date_time_test.exs diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex index c660f30f0..eea15ce1c 100644 --- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex @@ -25,8 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do field(:actor, Types.ObjectID) field(:attributedTo, Types.ObjectID) field(:summary, :string) - # TODO: Write type - field(:published, :string) + field(:published, Types.DateTime) # TODO: Write type field(:emoji, :map, default: %{}) field(:sensitive, :boolean, default: false) diff --git a/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex b/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex new file mode 100644 index 000000000..4f412fcde --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex @@ -0,0 +1,34 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime do + @moduledoc """ + The AP standard defines the date fields in AP as xsd:DateTime. Elixir's + DateTime can't parse this, but it can parse the related iso8601. This + module punches the date until it looks like iso8601 and normalizes to + it. + + DateTimes without a timezone offset are treated as UTC. + + Reference: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published + """ + use Ecto.Type + + def type, do: :string + + def cast(datetime) when is_binary(datetime) do + with {:ok, datetime, _} <- DateTime.from_iso8601(datetime) do + {:ok, DateTime.to_iso8601(datetime)} + else + {:error, :missing_offset} -> cast("#{datetime}Z") + _e -> :error + end + end + + def cast(_), do: :error + + def dump(data) do + {:ok, data} + end + + def load(data) do + {:ok, data} + end +end diff --git a/test/web/activity_pub/object_validators/types/date_time_test.exs b/test/web/activity_pub/object_validators/types/date_time_test.exs new file mode 100644 index 000000000..3e17a9497 --- /dev/null +++ b/test/web/activity_pub/object_validators/types/date_time_test.exs @@ -0,0 +1,32 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTimeTest do + alias Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime + use Pleroma.DataCase + + test "it validates an xsd:Datetime" do + valid_strings = [ + "2004-04-12T13:20:00", + "2004-04-12T13:20:15.5", + "2004-04-12T13:20:00-05:00", + "2004-04-12T13:20:00Z" + ] + + invalid_strings = [ + "2004-04-12T13:00", + "2004-04-1213:20:00", + "99-04-12T13:00", + "2004-04-12" + ] + + assert {:ok, "2004-04-01T12:00:00Z"} == DateTime.cast("2004-04-01T12:00:00Z") + + Enum.each(valid_strings, fn date_time -> + result = DateTime.cast(date_time) + assert {:ok, _} = result + end) + + Enum.each(invalid_strings, fn date_time -> + result = DateTime.cast(date_time) + assert :error == result + end) + end +end From d4bafabfd14887e61eb5bc1d877035dcfebbd33f Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 9 Dec 2019 10:39:14 +0100 Subject: [PATCH 027/114] Beginnings of the create validator --- .../object_validators/create_validator.ex | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/object_validators/create_validator.ex diff --git a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex new file mode 100644 index 000000000..bd90f7250 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/create_validator.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.Web.ActivityPub.ObjectValidators.CreateNoteValidator do + use Ecto.Schema + + alias Pleroma.Web.ActivityPub.ObjectValidators.Types + alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator + + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field(:id, :string, primary_key: true) + field(:actor, Types.ObjectID) + field(:type, :string) + field(:to, {:array, :string}) + field(:cc, {:array, :string}) + field(:bto, {:array, :string}, default: []) + field(:bcc, {:array, :string}, default: []) + + embeds_one(:object, NoteValidator) + end + + def cast_data(data) do + %__MODULE__{} + |> cast(data, __schema__(:fields)) + end +end From cd040691bd28fea1437b8f1c39bb914465e1ff46 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 10 Feb 2020 09:01:45 +0300 Subject: [PATCH 028/114] maked `unread_count` as virtual field --- lib/pleroma/marker.ex | 31 ++- lib/pleroma/notification.ex | 14 +- .../controllers/marker_controller.ex | 8 +- mix.lock | 192 +++++++++--------- ....exs => 20200210050658_update_markers.exs} | 15 +- test/marker_test.exs | 14 +- test/notification_test.exs | 5 +- 7 files changed, 123 insertions(+), 156 deletions(-) rename priv/repo/migrations/{20191030202008_add_unread_to_marker.exs => 20200210050658_update_markers.exs} (68%) diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index 2d217a0b7..dab97d8b6 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Marker do field(:last_read_id, :string, default: "") field(:timeline, :string, default: "") field(:lock_version, :integer, default: 0) - field(:unread_count, :integer, default: 0) + field(:unread_count, :integer, default: 0, virtual: true) belongs_to(:user, User, type: FlakeId.Ecto.CompatType) timestamps() @@ -33,14 +33,15 @@ defmodule Pleroma.Marker do opts: `recount_unread` - run force recount unread notifications for `true` value """ - @spec get_markers(User.t(), list(String), map()) :: list(t()) - def get_markers(user, timelines \\ [], opts \\ %{}) do + @spec get_markers(User.t(), list(String)) :: list(t()) + def get_markers(user, timelines \\ []) do user |> get_query(timelines) - |> recount_unread_notifications(opts[:recount_unread]) + |> unread_count_query() |> Repo.all() end + @spec upsert(User.t(), map()) :: {:ok | :error, any()} def upsert(%User{} = user, attrs) do attrs |> Map.take(@timelines) @@ -52,22 +53,18 @@ def upsert(%User{} = user, attrs) do Multi.insert(multi, timeline, marker, returning: true, - on_conflict: {:replace, [:last_read_id, :unread_count]}, + on_conflict: {:replace, [:last_read_id]}, conflict_target: [:user_id, :timeline] ) end) |> Repo.transaction() end - @spec multi_set_unread_count(Multi.t(), User.t(), String.t()) :: Multi.t() - def multi_set_unread_count(multi, %User{} = user, "notifications") do + @spec multi_set_last_read_id(Multi.t(), User.t(), String.t()) :: Multi.t() + def multi_set_last_read_id(multi, %User{} = user, "notifications") do multi |> Multi.run(:counters, fn _repo, _changes -> - {:ok, - %{ - unread_count: Repo.aggregate(Notification.unread_count_query(user), :count, :id), - last_read_id: Repo.one(Notification.last_read_query(user)) - }} + {:ok, %{last_read_id: Repo.one(Notification.last_read_query(user))}} end) |> Multi.insert( :marker, @@ -77,12 +74,12 @@ def multi_set_unread_count(multi, %User{} = user, "notifications") do |> Ecto.Changeset.change() end, returning: true, - on_conflict: {:replace, [:last_read_id, :unread_count]}, + on_conflict: {:replace, [:last_read_id]}, conflict_target: [:user_id, :timeline] ) end - def multi_set_unread_count(multi, _, _), do: multi + def multi_set_last_read_id(multi, _, _), do: multi defp get_marker(user, timeline) do case Repo.find_resource(get_query(user, timeline)) do @@ -94,7 +91,7 @@ defp get_marker(user, timeline) do @doc false defp changeset(marker, attrs) do marker - |> cast(attrs, [:last_read_id, :unread_count]) + |> cast(attrs, [:last_read_id]) |> validate_required([:user_id, :timeline, :last_read_id]) |> validate_inclusion(:timeline, @timelines) end @@ -111,7 +108,7 @@ defp get_query(user, timelines) do |> by_timeline(timelines) end - defp recount_unread_notifications(query, true) do + defp unread_count_query(query) do from( q in query, left_join: n in "notifications", @@ -122,6 +119,4 @@ defp recount_unread_notifications(query, true) do } ) end - - defp recount_unread_notifications(query, _), do: query end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 2e4fe2edb..70fd97bfa 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -38,14 +38,6 @@ def changeset(%Notification{} = notification, attrs) do |> cast(attrs, [:seen]) end - @spec unread_count_query(User.t()) :: Ecto.Queryable.t() - def unread_count_query(user) do - from(q in Pleroma.Notification, - where: q.user_id == ^user.id, - where: q.seen == false - ) - end - @spec last_read_query(User.t()) :: Ecto.Queryable.t() def last_read_query(user) do from(q in Pleroma.Notification, @@ -229,7 +221,7 @@ def set_read_up_to(%{id: user_id} = user, id) do {:ok, %{ids: {_, notification_ids}}} = Multi.new() |> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()]) - |> Marker.multi_set_unread_count(user, "notifications") + |> Marker.multi_set_last_read_id(user, "notifications") |> Repo.transaction() Notification @@ -253,7 +245,7 @@ def read_one(%User{} = user, notification_id) do with {:ok, %Notification{} = notification} <- get(user, notification_id) do Multi.new() |> Multi.update(:update, changeset(notification, %{seen: true})) - |> Marker.multi_set_unread_count(user, "notifications") + |> Marker.multi_set_last_read_id(user, "notifications") |> Repo.transaction() |> case do {:ok, %{update: notification}} -> {:ok, notification} @@ -340,7 +332,7 @@ def create_notification(%Activity{} = activity, %User{} = user) do {:ok, %{notification: notification}} = Multi.new() |> Multi.insert(:notification, %Notification{user_id: user.id, activity: activity}) - |> Marker.multi_set_unread_count(user, "notifications") + |> Marker.multi_set_last_read_id(user, "notifications") |> Repo.transaction() ["user", "user:notification"] diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index 6649ffbda..ce025624d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -18,13 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do # GET /api/v1/markers def index(%{assigns: %{user: user}} = conn, params) do - markers = - Pleroma.Marker.get_markers( - user, - params["timeline"], - %{recount_unread: true} - ) - + markers = Pleroma.Marker.get_markers(user, params["timeline"]) render(conn, "markers.json", %{markers: markers}) end diff --git a/mix.lock b/mix.lock index b8a35a795..1893e7e41 100644 --- a/mix.lock +++ b/mix.lock @@ -1,112 +1,112 @@ %{ - "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"}, + "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, - "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm"}, - "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, - "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, - "cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"}, - "calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, + "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"}, + "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"}, + "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5a981b98ac7d366a9b6bf40eac389aaf4d6e623c631e6b6f8a6b571efaafd338"}, + "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "3aadb1e605747122f60aa7b0b121cca23c14868558157563b3f3e19ea929f7d0"}, + "calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "738d0e17a93c2ccfe4ddc707bdc8e672e9074c8569498483feb1c4530fb91b2b"}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]}, - "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, - "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"}, - "comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"}, - "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, - "cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, - "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"}, - "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, + "comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm", "d8700a0ca4dbb616c22c9b3f6dd539d88deaafec3efe66869d6370c9a559b3e9"}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, + "cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9af027d20dc12dd0c4345a6b87247e0c62965871feea0bfecf9764648b02cc69"}, + "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, + "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, + "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"}, + "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "48e513299cd28b12c77266c0ed5b1c844368e5c1823724994ae84834f43d6bbe"}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, - "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm"}, - "db_connection": {:hex, :db_connection, "2.2.0", "e923e88887cd60f9891fd324ac5e0290954511d090553c415fbf54be4c57ee63", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm"}, - "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"}, - "ecto": {:hex, :ecto, "3.3.1", "82ab74298065bf0c64ca299f6c6785e68ea5d6b980883ee80b044499df35aba1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, - "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, - "ecto_sql": {:hex, :ecto_sql, "3.3.2", "92804e0de69bb63e621273c3492252cb08a29475c05d40eeb6f41ad2d483cfd3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"}, - "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"}, + "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, + "db_connection": {:hex, :db_connection, "2.2.0", "e923e88887cd60f9891fd324ac5e0290954511d090553c415fbf54be4c57ee63", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "bdf196feedfa6b83071e808b2b086fb113f8a1c4c7761f6eff6fe4b96aba0086"}, + "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, + "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, + "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm", "5e8806285d8a3a8999bd38e4a73c58d28534c856bc38c44818e5ba85bbda16fb"}, + "ecto": {:hex, :ecto, "3.3.1", "82ab74298065bf0c64ca299f6c6785e68ea5d6b980883ee80b044499df35aba1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "e6c614dfe3bcff2d575ce16d815dbd43f4ee1844599a83de1eea81976a31c174"}, + "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, + "ecto_sql": {:hex, :ecto_sql, "3.3.2", "92804e0de69bb63e621273c3492252cb08a29475c05d40eeb6f41ad2d483cfd3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b82d89d4e6a9f7f7f04783b07e8b0af968e0be2f01ee4b39047fe727c5c07471"}, + "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm", "98d0f3c6f4b8a0333170df770c6fe772b3d04564fb514c1a09504cf5ab2f48a5"}, + "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, - "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"}, - "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"}, - "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"}, + "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"}, + "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, + "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"}, + "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"}, "ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]}, - "excoveralls": {:hex, :excoveralls, "0.11.2", "0c6f2c8db7683b0caa9d490fb8125709c54580b4255ffa7ad35f3264b075a643", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "fast_html": {:hex, :fast_html, "1.0.1", "5bc7df4dc4607ec2c314c16414e4111d79a209956c4f5df96602d194c61197f9", [:make, :mix], [], "hexpm"}, - "fast_sanitize": {:hex, :fast_sanitize, "0.1.6", "60a5ae96879956dea409a91a77f5dd2994c24cc10f80eefd8f9892ee4c0c7b25", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, - "floki": {:hex, :floki, "0.23.1", "e100306ce7d8841d70a559748e5091542e2cfc67ffb3ade92b89a8435034dab1", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm"}, - "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm"}, - "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"}, - "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, - "gettext": {:hex, :gettext, "0.17.1", "8baab33482df4907b3eae22f719da492cee3981a26e649b9c2be1c0192616962", [:mix], [], "hexpm"}, - "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "html_entities": {:hex, :html_entities, "0.5.0", "40f5c5b9cbe23073b48a4e69c67b6c11974f623a76165e2b92d098c0e88ccb1d", [:mix], [], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.11.2", "0c6f2c8db7683b0caa9d490fb8125709c54580b4255ffa7ad35f3264b075a643", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e11a4490976aabeed3eb9dc70ec94a4f2d11fed5c9d4b5dc5d89bfa0a215abb5"}, + "fast_html": {:hex, :fast_html, "1.0.1", "5bc7df4dc4607ec2c314c16414e4111d79a209956c4f5df96602d194c61197f9", [:make, :mix], [], "hexpm", "18e627dd62051a375ef94b197f41e8027c3e8eef0180ab8f81e0543b3dc6900a"}, + "fast_sanitize": {:hex, :fast_sanitize, "0.1.6", "60a5ae96879956dea409a91a77f5dd2994c24cc10f80eefd8f9892ee4c0c7b25", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b73f50f0cb522dd0331ea8e8c90b408de42c50f37641219d6364f0e3e7efd22c"}, + "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, + "floki": {:hex, :floki, "0.23.1", "e100306ce7d8841d70a559748e5091542e2cfc67ffb3ade92b89a8435034dab1", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "39b431b6330206cadee418e793177401ebedf2e86abc945ddd545aedb37dfc19"}, + "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, + "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm", "8453e2289d94c3199396eb517d65d6715ef26bcae0ee83eb5ff7a84445458d76"}, + "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm", "5cacd405e72b2609a7e1f891bddb80c53d0b3b7b0036d1648e7382ca108c41c8"}, + "gettext": {:hex, :gettext, "0.17.1", "8baab33482df4907b3eae22f719da492cee3981a26e649b9c2be1c0192616962", [:mix], [], "hexpm", "f7d97341e536f95b96eef2988d6d4230f7262cf239cda0e2e63123ee0b717222"}, + "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, + "html_entities": {:hex, :html_entities, "0.5.0", "40f5c5b9cbe23073b48a4e69c67b6c11974f623a76165e2b92d098c0e88ccb1d", [:mix], [], "hexpm", "8e9186e1873bea1067895f6a542b59df6c9fcf3b516ba272eeff3ea0c7b755cd"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]}, - "httpoison": {:hex, :httpoison, "1.6.1", "2ce5bf6e535cd0ab02e905ba8c276580bab80052c5c549f53ddea52d72e81f33", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, - "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm"}, - "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, - "joken": {:hex, :joken, "2.1.0", "bf21a73105d82649f617c5e59a7f8919aa47013d2519ebcc39d998d8d12adda9", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"}, - "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, - "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, - "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, - "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, - "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, + "httpoison": {:hex, :httpoison, "1.6.1", "2ce5bf6e535cd0ab02e905ba8c276580bab80052c5c549f53ddea52d72e81f33", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "89149056039084024a284cd703b2d1900d584958dba432132cb21ef35aed7487"}, + "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, + "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, + "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, + "joken": {:hex, :joken, "2.1.0", "bf21a73105d82649f617c5e59a7f8919aa47013d2519ebcc39d998d8d12adda9", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "eb02df7d5526df13063397e051b926b7006d5986d66f399eefc474f560cdad6a"}, + "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm", "6429c4fee52b2dda7861ee19a4f09c8c1ffa213bee3a1ec187828fde95d447ed"}, + "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm", "1feaf05ee886815ad047cad7ede17d6910710986148ae09cf73eee2989717b81"}, + "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, + "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, - "mock": {:hex, :mock, "0.3.4", "c5862eb3b8c64237f45f586cf00c9d892ba07bb48305a43319d428ce3c2897dd", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, - "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"}, - "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"}, + "mock": {:hex, :mock, "0.3.4", "c5862eb3b8c64237f45f586cf00c9d892ba07bb48305a43319d428ce3c2897dd", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "e6d886252f1a41f4ba06ecf2b4c8d38760b34b1c08a11c28f7397b2e03995964"}, + "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm", "3bc928d817974fa10cc11e6c89b9a9361e37e96dbbf3d868c41094ec05745dcd"}, + "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm", "052346cf322311c49a0f22789f3698eea030eec09b8c47367f0686ef2634ae14"}, "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm", "00e3ebdc821fb3a36957320d49e8f4bfa310d73ea31c90e5f925dc75e030da8f"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, - "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, - "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm"}, - "phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"}, - "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"}, - "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, - "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, - "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"}, - "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, - "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"}, - "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, - "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"}, - "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"}, - "quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"}, - "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, + "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, + "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"}, + "phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "256ad7a140efadc3f0290470369da5bd3de985ec7c706eba07c2641b228974be"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "fe15d9fee5b82f5e64800502011ffe530650d42e1710ae9b14bc4c9be38bf303"}, + "phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8b01b3d6d39731ab18aa548d928b5796166d2500755f553725cfe967bafba7d9"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, + "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"}, + "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6cd8ddd1bd1fbfa54d3fc61d4719c2057dae67615395d58d40437a919a46f132"}, + "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm", "73c1682f0e414cfb5d9b95c8e8cd6ffcfdae699e3b05e1db744e58b7be857759"}, + "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, + "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, + "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"}, + "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm", "d39f2ce1f3f29f3bf04f915aa3cf9c7cd4d2cee2f975e05f526e06cae9b7c902"}, + "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, + "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"}, + "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, + "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, + "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"}, + "quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm", "6de553ba9ac0668d3728b699d5065543f3e40c854154017461ee8c09038752da"}, + "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "825dc00aaba5a1b7c4202a532b696b595dd3bcb3", [ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"]}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"}, - "swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm"}, - "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"}, - "swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, + "swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "94884f84783fc1ba027aba8fe8a7dae4aad78c98e9f9c76667ec3471585c08c6"}, + "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, + "swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"}, "syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]}, - "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm"}, - "tesla": {:hex, :tesla, "1.3.0", "f35d72f029e608f9cdc6f6d6fcc7c66cf6d6512a70cfef9206b21b8bd0203a30", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 0.4", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, - "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, - "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "tzdata": {:hex, :tzdata, "0.5.22", "f2ba9105117ee0360eae2eca389783ef7db36d533899b2e84559404dbc77ebb8", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "ueberauth": {:hex, :ueberauth, "0.6.2", "25a31111249d60bad8b65438b2306a4dc91f3208faa62f5a8c33e8713989b2e8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, - "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"}, - "web_push_encryption": {:hex, :web_push_encryption, "0.2.3", "a0ceab85a805a30852f143d22d71c434046fbdbafbc7292e7887cec500826a80", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, + "tesla": {:hex, :tesla, "1.3.0", "f35d72f029e608f9cdc6f6d6fcc7c66cf6d6512a70cfef9206b21b8bd0203a30", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 0.4", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "93a7cacc5ca47997759cfa1d3ab25501d291e490908006d5be56f37f89d96693"}, + "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"}, + "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, + "tzdata": {:hex, :tzdata, "0.5.22", "f2ba9105117ee0360eae2eca389783ef7db36d533899b2e84559404dbc77ebb8", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cd66c8a1e6a9e121d1f538b01bef459334bb4029a1ffb4eeeb5e4eae0337e7b6"}, + "ueberauth": {:hex, :ueberauth, "0.6.2", "25a31111249d60bad8b65438b2306a4dc91f3208faa62f5a8c33e8713989b2e8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "db9fbfb5ac707bc4f85a297758406340bf0358b4af737a88113c1a9eee120ac7"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, + "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, + "web_push_encryption": {:hex, :web_push_encryption, "0.2.3", "a0ceab85a805a30852f143d22d71c434046fbdbafbc7292e7887cec500826a80", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "9315c8f37c108835cf3f8e9157d7a9b8f420a34f402d1b1620a31aed5b93ecdf"}, "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, } diff --git a/priv/repo/migrations/20191030202008_add_unread_to_marker.exs b/priv/repo/migrations/20200210050658_update_markers.exs similarity index 68% rename from priv/repo/migrations/20191030202008_add_unread_to_marker.exs rename to priv/repo/migrations/20200210050658_update_markers.exs index 2b3abc682..b280e156c 100644 --- a/priv/repo/migrations/20191030202008_add_unread_to_marker.exs +++ b/priv/repo/migrations/20200210050658_update_markers.exs @@ -1,25 +1,17 @@ -defmodule Pleroma.Repo.Migrations.AddUnreadToMarker do +defmodule Pleroma.Repo.Migrations.UpdateMarkers do use Ecto.Migration import Ecto.Query alias Pleroma.Repo def up do - alter table(:markers) do - add_if_not_exists(:unread_count, :integer, default: 0) - end - - flush() - update_markers() end def down do - alter table(:markers) do - remove_if_exists(:unread_count, :integer) - end + :ok end - def update_markers do + defp update_markers do now = NaiveDateTime.utc_now() markers_attrs = @@ -27,7 +19,6 @@ def update_markers do select: %{ timeline: "notifications", user_id: q.user_id, - unread_count: fragment("SUM( CASE WHEN seen = false THEN 1 ELSE 0 END )"), last_read_id: type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) }, diff --git a/test/marker_test.exs b/test/marker_test.exs index 7b1d2218a..54c710691 100644 --- a/test/marker_test.exs +++ b/test/marker_test.exs @@ -15,7 +15,7 @@ test "returns multi" do assert %Ecto.Multi{ operations: [marker: {:run, _}, counters: {:run, _}] } = - Marker.multi_set_unread_count( + Marker.multi_set_last_read_id( Ecto.Multi.new(), user, "notifications" @@ -25,19 +25,12 @@ test "returns multi" do test "return empty multi" do user = insert(:user) multi = Ecto.Multi.new() - assert Marker.multi_set_unread_count(multi, user, "home") == multi + assert Marker.multi_set_last_read_id(multi, user, "home") == multi end end describe "get_markers/2" do test "returns user markers" do - user = insert(:user) - marker = insert(:marker, user: user) - insert(:marker, timeline: "home", user: user) - assert Marker.get_markers(user, ["notifications"]) == [refresh_record(marker)] - end - - test "returns user markers with recount unread notifications" do user = insert(:user) marker = insert(:marker, user: user) insert(:notification, user: user) @@ -46,8 +39,7 @@ test "returns user markers with recount unread notifications" do assert Marker.get_markers( user, - ["notifications"], - %{recount_unread: true} + ["notifications"] ) == [%Marker{refresh_record(marker) | unread_count: 2}] end end diff --git a/test/notification_test.exs b/test/notification_test.exs index c9b352097..49a79b2d3 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -338,12 +338,15 @@ test "it sets all notifications as read up to a specified notification ID" do assert n2.seen == true assert n3.seen == false - assert %Pleroma.Marker{unread_count: 1} = + assert %Pleroma.Marker{} = + m = Pleroma.Repo.get_by( Pleroma.Marker, user_id: other_user.id, timeline: "notifications" ) + + assert m.last_read_id == to_string(n2.id) end end From 3830cb538bd3aaee3fc48bc97b57230a558b98cf Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 10 Feb 2020 09:14:15 +0300 Subject: [PATCH 029/114] removed a comments --- lib/pleroma/marker.ex | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index dab97d8b6..ff5f60351 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -27,12 +27,7 @@ defmodule Pleroma.Marker do timestamps() end - @doc """ - Gets markers by user and timeline. - - opts: - `recount_unread` - run force recount unread notifications for `true` value - """ + @doc "Gets markers by user and timeline." @spec get_markers(User.t(), list(String)) :: list(t()) def get_markers(user, timelines \\ []) do user From d3cf7e19fbe089b3a6d62d6a26f3dfc866a6669d Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 17 Mar 2020 13:02:10 +0100 Subject: [PATCH 030/114] activity_pub_controller_test.exs: test posting with AP C2S uploaded media --- .../activity_pub_controller_test.exs | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index bd8e0b5cc..2bd494a37 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1241,16 +1241,46 @@ test "POST /api/ap/upload_media", %{conn: conn} do filename: "an_image.jpg" } - conn = + object = conn |> assign(:user, user) |> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) + |> json_response(:created) - assert object = json_response(conn, :created) assert object["name"] == desc assert object["type"] == "Document" assert object["actor"] == user.ap_id + assert [%{"href" => object_href}] = object["url"] + activity_request = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Create", + "object" => %{ + "type" => "Note", + "content" => "AP C2S test, attachment", + "attachment" => [object] + }, + "to" => "https://www.w3.org/ns/activitystreams#Public", + "cc" => [] + } + + activity_response = + conn + |> assign(:user, user) + |> post("/users/#{user.nickname}/outbox", activity_request) + |> json_response(:created) + + assert activity_response["id"] + assert activity_response["object"] + assert activity_response["actor"] == user.ap_id + + assert %Object{data: %{"attachment" => [attachment]}} = Object.normalize(activity_response["object"]) + assert attachment["type"] == "Document" + assert attachment["name"] == desc + assert [%{"href" => attachment_href}] = attachment["url"] + assert attachment_href == object_href + + # Fails if unauthenticated conn |> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) |> json_response(403) From f9d622d25a744f58fbaf8370ad4435597bb15bf0 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 19 Mar 2020 15:08:49 +0100 Subject: [PATCH 031/114] WIP --- lib/pleroma/web/activity_pub/transmogrifier.ex | 15 --------------- .../activity_pub_controller_test.exs | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 9cd3de705..db848f657 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -202,21 +202,6 @@ def fix_context(object) do |> Map.put("conversation", context) end - def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do - attachments = - Enum.map(attachment, fn data -> - media_type = data["mediaType"] || data["mimeType"] - href = data["url"] || data["href"] - url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}] - - data - |> Map.put("mediaType", media_type) - |> Map.put("url", url) - end) - - Map.put(object, "attachment", attachments) - end - def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do object |> Map.put("attachment", [attachment]) diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 2bd494a37..01c955c0a 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1250,7 +1250,9 @@ test "POST /api/ap/upload_media", %{conn: conn} do assert object["name"] == desc assert object["type"] == "Document" assert object["actor"] == user.ap_id - assert [%{"href" => object_href}] = object["url"] + assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"] + assert is_binary(object_href) + assert object_mediatype == "image/jpeg" activity_request = %{ "@context" => "https://www.w3.org/ns/activitystreams", @@ -1274,11 +1276,19 @@ test "POST /api/ap/upload_media", %{conn: conn} do assert activity_response["object"] assert activity_response["actor"] == user.ap_id - assert %Object{data: %{"attachment" => [attachment]}} = Object.normalize(activity_response["object"]) + assert %Object{data: %{"attachment" => [attachment]}} = + Object.normalize(activity_response["object"]) + assert attachment["type"] == "Document" assert attachment["name"] == desc - assert [%{"href" => attachment_href}] = attachment["url"] - assert attachment_href == object_href + + assert [ + %{ + "href" => ^object_href, + "type" => "Link", + "mediaType" => ^object_mediatype + } + ] = attachment["url"] # Fails if unauthenticated conn From 7d275970ab191af539acbc0baec3bc1d0a2558e1 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 19 Mar 2020 10:08:11 -0500 Subject: [PATCH 032/114] Add emoji reactions to features in nodeinfo --- lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 18eb41333..c653a80c3 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -74,7 +74,8 @@ def raw_nodeinfo do end, if Config.get([:instance, :safe_dm_mentions]) do "safe_dm_mentions" - end + end, + "pleroma_emoji_reactions" ] |> Enum.filter(& &1) From 9b9d67bbec537df6f7c5729e81da6deeaf896bd9 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 19 Mar 2020 18:16:12 +0100 Subject: [PATCH 033/114] Fix linting. --- .../web/activity_pub/object_validators/create_validator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex index bd90f7250..9e480c4ed 100644 --- a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do use Ecto.Schema - alias Pleroma.Web.ActivityPub.ObjectValidators.Types alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.Types import Ecto.Changeset From c1fd4f665335ba67336bd1b2fab2d9df5e247e08 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 19 Mar 2020 19:10:03 +0100 Subject: [PATCH 034/114] transmogrifier.ex: rework fix_attachment for better IR --- .../web/activity_pub/transmogrifier.ex | 45 +++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 30 +++---------- 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index db848f657..df5ca0239 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -202,6 +202,51 @@ def fix_context(object) do |> Map.put("conversation", context) end + defp add_if_present(map, _key, nil), do: map + + defp add_if_present(map, key, value) do + Map.put(map, key, value) + end + + def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do + attachments = + Enum.map(attachment, fn data -> + url = + cond do + is_list(data["url"]) -> List.first(data["url"]) + is_map(data["url"]) -> data["url"] + true -> nil + end + + media_type = + cond do + is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"] + is_binary(data["mediaType"]) -> data["mediaType"] + is_binary(data["mimeType"]) -> data["mimeType"] + true -> nil + end + + href = + cond do + is_map(url) && is_binary(url["href"]) -> url["href"] + is_binary(data["url"]) -> data["url"] + is_binary(data["href"]) -> data["href"] + end + + attachment_url = + %{"href" => href} + |> add_if_present("mediaType", media_type) + |> add_if_present("type", Map.get(url || %{}, "type")) + + %{"url" => [attachment_url]} + |> add_if_present("mediaType", media_type) + |> add_if_present("type", data["type"]) + |> add_if_present("name", data["name"]) + end) + + Map.put(object, "attachment", attachments) + end + def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do object |> Map.put("attachment", [attachment]) diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index efbca82f6..242d933e7 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1228,19 +1228,13 @@ test "it remaps video URLs as attachments if necessary" do attachment = %{ "type" => "Link", "mediaType" => "video/mp4", - "href" => - "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", - "mimeType" => "video/mp4", - "size" => 5_015_880, "url" => [ %{ "href" => "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" + "mediaType" => "video/mp4" } - ], - "width" => 480 + ] } assert object.data["url"] == @@ -2067,11 +2061,7 @@ test "returns modified object when attachment is map" do %{ "mediaType" => "video/mp4", "url" => [ - %{ - "href" => "https://peertube.moe/stat-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } + %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"} ] } ] @@ -2089,23 +2079,13 @@ test "returns modified object when attachment is list" do %{ "mediaType" => "video/mp4", "url" => [ - %{ - "href" => "https://pe.er/stat-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } + %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"} ] }, %{ - "href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4", - "mimeType" => "video/mp4", "url" => [ - %{ - "href" => "https://pe.er/stat-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } + %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"} ] } ] From 6c1232b486dcad5a644b4292697d08ebe3000cb3 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 20 Mar 2020 15:00:28 +0100 Subject: [PATCH 035/114] NotificationController: Fix test. --- .../mastodon_api/controllers/notification_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index e407b8297..adbb78da6 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -310,7 +310,7 @@ test "filters notifications using include_types" do {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) - {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user) + {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id) {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user) {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) From 4a2538967caf5b0f9970cc5f973c16ea5d776aa3 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 24 Mar 2020 20:18:27 +0400 Subject: [PATCH 036/114] Support pagination in conversations --- CHANGELOG.md | 3 +++ lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- .../controllers/pleroma_api_controller.ex | 10 +++++----- .../controllers/pleroma_api_controller_test.exs | 17 +++++++++++++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15a073c64..905364d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Support for `include_types` in `/api/v1/notifications`. +### Fixed +- Support pagination in conversations API + ## [2.0.0] - 2019-03-08 ### Security - Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request. diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 30e282840..351d1bdb8 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -696,7 +696,7 @@ def move(%User{} = origin, %User{} = target, local \\ true) do end end - defp fetch_activities_for_context_query(context, opts) do + def fetch_activities_for_context_query(context, opts) do public = [Constants.as_public()] recipients = diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index dae7f0f2f..edb071baa 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -110,12 +110,11 @@ def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) end def conversation_statuses( - %{assigns: %{user: user}} = conn, + %{assigns: %{user: %{id: user_id} = user}} = conn, %{"id" => participation_id} = params ) do - with %Participation{} = participation <- - Participation.get(participation_id, preload: [:conversation]), - true <- user.id == participation.user_id do + with %Participation{user_id: ^user_id} = participation <- + Participation.get(participation_id, preload: [:conversation]) do params = params |> Map.put("blocking_user", user) @@ -124,7 +123,8 @@ def conversation_statuses( activities = participation.conversation.ap_id - |> ActivityPub.fetch_activities_for_context(params) + |> ActivityPub.fetch_activities_for_context_query(params) + |> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false)) |> Enum.reverse() conn diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs index 32250f06f..8bf7eb3be 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -169,6 +169,23 @@ test "/api/v1/pleroma/conversations/:id/statuses" do id_one = activity.id id_two = activity_two.id assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result + + {:ok, %{id: id_three}} = + CommonAPI.post(other_user, %{ + "status" => "Bye!", + "in_reply_to_status_id" => activity.id, + "in_reply_to_conversation_id" => participation.id + }) + + assert [%{"id" => ^id_two}, %{"id" => ^id_three}] = + conn + |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2") + |> json_response(:ok) + + assert [%{"id" => ^id_three}] = + conn + |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}") + |> json_response(:ok) end test "PATCH /api/v1/pleroma/conversations/:id" do From 74560e888e5e3e4dc2fa5b4fec4cf3986a1d1a55 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 24 Mar 2020 18:20:58 +0000 Subject: [PATCH 037/114] Apply suggestion to lib/pleroma/web/activity_pub/object_validators/create_validator.ex --- .../web/activity_pub/object_validators/create_validator.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex index 9e480c4ed..872a12c48 100644 --- a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex @@ -25,7 +25,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do end def cast_data(data) do - %__MODULE__{} - |> cast(data, __schema__(:fields)) + cast(%__MODULE__{}, data, __schema__(:fields)) end end From aaf00f1ff59fc279758f5fa5ceaf758d683bd216 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 24 Mar 2020 18:24:09 +0000 Subject: [PATCH 038/114] Apply suggestion to lib/pleroma/web/activity_pub/pipeline.ex --- lib/pleroma/web/activity_pub/pipeline.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index cb3571917..25f29bf63 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -35,7 +35,7 @@ defp maybe_federate(activity, meta) do {:ok, :not_federated} end else - _e -> {:error, "local not set in meta"} + _e -> {:error, :badarg} end end end From f31688246470273cc35588d0f1c2187edc6084c7 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 24 Mar 2020 18:37:53 +0000 Subject: [PATCH 039/114] Apply suggestion to lib/pleroma/web/activity_pub/activity_pub.ex --- lib/pleroma/web/activity_pub/activity_pub.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index d9f30e629..dd4b04185 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -150,7 +150,6 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when {_, true} <- {:remote_limit_error, check_remote_limit(map)}, {:ok, map} <- MRF.filter(map), {recipients, _, _} = get_recipients(map), - # ??? {:fake, false, map, recipients} <- {:fake, fake, map, recipients}, {:containment, :ok} <- {:containment, Containment.contain_child(map)}, {:ok, map, object} <- insert_full_object(map) do From 64165d1df95bc3a22260dafa4584471427685864 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 24 Mar 2020 20:21:27 +0100 Subject: [PATCH 040/114] node_info_test.exs: Add test on the default feature list --- test/web/node_info_test.exs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index ee10ad5db..e8922a8ee 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -128,6 +128,27 @@ test "it shows if federation is enabled/disabled", %{conn: conn} do end end + test "it shows default features flags", %{conn: conn} do + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + + assert response["metadata"]["features"] -- + [ + "pleroma_api", + "mastodon_api", + "mastodon_api_streaming", + "polls", + "pleroma_explicit_addressing", + "shareable_emoji_packs", + "multifetch", + "chat", + "relay", + "pleroma_emoji_reactions" + ] == [] + end + test "it shows MRF transparency data if enabled", %{conn: conn} do config = Pleroma.Config.get([:instance, :rewrite_policy]) Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) From 03a18cf037d7a9b4ba84ff456b434d65e3290965 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 24 Mar 2020 20:39:19 +0100 Subject: [PATCH 041/114] node_info_test: Bump default features list --- test/web/node_info_test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index 01a67afd7..e5eebced1 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -145,7 +145,8 @@ test "it shows default features flags", %{conn: conn} do "multifetch", "chat", "relay", - "pleroma_emoji_reactions" + "pleroma_emoji_reactions", + "pleroma:api/v1/notifications:include_types_filter" ] == [] end From 4cf1007a7d478a54a759d018dd7ce958a45f3977 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 26 Mar 2020 15:16:54 +0100 Subject: [PATCH 042/114] ActivityPub: Small refactor. --- lib/pleroma/web/activity_pub/activity_pub.ex | 23 ++++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index dd4b04185..35c2eb133 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -129,18 +129,17 @@ def increase_poll_votes_if_vote(_create_data), do: :noop # TODO rewrite in with style @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} def persist(object, meta) do - local = Keyword.fetch!(meta, :local) - {recipients, _, _} = get_recipients(object) - - {:ok, activity} = - Repo.insert(%Activity{ - data: object, - local: local, - recipients: recipients, - actor: object["actor"] - }) - - {:ok, activity, meta} + with local <- Keyword.fetch!(meta, :local), + {recipients, _, _} <- get_recipients(object), + {:ok, activity} <- + Repo.insert(%Activity{ + data: object, + local: local, + recipients: recipients, + actor: object["actor"] + }) do + {:ok, activity, meta} + end end def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do From d7aa0b645b0da48af830f252ae80458afc965281 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 26 Mar 2020 14:23:19 +0000 Subject: [PATCH 043/114] Apply suggestion to lib/pleroma/web/activity_pub/object_validator.ex --- lib/pleroma/web/activity_pub/object_validator.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index cff924047..9b2889e92 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -26,8 +26,7 @@ def validate(%{"type" => "Like"} = object, meta) do def stringify_keys(object) do object - |> Enum.map(fn {key, val} -> {to_string(key), val} end) - |> Enum.into(%{}) + |> Map.new(fn {key, val} -> {to_string(key), val} end) end def fetch_actor_and_object(object) do From eaacc648392e6544cd3a3b77bde266e34cebf634 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 26 Mar 2020 15:33:10 +0100 Subject: [PATCH 044/114] Refactors. --- lib/pleroma/web/activity_pub/activity_pub.ex | 3 +-- .../activity_pub/object_validators/common_validations.ex | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 35c2eb133..55f4de693 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -125,8 +125,6 @@ def increase_poll_votes_if_vote(%{ def increase_poll_votes_if_vote(_create_data), do: :noop - @spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()} - # TODO rewrite in with style @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} def persist(object, meta) do with local <- Keyword.fetch!(meta, :local), @@ -142,6 +140,7 @@ def persist(object, meta) do end end + @spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()} def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do with nil <- Activity.normalize(map), map <- lazy_put_activity_defaults(map, fake), diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex index db0e2072d..26a57f02b 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -21,11 +21,11 @@ def validate_actor_presence(cng, field_name \\ :actor) do def validate_object_presence(cng, field_name \\ :object) do cng - |> validate_change(field_name, fn field_name, actor -> - if Object.get_cached_by_ap_id(actor) do + |> validate_change(field_name, fn field_name, object -> + if Object.get_cached_by_ap_id(object) do [] else - [{field_name, "can't find user"}] + [{field_name, "can't find object"}] end end) end From 0adaab8e753b0ec22feccfc03d301073327a6d31 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 26 Mar 2020 15:37:42 +0100 Subject: [PATCH 045/114] Bump copyright dates. --- COPYING | 4 ++-- lib/pleroma/web/activity_pub/object_validator.ex | 2 +- .../web/activity_pub/object_validators/common_validations.ex | 2 +- .../web/activity_pub/object_validators/create_validator.ex | 2 +- .../web/activity_pub/object_validators/like_validator.ex | 2 +- .../web/activity_pub/object_validators/note_validator.ex | 2 +- lib/pleroma/web/activity_pub/pipeline.ex | 2 +- priv/repo/migrations/20190408123347_create_conversations.exs | 2 +- .../activity_pub/object_validators/note_validator_test.exs | 2 +- test/web/activity_pub/pipeline_test.exs | 2 +- test/web/activity_pub/side_effects_test.exs | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/COPYING b/COPYING index 0aede0fba..3140c8038 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Unless otherwise stated this repository is copyright © 2017-2019 +Unless otherwise stated this repository is copyright © 2017-2020 Pleroma Authors , and is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as AGPL-3. @@ -23,7 +23,7 @@ priv/static/images/pleroma-fox-tan-shy.png --- -The following files are copyright © 2017-2019 Pleroma Authors +The following files are copyright © 2017-2020 Pleroma Authors , and are distributed under the Creative Commons Attribution-ShareAlike 4.0 International license, you should have received a copy of the license file as CC-BY-SA-4.0. diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 9b2889e92..dc4bce059 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidator do diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex index 26a57f02b..b479c3918 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do diff --git a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex index 872a12c48..908381981 100644 --- a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index ccbc7d071..2c1d38b06 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex index eea15ce1c..fc65f1b7c 100644 --- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 25f29bf63..eed53cd34 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Pipeline do diff --git a/priv/repo/migrations/20190408123347_create_conversations.exs b/priv/repo/migrations/20190408123347_create_conversations.exs index d75459e82..3eaa6136c 100644 --- a/priv/repo/migrations/20190408123347_create_conversations.exs +++ b/priv/repo/migrations/20190408123347_create_conversations.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Repo.Migrations.CreateConversations do diff --git a/test/web/activity_pub/object_validators/note_validator_test.exs b/test/web/activity_pub/object_validators/note_validator_test.exs index 2bcd75e25..30c481ffb 100644 --- a/test/web/activity_pub/object_validators/note_validator_test.exs +++ b/test/web/activity_pub/object_validators/note_validator_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do diff --git a/test/web/activity_pub/pipeline_test.exs b/test/web/activity_pub/pipeline_test.exs index 318d306af..f3c437498 100644 --- a/test/web/activity_pub/pipeline_test.exs +++ b/test/web/activity_pub/pipeline_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.PipelineTest do diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index ef91954ae..b67bd14b3 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.SideEffectsTest do From 0c60c0a76a2fcc8d13992b51704c21a35da10a0b Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 26 Mar 2020 15:44:14 +0100 Subject: [PATCH 046/114] Validators: Use correct type for IDs. --- .../web/activity_pub/object_validators/create_validator.ex | 2 +- .../web/activity_pub/object_validators/like_validator.ex | 2 +- .../web/activity_pub/object_validators/note_validator.ex | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex index 908381981..926804ce7 100644 --- a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do @primary_key false embedded_schema do - field(:id, :string, primary_key: true) + field(:id, Types.ObjectID, primary_key: true) field(:actor, Types.ObjectID) field(:type, :string) field(:to, {:array, :string}) diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index 2c1d38b06..49546ceaa 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do @primary_key false embedded_schema do - field(:id, :string, primary_key: true) + field(:id, Types.ObjectID, primary_key: true) field(:type, :string) field(:object, Types.ObjectID) field(:actor, Types.ObjectID) diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex index fc65f1b7c..c95b622e4 100644 --- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do @primary_key false embedded_schema do - field(:id, :string, primary_key: true) + field(:id, Types.ObjectID, primary_key: true) field(:to, {:array, :string}, default: []) field(:cc, {:array, :string}, default: []) field(:bto, {:array, :string}, default: []) From 69fc1dd69ff9d63af1785bb0701576cb5cde51f2 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 26 Mar 2020 14:45:28 +0000 Subject: [PATCH 047/114] Apply suggestion to lib/pleroma/web/activity_pub/pipeline.ex --- lib/pleroma/web/activity_pub/pipeline.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 25f29bf63..0068d60be 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -22,6 +22,7 @@ def common_pipeline(object, meta) do {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do {:ok, activity, meta} else + {:mrf_object, {:reject, _}} -> {:ok, nil, meta} e -> {:error, e} end end From be9d18461a5ed6bd835e2eba8d3b54ba61fc51fb Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 28 Mar 2020 18:49:03 +0300 Subject: [PATCH 048/114] FollowingRelationship storage & performance optimizations (state turned `ecto_enum`-driven integer, reorganized indices etc.). --- lib/pleroma/ecto_enums.ex | 6 +++ lib/pleroma/following_relationship.ex | 43 ++++++++++++++++--- lib/pleroma/user.ex | 18 +++++--- lib/pleroma/user/query.ex | 6 +-- lib/pleroma/web/activity_pub/mrf.ex | 2 +- .../web/activity_pub/transmogrifier.ex | 13 +++--- lib/pleroma/web/common_api/common_api.ex | 4 +- .../web/mastodon_api/views/account_view.ex | 6 +-- ...llowing_relationships_state_to_integer.exs | 29 +++++++++++++ ...owing_relationships_following_id_index.exs | 11 +++++ test/following_relationship_test.exs | 8 ++-- test/tasks/user_test.exs | 2 +- test/user_test.exs | 9 ++-- test/web/activity_pub/transmogrifier_test.exs | 2 +- test/web/common_api/common_api_test.exs | 4 +- .../follow_request_controller_test.exs | 4 +- test/web/streamer/streamer_test.exs | 6 +-- 17 files changed, 128 insertions(+), 45 deletions(-) create mode 100644 priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs create mode 100644 priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex index d9b601223..b98ac4ba1 100644 --- a/lib/pleroma/ecto_enums.ex +++ b/lib/pleroma/ecto_enums.ex @@ -11,3 +11,9 @@ notification_mute: 4, inverse_subscription: 5 ) + +defenum(FollowingRelationshipStateEnum, + follow_pending: 1, + follow_accept: 2, + follow_reject: 3 +) diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index a9538ea4e..a28da8bec 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -13,7 +13,7 @@ defmodule Pleroma.FollowingRelationship do alias Pleroma.User schema "following_relationships" do - field(:state, :string, default: "accept") + field(:state, FollowingRelationshipStateEnum, default: :follow_pending) belongs_to(:follower, User, type: CompatType) belongs_to(:following, User, type: CompatType) @@ -27,6 +27,19 @@ def changeset(%__MODULE__{} = following_relationship, attrs) do |> put_assoc(:follower, attrs.follower) |> put_assoc(:following, attrs.following) |> validate_required([:state, :follower, :following]) + |> unique_constraint(:follower_id, + name: :following_relationships_follower_id_following_id_index + ) + |> validate_not_self_relationship() + end + + def state_to_enum(state) when is_binary(state) do + case state do + "pending" -> :follow_pending + "accept" -> :follow_accept + "reject" -> :follow_reject + _ -> raise "State is not convertible to FollowingRelationshipStateEnum: #{state}" + end end def get(%User{} = follower, %User{} = following) do @@ -35,7 +48,7 @@ def get(%User{} = follower, %User{} = following) do |> Repo.one() end - def update(follower, following, "reject"), do: unfollow(follower, following) + def update(follower, following, :follow_reject), do: unfollow(follower, following) def update(%User{} = follower, %User{} = following, state) do case get(follower, following) do @@ -50,7 +63,7 @@ def update(%User{} = follower, %User{} = following, state) do end end - def follow(%User{} = follower, %User{} = following, state \\ "accept") do + def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do %__MODULE__{} |> changeset(%{follower: follower, following: following, state: state}) |> Repo.insert(on_conflict: :nothing) @@ -80,7 +93,7 @@ def following_count(%User{} = user) do def get_follow_requests(%User{id: id}) do __MODULE__ |> join(:inner, [r], f in assoc(r, :follower)) - |> where([r], r.state == "pending") + |> where([r], r.state == ^:follow_pending) |> where([r], r.following_id == ^id) |> select([r, f], f) |> Repo.all() @@ -88,7 +101,7 @@ def get_follow_requests(%User{id: id}) do def following?(%User{id: follower_id}, %User{id: followed_id}) do __MODULE__ - |> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept") + |> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept) |> Repo.exists?() end @@ -97,7 +110,7 @@ def following(%User{} = user) do __MODULE__ |> join(:inner, [r], u in User, on: r.following_id == u.id) |> where([r], r.follower_id == ^user.id) - |> where([r], r.state == "accept") + |> where([r], r.state == ^:follow_accept) |> select([r, u], u.follower_address) |> Repo.all() @@ -157,4 +170,22 @@ def find(following_relationships, follower, following) do fr -> fr.follower_id == follower.id and fr.following_id == following.id end) end + + defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do + changeset + |> validate_change(:following_id, fn _, following_id -> + if following_id == get_field(changeset, :follower_id) do + [target_id: "can't be equal to follower_id"] + else + [] + end + end) + |> validate_change(:follower_id, fn _, follower_id -> + if follower_id == get_field(changeset, :following_id) do + [source_id: "can't be equal to following_id"] + else + [] + end + end) + end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d9aa54057..6ffb82045 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -697,7 +697,7 @@ def needs_update?(_), do: true @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()} def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do - follow(follower, followed, "pending") + follow(follower, followed, :follow_pending) end def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do @@ -717,14 +717,14 @@ def maybe_direct_follow(%User{} = follower, %User{} = followed) do def follow_all(follower, followeds) do followeds |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end) - |> Enum.each(&follow(follower, &1, "accept")) + |> Enum.each(&follow(follower, &1, :follow_accept)) set_cache(follower) end defdelegate following(user), to: FollowingRelationship - def follow(%User{} = follower, %User{} = followed, state \\ "accept") do + def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) cond do @@ -751,7 +751,7 @@ def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do def unfollow(%User{} = follower, %User{} = followed) do case get_follow_state(follower, followed) do - state when state in ["accept", "pending"] -> + state when state in [:follow_pending, :follow_accept] -> FollowingRelationship.unfollow(follower, followed) {:ok, followed} = update_follower_count(followed) @@ -769,6 +769,7 @@ def unfollow(%User{} = follower, %User{} = followed) do defdelegate following?(follower, followed), to: FollowingRelationship + @doc "Returns follow state as FollowingRelationshipStateEnum value" def get_follow_state(%User{} = follower, %User{} = following) do following_relationship = FollowingRelationship.get(follower, following) get_follow_state(follower, following, following_relationship) @@ -782,8 +783,11 @@ def get_follow_state( case {following_relationship, following.local} do {nil, false} -> case Utils.fetch_latest_follow(follower, following) do - %{data: %{"state" => state}} when state in ["pending", "accept"] -> state - _ -> nil + %Activity{data: %{"state" => state}} when state in ["pending", "accept"] -> + FollowingRelationship.state_to_enum(state) + + _ -> + nil end {%{state: state}, _} -> @@ -1282,7 +1286,7 @@ def blocks?(nil, _), do: false def blocks?(%User{} = user, %User{} = target) do blocks_user?(user, target) || - (!User.following?(user, target) && blocks_domain?(user, target)) + (blocks_domain?(user, target) and not User.following?(user, target)) end def blocks_user?(%User{} = user, %User{} = target) do diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index 884e33039..ec88088cf 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -148,7 +148,7 @@ defp compose_query({:followers, %User{id: id}}, query) do as: :relationships, on: r.following_id == ^id and r.follower_id == u.id ) - |> where([relationships: r], r.state == "accept") + |> where([relationships: r], r.state == ^:follow_accept) end defp compose_query({:friends, %User{id: id}}, query) do @@ -158,7 +158,7 @@ defp compose_query({:friends, %User{id: id}}, query) do as: :relationships, on: r.following_id == u.id and r.follower_id == ^id ) - |> where([relationships: r], r.state == "accept") + |> where([relationships: r], r.state == ^:follow_accept) end defp compose_query({:recipients_from_activity, to}, query) do @@ -173,7 +173,7 @@ defp compose_query({:recipients_from_activity, to}, query) do ) |> where( [u, following: f, relationships: r], - u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept") + u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept) ) |> distinct(true) end diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index a0b3af432..f54647945 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -33,7 +33,7 @@ def subdomains_regex(domains) when is_list(domains) do @spec subdomain_match?([Regex.t()], String.t()) :: boolean() def subdomain_match?(domains, host) do - Enum.any?(domains, fn domain -> Regex.match?(domain, host) end) + !!Enum.find(domains, fn domain -> Regex.match?(domain, host) end) end @callback describe() :: {:ok | :error, Map.t()} diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index d6549a932..37e485741 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -490,7 +490,8 @@ def handle_incoming( {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)}, {_, {:ok, _}} <- {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")}, - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do + {:ok, _relationship} <- + FollowingRelationship.update(follower, followed, :follow_accept) do ActivityPub.accept(%{ to: [follower.ap_id], actor: followed, @@ -500,7 +501,7 @@ def handle_incoming( else {:user_blocked, true} -> {:ok, _} = Utils.update_follow_state_for_all(activity, "reject") - {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject") + {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject) ActivityPub.reject(%{ to: [follower.ap_id], @@ -511,7 +512,7 @@ def handle_incoming( {:follow, {:error, _}} -> {:ok, _} = Utils.update_follow_state_for_all(activity, "reject") - {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject") + {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject) ActivityPub.reject(%{ to: [follower.ap_id], @@ -521,7 +522,7 @@ def handle_incoming( }) {:user_locked, true} -> - {:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending") + {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending) :noop end @@ -541,7 +542,7 @@ def handle_incoming( {:ok, follow_activity} <- get_follow_activity(follow_object, followed), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do + {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do ActivityPub.accept(%{ to: follow_activity.data["to"], type: "Accept", @@ -564,7 +565,7 @@ def handle_incoming( {:ok, follow_activity} <- get_follow_activity(follow_object, followed), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"), + {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject), {:ok, activity} <- ActivityPub.reject(%{ to: follow_activity.data["to"], diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 2646b9f7b..d530da42c 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -42,7 +42,7 @@ def accept_follow_request(follower, followed) do with {:ok, follower} <- User.follow(follower, followed), %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"), + {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept), {:ok, _activity} <- ActivityPub.accept(%{ to: [follower.ap_id], @@ -57,7 +57,7 @@ def accept_follow_request(follower, followed) do def reject_follow_request(follower, followed) do with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"), + {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject), {:ok, _activity} <- ActivityPub.reject(%{ to: [follower.ap_id], diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 0efcabc01..f2dc2a9bd 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -71,7 +71,7 @@ def render( followed_by = if following_relationships do case FollowingRelationship.find(following_relationships, target, reading_user) do - %{state: "accept"} -> true + %{state: :follow_accept} -> true _ -> false end else @@ -81,7 +81,7 @@ def render( # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags %{ id: to_string(target.id), - following: follow_state == "accept", + following: follow_state == :follow_accept, followed_by: followed_by, blocking: UserRelationship.exists?( @@ -123,7 +123,7 @@ def render( reading_user, &User.subscribed_to?(&2, &1) ), - requested: follow_state == "pending", + requested: follow_state == :follow_pending, domain_blocking: User.blocks_domain?(reading_user, target), showing_reblogs: not UserRelationship.exists?( diff --git a/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs new file mode 100644 index 000000000..d5a431c00 --- /dev/null +++ b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs @@ -0,0 +1,29 @@ +defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do + use Ecto.Migration + + @alter_apps_scopes "ALTER TABLE following_relationships ALTER COLUMN state" + + def up do + execute(""" + #{@alter_apps_scopes} TYPE integer USING + CASE + WHEN state = 'pending' THEN 1 + WHEN state = 'accept' THEN 2 + WHEN state = 'reject' THEN 3 + ELSE 0 + END; + """) + end + + def down do + execute(""" + #{@alter_apps_scopes} TYPE varchar(255) USING + CASE + WHEN state = 1 THEN 'pending' + WHEN state = 2 THEN 'accept' + WHEN state = 3 THEN 'reject' + ELSE '' + END; + """) + end +end diff --git a/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs new file mode 100644 index 000000000..4c9faf48f --- /dev/null +++ b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs @@ -0,0 +1,11 @@ +defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do + use Ecto.Migration + + # [:follower_index] index is useless because of [:follower_id, :following_id] index + # [:following_id] index makes sense because of user's followers-targeted queries + def change do + drop_if_exists(index(:following_relationships, [:follower_id])) + + create_if_not_exists(drop_if_exists(index(:following_relationships, [:following_id]))) + end +end diff --git a/test/following_relationship_test.exs b/test/following_relationship_test.exs index 865bb3838..17a468abb 100644 --- a/test/following_relationship_test.exs +++ b/test/following_relationship_test.exs @@ -15,28 +15,28 @@ defmodule Pleroma.FollowingRelationshipTest do test "returns following addresses without internal.fetch" do user = insert(:user) fetch_actor = InternalFetchActor.get_actor() - FollowingRelationship.follow(fetch_actor, user, "accept") + FollowingRelationship.follow(fetch_actor, user, :follow_accept) assert FollowingRelationship.following(fetch_actor) == [user.follower_address] end test "returns following addresses without relay" do user = insert(:user) relay_actor = Relay.get_actor() - FollowingRelationship.follow(relay_actor, user, "accept") + FollowingRelationship.follow(relay_actor, user, :follow_accept) assert FollowingRelationship.following(relay_actor) == [user.follower_address] end test "returns following addresses without remote user" do user = insert(:user) actor = insert(:user, local: false) - FollowingRelationship.follow(actor, user, "accept") + FollowingRelationship.follow(actor, user, :follow_accept) assert FollowingRelationship.following(actor) == [user.follower_address] end test "returns following addresses with local user" do user = insert(:user) actor = insert(:user, local: true) - FollowingRelationship.follow(actor, user, "accept") + FollowingRelationship.follow(actor, user, :follow_accept) assert FollowingRelationship.following(actor) == [ actor.follower_address, diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index b45f37263..8df835b56 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -140,7 +140,7 @@ test "no user to toggle" do test "user is unsubscribed" do followed = insert(:user) user = insert(:user) - User.follow(user, followed, "accept") + User.follow(user, followed, :follow_accept) Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname]) diff --git a/test/user_test.exs b/test/user_test.exs index 8055ebd08..e7dfc5980 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -194,7 +194,8 @@ test "doesn't return already accepted or duplicate follow requests" do CommonAPI.follow(pending_follower, locked) CommonAPI.follow(pending_follower, locked) CommonAPI.follow(accepted_follower, locked) - Pleroma.FollowingRelationship.update(accepted_follower, locked, "accept") + + Pleroma.FollowingRelationship.update(accepted_follower, locked, :follow_accept) assert [^pending_follower] = User.get_follow_requests(locked) end @@ -319,7 +320,7 @@ test "unfollow with syncronizes external user" do following_address: "http://localhost:4001/users/fuser2/following" }) - {:ok, user} = User.follow(user, followed, "accept") + {:ok, user} = User.follow(user, followed, :follow_accept) {:ok, user, _activity} = User.unfollow(user, followed) @@ -332,7 +333,7 @@ test "unfollow takes a user and another user" do followed = insert(:user) user = insert(:user) - {:ok, user} = User.follow(user, followed, "accept") + {:ok, user} = User.follow(user, followed, :follow_accept) assert User.following(user) == [user.follower_address, followed.follower_address] @@ -353,7 +354,7 @@ test "unfollow doesn't unfollow yourself" do test "test if a user is following another user" do followed = insert(:user) user = insert(:user) - User.follow(user, followed, "accept") + User.follow(user, followed, :follow_accept) assert User.following?(user, followed) refute User.following?(followed, user) diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index b2cabbd30..b998f0d78 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1622,7 +1622,7 @@ test "it upgrades a user to activitypub" do }) user_two = insert(:user) - Pleroma.FollowingRelationship.follow(user_two, user, "accept") + Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept) {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"}) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 0da0bd2e2..e53a7cedd 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -562,7 +562,7 @@ test "cancels a pending follow for a local user" do assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = CommonAPI.follow(follower, followed) - assert User.get_follow_state(follower, followed) == "pending" + assert User.get_follow_state(follower, followed) == :follow_pending assert {:ok, follower} = CommonAPI.unfollow(follower, followed) assert User.get_follow_state(follower, followed) == nil @@ -584,7 +584,7 @@ test "cancels a pending follow for a remote user" do assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = CommonAPI.follow(follower, followed) - assert User.get_follow_state(follower, followed) == "pending" + assert User.get_follow_state(follower, followed) == :follow_pending assert {:ok, follower} = CommonAPI.unfollow(follower, followed) assert User.get_follow_state(follower, followed) == nil diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs index dd848821a..d8dbe4800 100644 --- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -21,7 +21,7 @@ test "/api/v1/follow_requests works", %{user: user, conn: conn} do other_user = insert(:user) {:ok, _activity} = ActivityPub.follow(other_user, user) - {:ok, other_user} = User.follow(other_user, user, "pending") + {:ok, other_user} = User.follow(other_user, user, :follow_pending) assert User.following?(other_user, user) == false @@ -35,7 +35,7 @@ test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do other_user = insert(:user) {:ok, _activity} = ActivityPub.follow(other_user, user) - {:ok, other_user} = User.follow(other_user, user, "pending") + {:ok, other_user} = User.follow(other_user, user, :follow_pending) user = User.get_cached_by_id(user.id) other_user = User.get_cached_by_id(other_user.id) diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index a5d6e8ecf..ad8ce030b 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -197,7 +197,7 @@ test "it doesn't send to user if recipients invalid and thread containment is en Pleroma.Config.put([:instance, :skip_thread_containment], false) author = insert(:user) user = insert(:user) - User.follow(user, author, "accept") + User.follow(user, author, :follow_accept) activity = insert(:note_activity, @@ -220,7 +220,7 @@ test "it sends message if recipients invalid and thread containment is disabled" Pleroma.Config.put([:instance, :skip_thread_containment], true) author = insert(:user) user = insert(:user) - User.follow(user, author, "accept") + User.follow(user, author, :follow_accept) activity = insert(:note_activity, @@ -243,7 +243,7 @@ test "it sends message if recipients invalid and thread containment is enabled b Pleroma.Config.put([:instance, :skip_thread_containment], false) author = insert(:user) user = insert(:user, skip_thread_containment: true) - User.follow(user, author, "accept") + User.follow(user, author, :follow_accept) activity = insert(:note_activity, From 9c94b6a327118d8c7ea21355d6c378ef31c54321 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 30 Mar 2020 19:08:37 +0300 Subject: [PATCH 049/114] [#2332] Misc. fixes per code change requests. --- lib/pleroma/web/activity_pub/mrf.ex | 2 +- ...328130139_add_following_relationships_following_id_index.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index f54647945..a0b3af432 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -33,7 +33,7 @@ def subdomains_regex(domains) when is_list(domains) do @spec subdomain_match?([Regex.t()], String.t()) :: boolean() def subdomain_match?(domains, host) do - !!Enum.find(domains, fn domain -> Regex.match?(domain, host) end) + Enum.any?(domains, fn domain -> Regex.match?(domain, host) end) end @callback describe() :: {:ok | :error, Map.t()} diff --git a/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs index 4c9faf48f..884832f84 100644 --- a/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs +++ b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs @@ -6,6 +6,6 @@ defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do def change do drop_if_exists(index(:following_relationships, [:follower_id])) - create_if_not_exists(drop_if_exists(index(:following_relationships, [:following_id]))) + create_if_not_exists(index(:following_relationships, [:following_id])) end end From ea9c57b26ed463622e4489736fcddb8fca1b3341 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 31 Mar 2020 09:21:42 +0300 Subject: [PATCH 050/114] [#2332] Misc. improvements per code change requests. --- lib/pleroma/ecto_enums.ex | 4 +- lib/pleroma/following_relationship.ex | 42 +++++++++++-------- lib/pleroma/user.ex | 2 +- lib/pleroma/user_relationship.ex | 33 +++++++++------ ...llowing_relationships_state_to_integer.exs | 6 +-- 5 files changed, 52 insertions(+), 35 deletions(-) diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex index b98ac4ba1..6fc47620c 100644 --- a/lib/pleroma/ecto_enums.ex +++ b/lib/pleroma/ecto_enums.ex @@ -4,7 +4,7 @@ import EctoEnum -defenum(UserRelationshipTypeEnum, +defenum(Pleroma.UserRelationship.Type, block: 1, mute: 2, reblog_mute: 3, @@ -12,7 +12,7 @@ inverse_subscription: 5 ) -defenum(FollowingRelationshipStateEnum, +defenum(Pleroma.FollowingRelationship.State, follow_pending: 1, follow_accept: 2, follow_reject: 3 diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index a28da8bec..9ccf40495 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -8,12 +8,13 @@ defmodule Pleroma.FollowingRelationship do import Ecto.Changeset import Ecto.Query + alias Ecto.Changeset alias FlakeId.Ecto.CompatType alias Pleroma.Repo alias Pleroma.User schema "following_relationships" do - field(:state, FollowingRelationshipStateEnum, default: :follow_pending) + field(:state, Pleroma.FollowingRelationship.State, default: :follow_pending) belongs_to(:follower, User, type: CompatType) belongs_to(:following, User, type: CompatType) @@ -33,13 +34,12 @@ def changeset(%__MODULE__{} = following_relationship, attrs) do |> validate_not_self_relationship() end - def state_to_enum(state) when is_binary(state) do - case state do - "pending" -> :follow_pending - "accept" -> :follow_accept - "reject" -> :follow_reject - _ -> raise "State is not convertible to FollowingRelationshipStateEnum: #{state}" - end + def state_to_enum(state) when state in ["pending", "accept", "reject"] do + String.to_existing_atom("follow_#{state}") + end + + def state_to_enum(state) do + raise "State is not convertible to Pleroma.FollowingRelationship.State: #{state}" end def get(%User{} = follower, %User{} = following) do @@ -171,16 +171,14 @@ def find(following_relationships, follower, following) do end) end - defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do + defp validate_not_self_relationship(%Changeset{} = changeset) do changeset - |> validate_change(:following_id, fn _, following_id -> - if following_id == get_field(changeset, :follower_id) do - [target_id: "can't be equal to follower_id"] - else - [] - end - end) - |> validate_change(:follower_id, fn _, follower_id -> + |> validate_follower_id_following_id_inequality() + |> validate_following_id_follower_id_inequality() + end + + defp validate_follower_id_following_id_inequality(%Changeset{} = changeset) do + validate_change(changeset, :follower_id, fn _, follower_id -> if follower_id == get_field(changeset, :following_id) do [source_id: "can't be equal to following_id"] else @@ -188,4 +186,14 @@ defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do end end) end + + defp validate_following_id_follower_id_inequality(%Changeset{} = changeset) do + validate_change(changeset, :following_id, fn _, following_id -> + if following_id == get_field(changeset, :follower_id) do + [target_id: "can't be equal to follower_id"] + else + [] + end + end) + end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 6ffb82045..4f3abd7d5 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -769,7 +769,7 @@ def unfollow(%User{} = follower, %User{} = followed) do defdelegate following?(follower, followed), to: FollowingRelationship - @doc "Returns follow state as FollowingRelationshipStateEnum value" + @doc "Returns follow state as Pleroma.FollowingRelationship.State value" def get_follow_state(%User{} = follower, %User{} = following) do following_relationship = FollowingRelationship.get(follower, following) get_follow_state(follower, following, following_relationship) diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 18a5eec72..ad0d303b1 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do import Ecto.Changeset import Ecto.Query + alias Ecto.Changeset alias Pleroma.FollowingRelationship alias Pleroma.Repo alias Pleroma.User @@ -16,12 +17,12 @@ defmodule Pleroma.UserRelationship do schema "user_relationships" do belongs_to(:source, User, type: FlakeId.Ecto.CompatType) belongs_to(:target, User, type: FlakeId.Ecto.CompatType) - field(:relationship_type, UserRelationshipTypeEnum) + field(:relationship_type, Pleroma.UserRelationship.Type) timestamps(updated_at: false) end - for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do + for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do # `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`, # `def create_notification_mute/2`, `def create_inverse_subscription/2` def unquote(:"create_#{relationship_type}")(source, target), @@ -40,7 +41,7 @@ def unquote(:"#{relationship_type}_exists?")(source, target), def user_relationship_types, do: Keyword.keys(user_relationship_mappings()) - def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__() + def user_relationship_mappings, do: Pleroma.UserRelationship.Type.__enum_map__() def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do user_relationship @@ -147,16 +148,14 @@ def view_relationships_option(%User{} = reading_user, actors) do %{user_relationships: user_relationships, following_relationships: following_relationships} end - defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do + defp validate_not_self_relationship(%Changeset{} = changeset) do changeset - |> validate_change(:target_id, fn _, target_id -> - if target_id == get_field(changeset, :source_id) do - [target_id: "can't be equal to source_id"] - else - [] - end - end) - |> validate_change(:source_id, fn _, source_id -> + |> validate_source_id_target_id_inequality() + |> validate_target_id_source_id_inequality() + end + + defp validate_source_id_target_id_inequality(%Changeset{} = changeset) do + validate_change(changeset, :source_id, fn _, source_id -> if source_id == get_field(changeset, :target_id) do [source_id: "can't be equal to target_id"] else @@ -164,4 +163,14 @@ defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do end end) end + + defp validate_target_id_source_id_inequality(%Changeset{} = changeset) do + validate_change(changeset, :target_id, fn _, target_id -> + if target_id == get_field(changeset, :source_id) do + [target_id: "can't be equal to source_id"] + else + [] + end + end) + end end diff --git a/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs index d5a431c00..2b0820f3f 100644 --- a/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs +++ b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs @@ -1,11 +1,11 @@ defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do use Ecto.Migration - @alter_apps_scopes "ALTER TABLE following_relationships ALTER COLUMN state" + @alter_following_relationship_state "ALTER TABLE following_relationships ALTER COLUMN state" def up do execute(""" - #{@alter_apps_scopes} TYPE integer USING + #{@alter_following_relationship_state} TYPE integer USING CASE WHEN state = 'pending' THEN 1 WHEN state = 'accept' THEN 2 @@ -17,7 +17,7 @@ def up do def down do execute(""" - #{@alter_apps_scopes} TYPE varchar(255) USING + #{@alter_following_relationship_state} TYPE varchar(255) USING CASE WHEN state = 1 THEN 'pending' WHEN state = 2 THEN 'accept' From f6835333be745cd411b5d2571c304fc7a16d645e Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 31 Mar 2020 12:55:25 +0000 Subject: [PATCH 051/114] Apply suggestion to lib/pleroma/web/activity_pub/transmogrifier.ex --- lib/pleroma/web/activity_pub/transmogrifier.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index dbb14e9aa..23148b2a0 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -615,8 +615,7 @@ def handle_incoming(%{"type" => "Like"} = data, _options) do with {_, {:ok, cast_data_sym}} <- {:casting_data, data |> LikeValidator.cast_data() |> Ecto.Changeset.apply_action(:insert)}, - {_, cast_data} <- - {:stringify_keys, ObjectValidator.stringify_keys(cast_data_sym |> Map.from_struct())}, + cast_data = ObjectValidator.stringify_keys(Map.from_struct(cast_data_sym)), :ok <- ObjectValidator.fetch_actor_and_object(cast_data), {_, {:ok, cast_data}} <- {:maybe_add_context, maybe_add_context_from_object(cast_data)}, {_, {:ok, cast_data}} <- From d191b0942f64a32a2bf450318fac85981aa17c83 Mon Sep 17 00:00:00 2001 From: kPherox Date: Tue, 31 Mar 2020 22:48:42 +0900 Subject: [PATCH 052/114] Remove no longer used function --- lib/pleroma/user.ex | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d9aa54057..6644d6b66 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1983,17 +1983,6 @@ def fields(%{fields: nil}), do: [] def fields(%{fields: fields}), do: fields - def sanitized_fields(%User{} = user) do - user - |> User.fields() - |> Enum.map(fn %{"name" => name, "value" => value} -> - %{ - "name" => name, - "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) - } - end) - end - def validate_fields(changeset, remote? \\ false) do limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields limit = Pleroma.Config.get([:instance, limit_name], 0) From 643f15e77b7cdaaf2c22a876c98e5680edc32dc3 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 31 Mar 2020 16:11:38 +0200 Subject: [PATCH 053/114] Validators: ObjectID is an http uri. --- .../object_validators/types/object.ex | 16 ++++++-- .../types/object_id_test.exs | 38 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 test/web/activity_pub/object_validators/types/object_id_test.exs diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object.ex b/lib/pleroma/web/activity_pub/object_validators/types/object.ex index 92fc13ba8..8e70effe4 100644 --- a/lib/pleroma/web/activity_pub/object_validators/types/object.ex +++ b/lib/pleroma/web/activity_pub/object_validators/types/object.ex @@ -4,12 +4,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do def type, do: :string def cast(object) when is_binary(object) do - {:ok, object} + with %URI{ + scheme: scheme, + host: host + } + when scheme in ["https", "http"] and not is_nil(host) <- + URI.parse(object) do + {:ok, object} + else + _ -> + :error + end end - def cast(%{"id" => object}) when is_binary(object) do - {:ok, object} - end + def cast(%{"id" => object}), do: cast(object) def cast(_) do :error diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/web/activity_pub/object_validators/types/object_id_test.exs new file mode 100644 index 000000000..f4c5ed1dc --- /dev/null +++ b/test/web/activity_pub/object_validators/types/object_id_test.exs @@ -0,0 +1,38 @@ +defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do + alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID + use Pleroma.DataCase + + @uris [ + "http://lain.com/users/lain", + "http://lain.com", + "https://lain.com/object/1" + ] + + @non_uris [ + "https://", + "rin" + ] + + test "it rejects integers" do + assert :error == ObjectID.cast(1) + end + + test "it accepts http uris" do + Enum.each(@uris, fn uri -> + assert {:ok, uri} == ObjectID.cast(uri) + end) + end + + test "it accepts an object with a nested uri id" do + Enum.each(@uris, fn uri -> + assert {:ok, uri} == ObjectID.cast(%{"id" => uri}) + end) + end + + test "it rejects non-uri strings" do + Enum.each(@non_uris, fn non_uri -> + assert :error == ObjectID.cast(non_uri) + assert :error == ObjectID.cast(%{"id" => non_uri}) + end) + end +end From df5f89c0d6d8d385434d5d8a51719fa41631d7b2 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 31 Mar 2020 18:22:25 +0300 Subject: [PATCH 054/114] test for default features and changelog entry --- CHANGELOG.md | 1 + test/web/node_info_test.exs | 92 +++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 350e03894..747d84d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. +- NodeInfo: `pleroma_emoji_reactions` to the `features` list. - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
API Changes diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index e5eebced1..9bcc07b37 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -7,6 +7,8 @@ defmodule Pleroma.Web.NodeInfoTest do import Pleroma.Factory + alias Pleroma.Config + setup do: clear_config([:mrf_simple]) setup do: clear_config(:instance) @@ -47,7 +49,7 @@ test "nodeinfo shows restricted nicknames", %{conn: conn} do assert result = json_response(conn, 200) - assert Pleroma.Config.get([Pleroma.User, :restricted_nicknames]) == + assert Config.get([Pleroma.User, :restricted_nicknames]) == result["metadata"]["restrictedNicknames"] end @@ -65,10 +67,10 @@ test "returns software.repository field in nodeinfo 2.1", %{conn: conn} do end test "returns fieldsLimits field", %{conn: conn} do - Pleroma.Config.put([:instance, :max_account_fields], 10) - Pleroma.Config.put([:instance, :max_remote_account_fields], 15) - Pleroma.Config.put([:instance, :account_field_name_length], 255) - Pleroma.Config.put([:instance, :account_field_value_length], 2048) + Config.put([:instance, :max_account_fields], 10) + Config.put([:instance, :max_remote_account_fields], 15) + Config.put([:instance, :account_field_name_length], 255) + Config.put([:instance, :account_field_value_length], 2048) response = conn @@ -82,8 +84,8 @@ test "returns fieldsLimits field", %{conn: conn} do end test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do - option = Pleroma.Config.get([:instance, :safe_dm_mentions]) - Pleroma.Config.put([:instance, :safe_dm_mentions], true) + option = Config.get([:instance, :safe_dm_mentions]) + Config.put([:instance, :safe_dm_mentions], true) response = conn @@ -92,7 +94,7 @@ test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do assert "safe_dm_mentions" in response["metadata"]["features"] - Pleroma.Config.put([:instance, :safe_dm_mentions], false) + Config.put([:instance, :safe_dm_mentions], false) response = conn @@ -101,14 +103,14 @@ test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do refute "safe_dm_mentions" in response["metadata"]["features"] - Pleroma.Config.put([:instance, :safe_dm_mentions], option) + Config.put([:instance, :safe_dm_mentions], option) end describe "`metadata/federation/enabled`" do setup do: clear_config([:instance, :federating]) test "it shows if federation is enabled/disabled", %{conn: conn} do - Pleroma.Config.put([:instance, :federating], true) + Config.put([:instance, :federating], true) response = conn @@ -117,7 +119,7 @@ test "it shows if federation is enabled/disabled", %{conn: conn} do assert response["metadata"]["federation"]["enabled"] == true - Pleroma.Config.put([:instance, :federating], false) + Config.put([:instance, :federating], false) response = conn @@ -134,31 +136,33 @@ test "it shows default features flags", %{conn: conn} do |> get("/nodeinfo/2.1.json") |> json_response(:ok) - assert response["metadata"]["features"] -- - [ - "pleroma_api", - "mastodon_api", - "mastodon_api_streaming", - "polls", - "pleroma_explicit_addressing", - "shareable_emoji_packs", - "multifetch", - "chat", - "relay", - "pleroma_emoji_reactions", - "pleroma:api/v1/notifications:include_types_filter" - ] == [] + default_features = [ + "pleroma_api", + "mastodon_api", + "mastodon_api_streaming", + "polls", + "pleroma_explicit_addressing", + "shareable_emoji_packs", + "multifetch", + "pleroma_emoji_reactions", + "pleroma:api/v1/notifications:include_types_filter" + ] + + assert MapSet.subset?( + MapSet.new(default_features), + MapSet.new(response["metadata"]["features"]) + ) end test "it shows MRF transparency data if enabled", %{conn: conn} do - config = Pleroma.Config.get([:instance, :rewrite_policy]) - Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) + config = Config.get([:instance, :rewrite_policy]) + Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) - option = Pleroma.Config.get([:instance, :mrf_transparency]) - Pleroma.Config.put([:instance, :mrf_transparency], true) + option = Config.get([:instance, :mrf_transparency]) + Config.put([:instance, :mrf_transparency], true) simple_config = %{"reject" => ["example.com"]} - Pleroma.Config.put(:mrf_simple, simple_config) + Config.put(:mrf_simple, simple_config) response = conn @@ -167,25 +171,25 @@ test "it shows MRF transparency data if enabled", %{conn: conn} do assert response["metadata"]["federation"]["mrf_simple"] == simple_config - Pleroma.Config.put([:instance, :rewrite_policy], config) - Pleroma.Config.put([:instance, :mrf_transparency], option) - Pleroma.Config.put(:mrf_simple, %{}) + Config.put([:instance, :rewrite_policy], config) + Config.put([:instance, :mrf_transparency], option) + Config.put(:mrf_simple, %{}) end test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do - config = Pleroma.Config.get([:instance, :rewrite_policy]) - Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) + config = Config.get([:instance, :rewrite_policy]) + Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) - option = Pleroma.Config.get([:instance, :mrf_transparency]) - Pleroma.Config.put([:instance, :mrf_transparency], true) + option = Config.get([:instance, :mrf_transparency]) + Config.put([:instance, :mrf_transparency], true) - exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions]) - Pleroma.Config.put([:instance, :mrf_transparency_exclusions], ["other.site"]) + exclusions = Config.get([:instance, :mrf_transparency_exclusions]) + Config.put([:instance, :mrf_transparency_exclusions], ["other.site"]) simple_config = %{"reject" => ["example.com", "other.site"]} expected_config = %{"reject" => ["example.com"]} - Pleroma.Config.put(:mrf_simple, simple_config) + Config.put(:mrf_simple, simple_config) response = conn @@ -195,9 +199,9 @@ test "it performs exclusions from MRF transparency data if configured", %{conn: assert response["metadata"]["federation"]["mrf_simple"] == expected_config assert response["metadata"]["federation"]["exclusions"] == true - Pleroma.Config.put([:instance, :rewrite_policy], config) - Pleroma.Config.put([:instance, :mrf_transparency], option) - Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions) - Pleroma.Config.put(:mrf_simple, %{}) + Config.put([:instance, :rewrite_policy], config) + Config.put([:instance, :mrf_transparency], option) + Config.put([:instance, :mrf_transparency_exclusions], exclusions) + Config.put(:mrf_simple, %{}) end end From 7af0959a07ebd5f8242704658ccb770d86fdb4c6 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 31 Mar 2020 18:30:19 +0300 Subject: [PATCH 055/114] updating docs --- docs/API/pleroma_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index 12e63ef9f..90c43c356 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -431,7 +431,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa # Emoji Reactions -Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character. +Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character. To detect the presence of this feature, you can check `pleroma_emoji_reactions` entry in the features list of nodeinfo. ## `PUT /api/v1/pleroma/statuses/:id/reactions/:emoji` ### React to a post with a unicode emoji From aebec1bac9831da2bed5ee571225d92dc99a5d59 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 31 Mar 2020 17:47:34 +0200 Subject: [PATCH 056/114] Validator Test: Small refactor. --- .../object_validators/types/object_id_test.exs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/web/activity_pub/object_validators/types/object_id_test.exs index f4c5ed1dc..834213182 100644 --- a/test/web/activity_pub/object_validators/types/object_id_test.exs +++ b/test/web/activity_pub/object_validators/types/object_id_test.exs @@ -10,13 +10,12 @@ defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do @non_uris [ "https://", - "rin" + "rin", + 1, + :x, + %{"1" => 2} ] - test "it rejects integers" do - assert :error == ObjectID.cast(1) - end - test "it accepts http uris" do Enum.each(@uris, fn uri -> assert {:ok, uri} == ObjectID.cast(uri) From 057438a657eaadb963e006b84b890ae4f8441808 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 31 Mar 2020 17:56:05 +0200 Subject: [PATCH 057/114] CommonAPI: DRY up a bit. --- lib/pleroma/web/common_api/common_api.ex | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index f882f9fcb..74adcca55 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -112,8 +112,22 @@ def unrepeat(id_or_ap_id, user) do end end - @spec favorite(User.t(), binary()) :: {:ok, Activity.t()} | {:error, any()} + @spec favorite(User.t(), binary()) :: {:ok, Activity.t() | :already_liked} | {:error, any()} def favorite(%User{} = user, id) do + case favorite_helper(user, id) do + {:ok, _} = res -> + res + + {:error, :not_found} = res -> + res + + {:error, e} -> + Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}") + {:error, dgettext("errors", "Could not favorite")} + end + end + + def favorite_helper(user, id) do with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)}, {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)}, {_, {:ok, %Activity{} = activity, _meta}} <- @@ -138,13 +152,11 @@ def favorite(%User{} = user, id) do if {:object, {"already liked by this actor", []}} in changeset.errors do {:ok, :already_liked} else - Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}") - {:error, dgettext("errors", "Could not favorite"), e} + {:error, e} end e -> - Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}") - {:error, dgettext("errors", "Could not favorite"), e} + {:error, e} end end From 0be1fa0a8695df87a8b22279b885956943e33796 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 31 Mar 2020 17:00:48 +0000 Subject: [PATCH 058/114] Apply suggestion to lib/pleroma/web/activity_pub/transmogrifier.ex --- lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 23148b2a0..fb41ec8e9 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1291,6 +1291,6 @@ defp maybe_add_recipients_from_object(%{"object" => object} = data) do end defp maybe_add_recipients_from_object(_) do - {:error, "No referenced object"} + {:error, :no_object} end end From 288f2b5a7c728959d43205a97d5225b34b5b8161 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 31 Mar 2020 17:00:55 +0000 Subject: [PATCH 059/114] Apply suggestion to lib/pleroma/web/activity_pub/transmogrifier.ex --- lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index fb41ec8e9..a3529f09b 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1267,7 +1267,7 @@ defp maybe_add_context_from_object(%{"object" => object} = data) when is_binary( end defp maybe_add_context_from_object(_) do - {:error, "No referenced object"} + {:error, :no_context} end defp maybe_add_recipients_from_object(%{"object" => object} = data) do From ecac57732a063c1ad01aeb5aa4eb9853b6f904e9 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 31 Mar 2020 19:16:45 +0200 Subject: [PATCH 060/114] Transmogrifier: Only add context if it really is onne. --- lib/pleroma/web/activity_pub/transmogrifier.ex | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a3529f09b..f82142979 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1255,14 +1255,11 @@ defp maybe_add_context_from_object(%{"context" => context} = data) when is_binar do: {:ok, data} defp maybe_add_context_from_object(%{"object" => object} = data) when is_binary(object) do - if object = Object.normalize(object) do - data = - data - |> Map.put("context", object.data["context"]) - - {:ok, data} + with %{data: %{"context" => context}} when is_binary(context) <- Object.normalize(object) do + {:ok, Map.put(data, "context", context)} else - {:error, "No context on referenced object"} + _ -> + {:error, :no_context} end end From 1b323ce1c668c6a26617a05dcc12ee255c764e88 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 31 Mar 2020 17:28:18 +0000 Subject: [PATCH 061/114] Apply suggestion to lib/pleroma/web/activity_pub/transmogrifier.ex --- .../web/activity_pub/transmogrifier.ex | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index f82142979..a18ece6e7 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1267,24 +1267,19 @@ defp maybe_add_context_from_object(_) do {:error, :no_context} end - defp maybe_add_recipients_from_object(%{"object" => object} = data) do - to = data["to"] || [] - cc = data["cc"] || [] + defp maybe_add_recipients_from_object(%{"to" => [_ | _], "cc" => [_ | _]} = data), do: {:ok, data} - if to == [] && cc == [] do - if object = Object.normalize(object) do + defp maybe_add_recipients_from_object(%{"object" => object} = data) do + case Object.normalize(object) do + %{data: {"actor" => actor}} -> data = data - |> Map.put("to", [object.data["actor"]]) - |> Map.put("cc", cc) + |> Map.put("to", [actor]) + |> Map.put("cc", data["cc"] || []) {:ok, data} - else - {:error, "No actor on referenced object"} - end - else - {:ok, data} - end + nil -> {:error, :no_object} + _ -> {:error, :no_actor} end defp maybe_add_recipients_from_object(_) do From c982093cc2f538e8ef9dde365e163a944c6cb6d0 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 31 Mar 2020 19:33:41 +0200 Subject: [PATCH 062/114] Transmogrifier: Fix BAD code by RINPATCH --- lib/pleroma/web/activity_pub/transmogrifier.ex | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a18ece6e7..a4b385cd5 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1267,19 +1267,25 @@ defp maybe_add_context_from_object(_) do {:error, :no_context} end - defp maybe_add_recipients_from_object(%{"to" => [_ | _], "cc" => [_ | _]} = data), do: {:ok, data} + defp maybe_add_recipients_from_object(%{"to" => [_ | _], "cc" => [_ | _]} = data), + do: {:ok, data} defp maybe_add_recipients_from_object(%{"object" => object} = data) do case Object.normalize(object) do - %{data: {"actor" => actor}} -> + %{data: %{"actor" => actor}} -> data = data |> Map.put("to", [actor]) |> Map.put("cc", data["cc"] || []) {:ok, data} - nil -> {:error, :no_object} - _ -> {:error, :no_actor} + + nil -> + {:error, :no_object} + + _ -> + {:error, :no_actor} + end end defp maybe_add_recipients_from_object(_) do From dbf9d719f98770056ac906b3087e7ed501cd64e6 Mon Sep 17 00:00:00 2001 From: kPherox Date: Wed, 1 Apr 2020 00:05:13 +0900 Subject: [PATCH 063/114] split test for update profile fields --- .../update_credentials_test.exs | 98 ++++++++++--------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs index b693c1a47..8687d7995 100644 --- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -273,7 +273,7 @@ test "updates profile emojos", %{user: user, conn: conn} do test "update fields", %{conn: conn} do fields = [ %{"name" => "foo", "value" => ""}, - %{"name" => "link", "value" => "cofe.io"} + %{"name" => "link.io", "value" => "cofe.io"} ] account_data = @@ -283,7 +283,10 @@ test "update fields", %{conn: conn} do assert account_data["fields"] == [ %{"name" => "foo", "value" => "bar"}, - %{"name" => "link", "value" => ~S(cofe.io)} + %{ + "name" => "link.io", + "value" => ~S(cofe.io) + } ] assert account_data["source"]["fields"] == [ @@ -291,14 +294,16 @@ test "update fields", %{conn: conn} do "name" => "foo", "value" => "" }, - %{"name" => "link", "value" => "cofe.io"} + %{"name" => "link.io", "value" => "cofe.io"} ] + end + test "update fields by urlencoded", %{conn: conn} do fields = [ "fields_attributes[1][name]=link", - "fields_attributes[1][value]=cofe.io", - "fields_attributes[0][name]=foo", + "fields_attributes[1][value]=http://cofe.io", + "fields_attributes[0][name]=foo", "fields_attributes[0][value]=bar" ] |> Enum.join("&") @@ -310,51 +315,20 @@ test "update fields", %{conn: conn} do |> json_response(200) assert account["fields"] == [ - %{"name" => "foo", "value" => "bar"}, - %{"name" => "link", "value" => ~S(cofe.io)} + %{"name" => "foo", "value" => "bar"}, + %{ + "name" => "link", + "value" => ~S(http://cofe.io) + } ] assert account["source"]["fields"] == [ - %{ - "name" => "foo", - "value" => "bar" - }, - %{"name" => "link", "value" => "cofe.io"} + %{"name" => "foo", "value" => "bar"}, + %{"name" => "link", "value" => "http://cofe.io"} ] + end - name_limit = Pleroma.Config.get([:instance, :account_field_name_length]) - value_limit = Pleroma.Config.get([:instance, :account_field_value_length]) - - long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join() - - fields = [%{"name" => "foo", "value" => long_value}] - - assert %{"error" => "Invalid request"} == - conn - |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) - |> json_response(403) - - long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join() - - fields = [%{"name" => long_name, "value" => "bar"}] - - assert %{"error" => "Invalid request"} == - conn - |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) - |> json_response(403) - - Pleroma.Config.put([:instance, :max_account_fields], 1) - - fields = [ - %{"name" => "foo", "value" => "bar"}, - %{"name" => "link", "value" => "cofe.io"} - ] - - assert %{"error" => "Invalid request"} == - conn - |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) - |> json_response(403) - + test "update fields with empty name", %{conn: conn} do fields = [ %{"name" => "foo", "value" => ""}, %{"name" => "", "value" => "bar"} @@ -369,5 +343,39 @@ test "update fields", %{conn: conn} do %{"name" => "foo", "value" => ""} ] end + + test "update fields when invalid request", %{conn: conn} do + name_limit = Pleroma.Config.get([:instance, :account_field_name_length]) + value_limit = Pleroma.Config.get([:instance, :account_field_value_length]) + + long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join() + long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join() + + fields = [%{"name" => "foo", "value" => long_value}] + + assert %{"error" => "Invalid request"} == + conn + |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) + |> json_response(403) + + fields = [%{"name" => long_name, "value" => "bar"}] + + assert %{"error" => "Invalid request"} == + conn + |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) + |> json_response(403) + + Pleroma.Config.put([:instance, :max_account_fields], 1) + + fields = [ + %{"name" => "foo", "value" => "bar"}, + %{"name" => "link", "value" => "cofe.io"} + ] + + assert %{"error" => "Invalid request"} == + conn + |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) + |> json_response(403) + end end end From 7408f003a663c5f634cabad963c0446ba54810bf Mon Sep 17 00:00:00 2001 From: kPherox Date: Tue, 31 Mar 2020 11:13:53 +0000 Subject: [PATCH 064/114] Use `Pleroma.Formatter.linkify` instead of `AutoLinker.link` --- lib/pleroma/user.ex | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 6644d6b66..c29935871 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -16,6 +16,7 @@ defmodule Pleroma.User do alias Pleroma.Conversation.Participation alias Pleroma.Delivery alias Pleroma.FollowingRelationship + alias Pleroma.Formatter alias Pleroma.HTML alias Pleroma.Keys alias Pleroma.Notification @@ -456,7 +457,7 @@ defp put_fields(changeset) do fields = raw_fields - |> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end) + |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end) changeset |> put_change(:raw_fields, raw_fields) @@ -466,6 +467,12 @@ defp put_fields(changeset) do end end + defp parse_fields(value) do + value + |> Formatter.linkify(mentions_format: :full) + |> elem(0) + end + defp put_change_if_present(changeset, map_field, value_function) do if value = get_change(changeset, map_field) do with {:ok, new_value} <- value_function.(value) do From 037b49c415060b4c7ad5a570da80857b4d2c43f1 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 1 Apr 2020 16:10:17 +0200 Subject: [PATCH 065/114] Validators: Correct ObjectID filename --- .../object_validators/types/{object.ex => object_id.ex} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/pleroma/web/activity_pub/object_validators/types/{object.ex => object_id.ex} (100%) diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object.ex b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex similarity index 100% rename from lib/pleroma/web/activity_pub/object_validators/types/object.ex rename to lib/pleroma/web/activity_pub/object_validators/types/object_id.ex From 2f2bd7fe72f474b7177c751a2dc3af716622ba91 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 1 Apr 2020 19:49:09 +0300 Subject: [PATCH 066/114] Ability to control the output of account/pleroma/relationship in statuses in order to improve the rendering performance. See `[:extensions, output_relationships_in_statuses_by_default]` setting and `with_relationships` param. --- CHANGELOG.md | 3 +- benchmarks/load_testing/fetcher.ex | 21 +++++++--- config/config.exs | 2 + config/description.exs | 16 ++++++++ lib/mix/tasks/pleroma/benchmark.ex | 3 +- lib/pleroma/user_relationship.ex | 18 +++++++-- .../web/admin_api/admin_api_controller.ex | 6 +-- .../web/admin_api/views/report_view.ex | 7 +++- lib/pleroma/web/common_api/activity_draft.ex | 2 +- lib/pleroma/web/controller_helper.ex | 24 +++++++++-- .../controllers/account_controller.ex | 15 ++++++- .../controllers/notification_controller.ex | 8 +++- .../controllers/search_controller.ex | 24 ++++++++--- .../controllers/status_controller.ex | 26 +++++++++--- .../controllers/timeline_controller.ex | 40 +++++++++++++++---- .../web/mastodon_api/views/account_view.ex | 15 ++++--- .../mastodon_api/views/notification_view.ex | 30 ++++++++------ .../web/mastodon_api/views/status_view.ex | 11 +++-- .../controllers/account_controller.ex | 9 ++++- .../controllers/pleroma_api_controller.ex | 17 ++++++-- .../20190414125034_migrate_old_bookmarks.exs | 1 - .../20190711042021_create_safe_jsonb_set.exs | 1 - .../notification_controller_test.exs | 20 ++++++++++ .../controllers/status_controller_test.exs | 6 ++- .../controllers/timeline_controller_test.exs | 29 ++++++++++++-- 25 files changed, 278 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 350e03894..a391bf1fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses. +- Configuration: `:extensions/:output_relationships_in_statuses_by_default` option (if `false`, disables the output of account/pleroma/relationship for statuses and notifications by default, breaking the compatibility with older PleromaFE versions).
API Changes - Mastodon API: Support for `include_types` in `/api/v1/notifications`. @@ -20,7 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [2.0.0] - 2019-03-08 ### Security -- Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request. +- Mastodon API: Fix being able to request enormous amount of statuses in timelines leading to DoS. Now limited to 40 per request. ### Removed - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media` diff --git a/benchmarks/load_testing/fetcher.ex b/benchmarks/load_testing/fetcher.ex index bd65ac84f..786929ace 100644 --- a/benchmarks/load_testing/fetcher.ex +++ b/benchmarks/load_testing/fetcher.ex @@ -386,47 +386,56 @@ defp render_timelines(user) do favourites = ActivityPub.fetch_favourites(user) + output_relationships = + !!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default]) + Benchee.run( %{ "Rendering home timeline" => fn -> StatusView.render("index.json", %{ activities: home_activities, for: user, - as: :activity + as: :activity, + skip_relationships: !output_relationships }) end, "Rendering direct timeline" => fn -> StatusView.render("index.json", %{ activities: direct_activities, for: user, - as: :activity + as: :activity, + skip_relationships: !output_relationships }) end, "Rendering public timeline" => fn -> StatusView.render("index.json", %{ activities: public_activities, for: user, - as: :activity + as: :activity, + skip_relationships: !output_relationships }) end, "Rendering tag timeline" => fn -> StatusView.render("index.json", %{ activities: tag_activities, for: user, - as: :activity + as: :activity, + skip_relationships: !output_relationships }) end, "Rendering notifications" => fn -> Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{ notifications: notifications, - for: user + for: user, + skip_relationships: !output_relationships }) end, "Rendering favourites timeline" => fn -> StatusView.render("index.json", %{ activities: favourites, for: user, - as: :activity + as: :activity, + skip_relationships: !output_relationships }) end }, diff --git a/config/config.exs b/config/config.exs index 2ab939107..73bf658fe 100644 --- a/config/config.exs +++ b/config/config.exs @@ -262,6 +262,8 @@ extended_nickname_format: true, cleanup_attachments: false +config :pleroma, :extensions, output_relationships_in_statuses_by_default: true + config :pleroma, :feed, post_title: %{ max_length: 100, diff --git a/config/description.exs b/config/description.exs index 9612adba7..d127f8f20 100644 --- a/config/description.exs +++ b/config/description.exs @@ -121,6 +121,22 @@ } ] }, + %{ + group: :pleroma, + key: :extensions, + type: :group, + description: "Pleroma-specific extensions", + children: [ + %{ + key: :output_relationships_in_statuses_by_default, + type: :beeolean, + description: + "If `true`, outputs account/pleroma/relationship map for each rendered status / notification (for all clients). " <> + "If `false`, outputs the above only if `with_relationships` param is tru-ish " <> + "(that breaks compatibility with older PleromaFE versions which do not send this param but expect the output)." + } + ] + }, %{ group: :pleroma, key: Pleroma.Uploaders.Local, diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index a4885b70c..b2bbe40ac 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -67,7 +67,8 @@ def run(["render_timeline", nickname | _] = args) do Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ activities: activities, for: user, - as: :activity + as: :activity, + skip_relationships: true }) end }, diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 18a5eec72..d42dc250e 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -129,17 +129,27 @@ def exists?(dictionary, rel_type, source, target, func) do end @doc ":relationships option for StatusView / AccountView / NotificationView" - def view_relationships_option(nil = _reading_user, _actors) do + def view_relationships_option(reading_user, actors, opts \\ []) + + def view_relationships_option(nil = _reading_user, _actors, _opts) do %{user_relationships: [], following_relationships: []} end - def view_relationships_option(%User{} = reading_user, actors) do + def view_relationships_option(%User{} = reading_user, actors, opts) do + {source_to_target_rel_types, target_to_source_rel_types} = + if opts[:source_mutes_only] do + # This option is used for rendering statuses (FE needs `muted` flag for each one anyways) + {[:mute], []} + else + {[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]} + end + user_relationships = UserRelationship.dictionary( [reading_user], actors, - [:block, :mute, :notification_mute, :reblog_mute], - [:block, :inverse_subscription] + source_to_target_rel_types, + target_to_source_rel_types ) following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index ca5439920..747d97f80 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -258,7 +258,7 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do conn |> put_view(Pleroma.Web.AdminAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity}) + |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false}) end def list_user_statuses(conn, %{"nickname" => nickname} = params) do @@ -277,7 +277,7 @@ def list_user_statuses(conn, %{"nickname" => nickname} = params) do conn |> put_view(StatusView) - |> render("index.json", %{activities: activities, as: :activity}) + |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false}) else _ -> {:error, :not_found} end @@ -801,7 +801,7 @@ def list_statuses(%{assigns: %{user: _admin}} = conn, params) do conn |> put_view(Pleroma.Web.AdminAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity}) + |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false}) end def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index ca0bcebc7..d50969b2a 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -38,7 +38,12 @@ def render("show.json", %{report: report, user: user, account: account, statuses actor: merge_account_views(user), content: content, created_at: created_at, - statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}), + statuses: + StatusView.render("index.json", %{ + activities: statuses, + as: :activity, + skip_relationships: false + }), state: report.data["state"], notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}) } diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index c4356f93b..c1cd15bb2 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -187,7 +187,7 @@ defp object(draft) do end defp preview?(draft) do - preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false + preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) %__MODULE__{draft | preview?: preview?} end diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index b49523ec3..4780081b2 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -5,10 +5,18 @@ defmodule Pleroma.Web.ControllerHelper do use Pleroma.Web, :controller - # As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html + alias Pleroma.Config + + # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"] - def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil - def truthy_param?(value), do: value not in @falsy_param_values + + def explicitly_falsy_param?(value), do: value in @falsy_param_values + + # Note: `nil` and `""` are considered falsy values in Pleroma + def falsy_param?(value), + do: explicitly_falsy_param?(value) or value in [nil, ""] + + def truthy_param?(value), do: not falsy_param?(value) def json_response(conn, status, json) do conn @@ -96,4 +104,14 @@ def try_render(conn, _, _) do def put_if_exist(map, _key, nil), do: map def put_if_exist(map, key, value), do: Map.put(map, key, value) + + @doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications" + def skip_relationships?(params) do + if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do + false + else + # BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships. + not truthy_param?(params["with_relationships"]) + end + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 21bc3d5a5..7da1a11f6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -6,7 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do use Pleroma.Web, :controller import Pleroma.Web.ControllerHelper, - only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3] + only: [ + add_link_headers: 2, + truthy_param?: 1, + assign_account_by_id: 2, + json_response: 3, + skip_relationships?: 1 + ] alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.RateLimiter @@ -237,7 +243,12 @@ def statuses(%{assigns: %{user: reading_user}} = conn, params) do conn |> add_link_headers(activities) |> put_view(StatusView) - |> render("index.json", activities: activities, for: reading_user, as: :activity) + |> render("index.json", + activities: activities, + for: reading_user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) else _e -> render_error(conn, :not_found, "Can't find user") end diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 0c9218454..c7e808253 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1] alias Pleroma.Notification alias Pleroma.Plugs.OAuthScopesPlug @@ -45,7 +45,11 @@ def index(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(notifications) - |> render("index.json", notifications: notifications, for: user) + |> render("index.json", + notifications: notifications, + for: user, + skip_relationships: skip_relationships?(params) + ) end # GET /api/v1/notifications/:id diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index fcab4ef63..c258742dd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -5,13 +5,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1] + alias Pleroma.Activity alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.RateLimiter alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web - alias Pleroma.Web.ControllerHelper alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.StatusView @@ -66,10 +67,11 @@ defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = para defp search_options(params, user) do [ + skip_relationships: skip_relationships?(params), resolve: params["resolve"] == "true", following: params["following"] == "true", - limit: ControllerHelper.fetch_integer_param(params, "limit"), - offset: ControllerHelper.fetch_integer_param(params, "offset"), + limit: fetch_integer_param(params, "limit"), + offset: fetch_integer_param(params, "offset"), type: params["type"], author: get_author(params), for_user: user @@ -79,12 +81,24 @@ defp search_options(params, user) do defp resource_search(_, "accounts", query, options) do accounts = with_fallback(fn -> User.search(query, options) end) - AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user) + + AccountView.render("index.json", + users: accounts, + for: options[:for_user], + as: :user, + skip_relationships: false + ) end defp resource_search(_, "statuses", query, options) do statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end) - StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity) + + StatusView.render("index.json", + activities: statuses, + for: options[:for_user], + as: :activity, + skip_relationships: options[:skip_relationships] + ) end defp resource_search(:v2, "hashtags", query, _options) do diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 37afe6949..eb3d90aeb 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -5,7 +5,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2] + import Pleroma.Web.ControllerHelper, + only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1] require Ecto.Query @@ -101,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do `ids` query param is required """ - def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do + def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do limit = 100 activities = @@ -110,7 +111,12 @@ def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do |> Activity.all_by_ids_with_object() |> Enum.filter(&Visibility.visible_for_user?(&1, user)) - render(conn, "index.json", activities: activities, for: user, as: :activity) + render(conn, "index.json", + activities: activities, + for: user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) end @doc """ @@ -360,7 +366,12 @@ def favourites(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(activities) - |> render("index.json", activities: activities, for: user, as: :activity) + |> render("index.json", + activities: activities, + for: user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) end @doc "GET /api/v1/bookmarks" @@ -378,6 +389,11 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(bookmarks) - |> render("index.json", %{activities: activities, for: user, as: :activity}) + |> render("index.json", + activities: activities, + for: user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) end end diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 91f41416d..b3c58005e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do use Pleroma.Web, :controller import Pleroma.Web.ControllerHelper, - only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1] + only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1] alias Pleroma.Pagination alias Pleroma.Plugs.OAuthScopesPlug @@ -14,9 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub - # TODO: Replace with a macro when there is a Phoenix release with + # TODO: Replace with a macro when there is a Phoenix release with the following commit in it: # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e - # in it plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct) plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public) @@ -49,7 +48,12 @@ def home(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(activities) - |> render("index.json", activities: activities, for: user, as: :activity) + |> render("index.json", + activities: activities, + for: user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) end # GET /api/v1/timelines/direct @@ -68,7 +72,12 @@ def direct(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(activities) - |> render("index.json", activities: activities, for: user, as: :activity) + |> render("index.json", + activities: activities, + for: user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) end # GET /api/v1/timelines/public @@ -95,7 +104,12 @@ def public(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(activities, %{"local" => local_only}) - |> render("index.json", activities: activities, for: user, as: :activity) + |> render("index.json", + activities: activities, + for: user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) else render_error(conn, :unauthorized, "authorization required for timeline view") end @@ -140,7 +154,12 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do conn |> add_link_headers(activities, %{"local" => local_only}) - |> render("index.json", activities: activities, for: user, as: :activity) + |> render("index.json", + activities: activities, + for: user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) end # GET /api/v1/timelines/list/:list_id @@ -164,7 +183,12 @@ def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do |> ActivityPub.fetch_activities_bounded(following, params) |> Enum.reverse() - render(conn, "index.json", activities: activities, for: user, as: :activity) + render(conn, "index.json", + activities: activities, + for: user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) else _e -> render_error(conn, :forbidden, "Error.") end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index c482bba64..b20a00a89 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do alias Pleroma.Web.MediaProxy def render("index.json", %{users: users} = opts) do + # Note: :skip_relationships option is currently intentionally not supported for accounts relationships_opt = cond do Map.has_key?(opts, :relationships) -> @@ -190,11 +191,15 @@ defp do_render("show.json", %{user: user} = opts) do end) relationship = - render("relationship.json", %{ - user: opts[:for], - target: user, - relationships: opts[:relationships] - }) + if opts[:skip_relationships] do + %{} + else + render("relationship.json", %{ + user: opts[:for], + target: user, + relationships: opts[:relationships] + }) + end %{ id: to_string(user.id), diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 89f5734ff..78d187f9a 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -51,14 +51,15 @@ def render("index.json", %{notifications: notifications, for: reading_user} = op |> Enum.filter(& &1) |> Kernel.++(move_activities_targets) - UserRelationship.view_relationships_option(reading_user, actors) + UserRelationship.view_relationships_option(reading_user, actors, + source_mutes_only: opts[:skip_relationships] + ) end - opts = %{ - for: reading_user, - parent_activities: parent_activities, - relationships: relationships_opt - } + opts = + opts + |> Map.put(:parent_activities, parent_activities) + |> Map.put(:relationships, relationships_opt) safe_render_many(notifications, NotificationView, "show.json", opts) end @@ -82,12 +83,16 @@ def render( mastodon_type = Activity.mastodon_notification_type(activity) + render_opts = %{ + relationships: opts[:relationships], + skip_relationships: opts[:skip_relationships] + } + with %{id: _} = account <- - AccountView.render("show.json", %{ - user: actor, - for: reading_user, - relationships: opts[:relationships] - }) do + AccountView.render( + "show.json", + Map.merge(render_opts, %{user: actor, for: reading_user}) + ) do response = %{ id: to_string(notification.id), type: mastodon_type, @@ -98,8 +103,6 @@ def render( } } - render_opts = %{relationships: opts[:relationships]} - case mastodon_type do "mention" -> put_status(response, activity, reading_user, render_opts) @@ -111,6 +114,7 @@ def render( put_status(response, parent_activity_fn.(), reading_user, render_opts) "move" -> + # Note: :skip_relationships option being applied to _account_ rendering (here) put_target(response, activity, reading_user, render_opts) "follow" -> diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 82326986c..9cbd31878 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -97,7 +97,9 @@ def render("index.json", opts) do true -> actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"])) - UserRelationship.view_relationships_option(opts[:for], actors) + UserRelationship.view_relationships_option(opts[:for], actors, + source_mutes_only: opts[:skip_relationships] + ) end opts = @@ -151,7 +153,8 @@ def render( AccountView.render("show.json", %{ user: user, for: opts[:for], - relationships: opts[:relationships] + relationships: opts[:relationships], + skip_relationships: opts[:skip_relationships] }), in_reply_to_id: nil, in_reply_to_account_id: nil, @@ -299,6 +302,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} _ -> [] end + # Status muted state (would do 1 request per status unless user mutes are preloaded) muted = thread_muted? || UserRelationship.exists?( @@ -317,7 +321,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} AccountView.render("show.json", %{ user: user, for: opts[:for], - relationships: opts[:relationships] + relationships: opts[:relationships], + skip_relationships: opts[:skip_relationships] }), in_reply_to_id: reply_to && to_string(reply_to.id), in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index dcba67d03..9d0b3b1e4 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do use Pleroma.Web, :controller import Pleroma.Web.ControllerHelper, - only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2] + only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1] alias Ecto.Changeset alias Pleroma.Plugs.OAuthScopesPlug @@ -139,7 +139,12 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do conn |> add_link_headers(activities) |> put_view(StatusView) - |> render("index.json", activities: activities, for: for_user, as: :activity) + |> render("index.json", + activities: activities, + for: for_user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) end @doc "POST /api/v1/pleroma/accounts/:id/subscribe" diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index dae7f0f2f..83983b576 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1] alias Pleroma.Activity alias Pleroma.Conversation.Participation @@ -130,7 +130,12 @@ def conversation_statuses( conn |> add_link_headers(activities) |> put_view(StatusView) - |> render("index.json", %{activities: activities, for: user, as: :activity}) + |> render("index.json", + activities: activities, + for: user, + as: :activity, + skip_relationships: skip_relationships?(params) + ) else _error -> conn @@ -184,13 +189,17 @@ def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_i end end - def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do + def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do with notifications <- Notification.set_read_up_to(user, max_id) do notifications = Enum.take(notifications, 80) conn |> put_view(NotificationView) - |> render("index.json", %{notifications: notifications, for: user}) + |> render("index.json", + notifications: notifications, + for: user, + skip_relationships: skip_relationships?(params) + ) end end end diff --git a/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs b/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs index c618ea381..b6f0ac66b 100644 --- a/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs +++ b/priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs @@ -3,7 +3,6 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do import Ecto.Query alias Pleroma.Activity alias Pleroma.Bookmark - alias Pleroma.User alias Pleroma.Repo def up do diff --git a/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs b/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs index 2f336a5e8..43d616705 100644 --- a/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs +++ b/priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs @@ -1,6 +1,5 @@ defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do use Ecto.Migration - alias Pleroma.User def change do execute(""" diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 7a0011646..42a311f99 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -12,6 +12,26 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do import Pleroma.Factory + test "does NOT render account/pleroma/relationship if this is disabled by default" do + clear_config([:extensions, :output_relationships_in_statuses_by_default], false) + + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, [_notification]} = Notification.create_notifications(activity) + + response = + conn + |> assign(:user, user) + |> get("/api/v1/notifications") + |> json_response(200) + + assert Enum.all?(response, fn n -> + get_in(n, ["account", "pleroma", "relationship"]) == %{} + end) + end + test "list of notifications" do %{user: user, conn: conn} = oauth_access(["read:notifications"]) other_user = insert(:user) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index d59974d50..6b126217a 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -1043,6 +1043,8 @@ test "replaces missing description with an empty string", %{conn: conn, user: us end test "bookmarks" do + bookmarks_uri = "/api/v1/bookmarks?with_relationships=true" + %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"]) author = insert(:user) @@ -1064,7 +1066,7 @@ test "bookmarks" do assert json_response(response2, 200)["bookmarked"] == true - bookmarks = get(conn, "/api/v1/bookmarks") + bookmarks = get(conn, bookmarks_uri) assert [json_response(response2, 200), json_response(response1, 200)] == json_response(bookmarks, 200) @@ -1073,7 +1075,7 @@ test "bookmarks" do assert json_response(response1, 200)["bookmarked"] == false - bookmarks = get(conn, "/api/v1/bookmarks") + bookmarks = get(conn, bookmarks_uri) assert [json_response(response2, 200)] == json_response(bookmarks, 200) end diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs index 97b1c3e66..06efdc901 100644 --- a/test/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs @@ -20,7 +20,30 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do describe "home" do setup do: oauth_access(["read:statuses"]) + test "does NOT render account/pleroma/relationship if this is disabled by default", %{ + user: user, + conn: conn + } do + clear_config([:extensions, :output_relationships_in_statuses_by_default], false) + + other_user = insert(:user) + + {:ok, _} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + + response = + conn + |> assign(:user, user) + |> get("/api/v1/timelines/home") + |> json_response(200) + + assert Enum.all?(response, fn n -> + get_in(n, ["account", "pleroma", "relationship"]) == %{} + end) + end + test "the home timeline", %{user: user, conn: conn} do + uri = "/api/v1/timelines/home?with_relationships=true" + following = insert(:user, nickname: "followed") third_user = insert(:user, nickname: "repeated") @@ -28,13 +51,13 @@ test "the home timeline", %{user: user, conn: conn} do {:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"}) {:ok, _, _} = CommonAPI.repeat(activity.id, following) - ret_conn = get(conn, "/api/v1/timelines/home") + ret_conn = get(conn, uri) assert Enum.empty?(json_response(ret_conn, :ok)) {:ok, _user} = User.follow(user, following) - ret_conn = get(conn, "/api/v1/timelines/home") + ret_conn = get(conn, uri) assert [ %{ @@ -59,7 +82,7 @@ test "the home timeline", %{user: user, conn: conn} do {:ok, _user} = User.follow(third_user, user) - ret_conn = get(conn, "/api/v1/timelines/home") + ret_conn = get(conn, uri) assert [ %{ From 2d64500a9dee8bc53c988719bde1c1f4f41575b7 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 1 Apr 2020 20:26:33 +0300 Subject: [PATCH 067/114] error improvement for email_invite endpoint --- docs/API/admin_api.md | 13 +++++++ .../web/admin_api/admin_api_controller.ex | 17 ++++++-- .../admin_api/admin_api_controller_test.exs | 39 ++++++++++++++++++- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index edcf73e14..179d8c451 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -392,6 +392,19 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret - `email` - `name`, optional +- Response: + - On success: `204`, empty response + - On failure: + - 400 Bad Request, JSON: + + ```json + [ + { + `error` // error message + } + ] + ``` + ## `GET /api/pleroma/admin/users/:nickname/password_reset` ### Get a password reset token for a given nickname diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index ca5439920..7b442f6e1 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -576,9 +576,8 @@ def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) @doc "Sends registration invite via email" def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do - with true <- - Config.get([:instance, :invites_enabled]) && - !Config.get([:instance, :registrations_open]), + with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])}, + {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])}, {:ok, invite_token} <- UserInviteToken.create_invite(), email <- Pleroma.Emails.UserEmail.user_invitation_email( @@ -589,6 +588,18 @@ def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) ), {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do json_response(conn, :no_content, "") + else + {:registrations_open, _} -> + errors( + conn, + {:error, "To send invites you need set `registrations_open` option to false."} + ) + + {:invites_enabled, _} -> + errors( + conn, + {:error, "To send invites you need set `invites_enabled` option to true."} + ) end end diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index ea0c92502..32fe69d19 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -625,6 +625,39 @@ test "it returns 403 if requested by a non-admin" do assert json_response(conn, :forbidden) end + + test "email with +", %{conn: conn, admin: admin} do + recipient_email = "foo+bar@baz.com" + + conn + |> put_req_header("content-type", "application/json;charset=utf-8") + |> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email}) + |> json_response(:no_content) + + token_record = + Pleroma.UserInviteToken + |> Repo.all() + |> List.last() + + assert token_record + refute token_record.used + + notify_email = Config.get([:instance, :notify_email]) + instance_name = Config.get([:instance, :name]) + + email = + Pleroma.Emails.UserEmail.user_invitation_email( + admin, + token_record, + recipient_email + ) + + Swoosh.TestAssertions.assert_email_sent( + from: {instance_name, notify_email}, + to: recipient_email, + html_body: email.html_body + ) + end end describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do @@ -637,7 +670,8 @@ test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") - assert json_response(conn, :internal_server_error) + assert json_response(conn, :bad_request) == + "To send invites you need set `invites_enabled` option to true." end test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do @@ -646,7 +680,8 @@ test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") - assert json_response(conn, :internal_server_error) + assert json_response(conn, :bad_request) == + "To send invites you need set `registrations_open` option to false." end end From 23219e6fb3163bfac07fb5fb1b2602dcd27e47c2 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 1 Apr 2020 23:00:59 +0400 Subject: [PATCH 068/114] Add OpenAPI --- lib/pleroma/web/api_spec.ex | 30 ++++++ .../web/api_spec/operations/app_operation.ex | 94 +++++++++++++++++++ .../api_spec/schemas/app_create_request.ex | 33 +++++++ .../api_spec/schemas/app_create_response.ex | 33 +++++++ .../controllers/app_controller.ex | 9 +- lib/pleroma/web/oauth/scopes.ex | 7 +- lib/pleroma/web/router.ex | 11 +++ mix.exs | 3 +- mix.lock | 1 + test/web/api_spec/app_operation_test.exs | 45 +++++++++ .../controllers/account_controller_test.exs | 4 +- .../controllers/app_controller_test.exs | 4 +- 12 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 lib/pleroma/web/api_spec.ex create mode 100644 lib/pleroma/web/api_spec/operations/app_operation.ex create mode 100644 lib/pleroma/web/api_spec/schemas/app_create_request.ex create mode 100644 lib/pleroma/web/api_spec/schemas/app_create_response.ex create mode 100644 test/web/api_spec/app_operation_test.exs diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex new file mode 100644 index 000000000..22f76d4bf --- /dev/null +++ b/lib/pleroma/web/api_spec.ex @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec do + alias OpenApiSpex.OpenApi + alias Pleroma.Web.Endpoint + alias Pleroma.Web.Router + + @behaviour OpenApi + + @impl OpenApi + def spec do + %OpenApi{ + servers: [ + # Populate the Server info from a phoenix endpoint + OpenApiSpex.Server.from_endpoint(Endpoint) + ], + info: %OpenApiSpex.Info{ + title: "Pleroma", + description: Application.spec(:pleroma, :description) |> to_string(), + version: Application.spec(:pleroma, :vsn) |> to_string() + }, + # populate the paths from a phoenix router + paths: OpenApiSpex.Paths.from_router(Router) + } + # discover request/response schemas from path specs + |> OpenApiSpex.resolve_schema_modules() + end +end diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex new file mode 100644 index 000000000..2a4958acf --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/app_operation.ex @@ -0,0 +1,94 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.AppOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest + alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse + + @spec open_api_operation(atom) :: Operation.t() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + @spec create_operation() :: Operation.t() + def create_operation do + %Operation{ + tags: ["apps"], + summary: "Create an application", + description: "Create a new application to obtain OAuth2 credentials", + operationId: "AppController.create", + requestBody: + Operation.request_body("Parameters", "application/json", AppCreateRequest, required: true), + responses: %{ + 200 => Operation.response("App", "application/json", AppCreateResponse), + 422 => + Operation.response( + "Unprocessable Entity", + "application/json", + %Schema{ + type: :object, + description: + "If a required parameter is missing or improperly formatted, the request will fail.", + properties: %{ + error: %Schema{type: :string} + }, + example: %{ + "error" => "Validation failed: Redirect URI must be an absolute URI." + } + } + ) + } + } + end + + def verify_credentials_operation do + %Operation{ + tags: ["apps"], + summary: "Verify your app works", + description: "Confirm that the app's OAuth2 credentials work.", + operationId: "AppController.verify_credentials", + parameters: [ + Operation.parameter(:authorization, :header, :string, "Bearer ", required: true) + ], + responses: %{ + 200 => + Operation.response("App", "application/json", %Schema{ + type: :object, + description: + "If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.", + properties: %{ + name: %Schema{type: :string}, + vapid_key: %Schema{type: :string}, + website: %Schema{type: :string, nullable: true} + }, + example: %{ + "name" => "My App", + "vapid_key" => + "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=", + "website" => "https://myapp.com/" + } + }), + 422 => + Operation.response( + "Unauthorized", + "application/json", + %Schema{ + type: :object, + description: + "If the Authorization header contains an invalid token, is malformed, or is not present, an error will be returned indicating an authorization failure.", + properties: %{ + error: %Schema{type: :string} + }, + example: %{ + "error" => "The access token is invalid." + } + } + ) + } + } + end +end diff --git a/lib/pleroma/web/api_spec/schemas/app_create_request.ex b/lib/pleroma/web/api_spec/schemas/app_create_request.ex new file mode 100644 index 000000000..8a83abef3 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/app_create_request.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateRequest do + alias OpenApiSpex.Schema + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AppCreateRequest", + description: "POST body for creating an app", + type: :object, + properties: %{ + client_name: %Schema{type: :string, description: "A name for your application."}, + redirect_uris: %Schema{ + type: :string, + description: + "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter." + }, + scopes: %Schema{ + type: :string, + description: "Space separated list of scopes. If none is provided, defaults to `read`." + }, + website: %Schema{type: :string, description: "A URL to the homepage of your app"} + }, + required: [:client_name, :redirect_uris], + example: %{ + "client_name" => "My App", + "redirect_uris" => "https://myapp.com/auth/callback", + "website" => "https://myapp.com/" + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/app_create_response.ex b/lib/pleroma/web/api_spec/schemas/app_create_response.ex new file mode 100644 index 000000000..f290fb031 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/app_create_response.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateResponse do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "AppCreateResponse", + description: "Response schema for an app", + type: :object, + properties: %{ + id: %Schema{type: :string}, + name: %Schema{type: :string}, + client_id: %Schema{type: :string}, + client_secret: %Schema{type: :string}, + redirect_uri: %Schema{type: :string}, + vapid_key: %Schema{type: :string}, + website: %Schema{type: :string, nullable: true} + }, + example: %{ + "id" => "123", + "name" => "My App", + "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM", + "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw", + "vapid_key" => + "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=", + "website" => "https://myapp.com/" + } + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 5e2871f18..005c60444 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -14,17 +14,20 @@ defmodule Pleroma.Web.MastodonAPI.AppController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials) + plug(OpenApiSpex.Plug.CastAndValidate) @local_mastodon_name "Mastodon-Local" + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation + @doc "POST /api/v1/apps" - def create(conn, params) do + def create(%{body_params: params} = conn, _params) do scopes = Scopes.fetch_scopes(params, ["read"]) app_attrs = params - |> Map.drop(["scope", "scopes"]) - |> Map.put("scopes", scopes) + |> Map.take([:client_name, :redirect_uris, :website]) + |> Map.put(:scopes, scopes) with cs <- App.register_changeset(%App{}, app_attrs), false <- cs.changes[:client_name] == @local_mastodon_name, diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex index 8ecf901f3..1023f16d4 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/oauth/scopes.ex @@ -15,7 +15,12 @@ defmodule Pleroma.Web.OAuth.Scopes do Note: `scopes` is used by Mastodon — supporting it but sticking to OAuth's standard `scope` wherever we control it """ - @spec fetch_scopes(map(), list()) :: list() + @spec fetch_scopes(map() | struct(), list()) :: list() + + def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do + parse_scopes(scopes, default) + end + def fetch_scopes(params, default) do parse_scopes(params["scope"] || params["scopes"], default) end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5a0902739..3ecd59cd1 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -29,6 +29,7 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Plugs.SetUserSessionIdPlug) plug(Pleroma.Plugs.EnsureUserKeyPlug) plug(Pleroma.Plugs.IdempotencyPlug) + plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec) end pipeline :authenticated_api do @@ -44,6 +45,7 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Plugs.SetUserSessionIdPlug) plug(Pleroma.Plugs.EnsureAuthenticatedPlug) plug(Pleroma.Plugs.IdempotencyPlug) + plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec) end pipeline :admin_api do @@ -61,6 +63,7 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Plugs.EnsureAuthenticatedPlug) plug(Pleroma.Plugs.UserIsAdminPlug) plug(Pleroma.Plugs.IdempotencyPlug) + plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec) end pipeline :mastodon_html do @@ -94,10 +97,12 @@ defmodule Pleroma.Web.Router do pipeline :config do plug(:accepts, ["json", "xml"]) + plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec) end pipeline :pleroma_api do plug(:accepts, ["html", "json"]) + plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec) end pipeline :mailbox_preview do @@ -500,6 +505,12 @@ defmodule Pleroma.Web.Router do ) end + scope "/api" do + pipe_through(:api) + + get("/openapi", OpenApiSpex.Plug.RenderSpec, []) + end + scope "/api", Pleroma.Web, as: :authenticated_twitter_api do pipe_through(:authenticated_api) diff --git a/mix.exs b/mix.exs index 890979f8b..ebd4a5ea6 100644 --- a/mix.exs +++ b/mix.exs @@ -171,7 +171,8 @@ defp deps do git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"}, {:mox, "~> 0.5", only: :test}, - {:restarter, path: "./restarter"} + {:restarter, path: "./restarter"}, + {:open_api_spex, "~> 3.6"} ] ++ oauth_deps() end diff --git a/mix.lock b/mix.lock index 62e14924a..fd26ca01b 100644 --- a/mix.lock +++ b/mix.lock @@ -72,6 +72,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"}, + "open_api_spex": {:hex, :open_api_spex, "3.6.0", "64205aba9f2607f71b08fd43e3351b9c5e9898ec5ef49fc0ae35890da502ade9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "126ba3473966277132079cb1d5bf1e3df9e36fe2acd00166e75fd125cecb59c5"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"}, "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"}, diff --git a/test/web/api_spec/app_operation_test.exs b/test/web/api_spec/app_operation_test.exs new file mode 100644 index 000000000..5b96abb44 --- /dev/null +++ b/test/web/api_spec/app_operation_test.exs @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.AppOperationTest do + use Pleroma.Web.ConnCase, async: true + + alias Pleroma.Web.ApiSpec + alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest + alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse + + import OpenApiSpex.TestAssertions + import Pleroma.Factory + + test "AppCreateRequest example matches schema" do + api_spec = ApiSpec.spec() + schema = AppCreateRequest.schema() + assert_schema(schema.example, "AppCreateRequest", api_spec) + end + + test "AppCreateResponse example matches schema" do + api_spec = ApiSpec.spec() + schema = AppCreateResponse.schema() + assert_schema(schema.example, "AppCreateResponse", api_spec) + end + + test "AppController produces a AppCreateResponse", %{conn: conn} do + api_spec = ApiSpec.spec() + app_attrs = build(:oauth_app) + + json = + conn + |> put_req_header("content-type", "application/json") + |> post( + "/api/v1/apps", + Jason.encode!(%{ + client_name: app_attrs.client_name, + redirect_uris: app_attrs.redirect_uris + }) + ) + |> json_response(200) + + assert_schema(json, "AppCreateResponse", api_spec) + end +end diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index a9fa0ce48..a450a732c 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -794,7 +794,9 @@ test "blocking / unblocking a user" do test "Account registration via Application", %{conn: conn} do conn = - post(conn, "/api/v1/apps", %{ + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/apps", %{ client_name: "client_name", redirect_uris: "urn:ietf:wg:oauth:2.0:oob", scopes: "read, write, follow" diff --git a/test/web/mastodon_api/controllers/app_controller_test.exs b/test/web/mastodon_api/controllers/app_controller_test.exs index 77d234d67..e7b11d14e 100644 --- a/test/web/mastodon_api/controllers/app_controller_test.exs +++ b/test/web/mastodon_api/controllers/app_controller_test.exs @@ -16,8 +16,7 @@ test "apps/verify_credentials", %{conn: conn} do conn = conn - |> assign(:user, token.user) - |> assign(:token, token) + |> put_req_header("authorization", "Bearer #{token.token}") |> get("/api/v1/apps/verify_credentials") app = Repo.preload(token, :app).app @@ -37,6 +36,7 @@ test "creates an oauth app", %{conn: conn} do conn = conn + |> put_req_header("content-type", "application/json") |> assign(:user, user) |> post("/api/v1/apps", %{ client_name: app_attrs.client_name, From 591f7015d91b383dae1ee29576d13c0fad65cad6 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 2 Apr 2020 09:34:11 +0300 Subject: [PATCH 069/114] update Oban package --- mix.exs | 2 +- mix.lock | 6 +++--- .../20200402063221_update_oban_jobs_table.exs | 11 +++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 priv/repo/migrations/20200402063221_update_oban_jobs_table.exs diff --git a/mix.exs b/mix.exs index 87c025d89..375bc67c1 100644 --- a/mix.exs +++ b/mix.exs @@ -108,7 +108,7 @@ defp deps do {:ecto_enum, "~> 1.4"}, {:ecto_sql, "~> 3.3.2"}, {:postgrex, ">= 0.13.5"}, - {:oban, "~> 0.12.1"}, + {:oban, "~> 1.2"}, {:gettext, "~> 0.15"}, {:comeonin, "~> 4.1.1"}, {:pbkdf2_elixir, "~> 0.12.3"}, diff --git a/mix.lock b/mix.lock index 6cca578d6..50be45a4d 100644 --- a/mix.lock +++ b/mix.lock @@ -26,7 +26,7 @@ "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, - "ecto": {:hex, :ecto, "3.3.3", "0830bf3aebcbf3d8c1a1811cd581773b6866886c012f52c0f027031fa96a0b53", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "12e368e3c2a2938d7776defaabdae40e82900fc4d8d66120ec1e01dfd8b93c3a"}, + "ecto": {:hex, :ecto, "3.4.0", "a7a83ab8359bf816ce729e5e65981ce25b9fc5adfc89c2ea3980f4fed0bfd7c1", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5eed18252f5b5bbadec56a24112b531343507dbe046273133176b12190ce19cc"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"}, "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, @@ -55,7 +55,7 @@ "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, - "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, + "jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"}, "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"}, "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, @@ -73,7 +73,7 @@ "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, - "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"}, + "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"}, "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"}, diff --git a/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs new file mode 100644 index 000000000..c8ee12192 --- /dev/null +++ b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs @@ -0,0 +1,11 @@ +defmodule Pleroma.Repo.Migrations.UpdateObanJobsTable do + use Ecto.Migration + + def up do + Oban.Migrations.up() + end + + def down do + Oban.Migrations.down(version: 1) + end +end From 0aa24a150bbb153f55ca92dfb595385b4fe3839c Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 2 Apr 2020 17:33:23 +0400 Subject: [PATCH 070/114] Add oAuth --- lib/pleroma/web/api_spec.ex | 16 +++++++++++++++- .../web/api_spec/operations/app_operation.ex | 6 ++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 22f76d4bf..41e48a085 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -22,7 +22,21 @@ def spec do version: Application.spec(:pleroma, :vsn) |> to_string() }, # populate the paths from a phoenix router - paths: OpenApiSpex.Paths.from_router(Router) + paths: OpenApiSpex.Paths.from_router(Router), + components: %OpenApiSpex.Components{ + securitySchemes: %{ + "oAuth" => %OpenApiSpex.SecurityScheme{ + type: "oauth2", + flows: %OpenApiSpex.OAuthFlows{ + password: %OpenApiSpex.OAuthFlow{ + authorizationUrl: "/oauth/authorize", + tokenUrl: "/oauth/token", + scopes: %{"read" => "read"} + } + } + } + } + } } # discover request/response schemas from path specs |> OpenApiSpex.resolve_schema_modules() diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex index 2a4958acf..41d56693a 100644 --- a/lib/pleroma/web/api_spec/operations/app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/app_operation.ex @@ -51,8 +51,10 @@ def verify_credentials_operation do summary: "Verify your app works", description: "Confirm that the app's OAuth2 credentials work.", operationId: "AppController.verify_credentials", - parameters: [ - Operation.parameter(:authorization, :header, :string, "Bearer ", required: true) + security: [ + %{ + "oAuth" => ["read"] + } ], responses: %{ 200 => From b59ac37b2c09d5dc80b59bd3a2aea36989bee713 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 6 Apr 2020 10:45:25 +0300 Subject: [PATCH 071/114] tests for emoji mix task --- coveralls.json | 6 + docs/administration/CLI_tasks/emoji.md | 4 +- lib/mix/tasks/pleroma/emoji.ex | 90 ++++--- test/fixtures/emoji/packs/blank.png.zip | Bin 0 -> 284 bytes .../emoji/packs/default-manifest.json | 10 + test/fixtures/emoji/packs/finmoji.json | 3 + test/fixtures/emoji/packs/manifest.json | 10 + test/tasks/emoji_test.exs | 226 ++++++++++++++++++ 8 files changed, 311 insertions(+), 38 deletions(-) create mode 100644 coveralls.json create mode 100644 test/fixtures/emoji/packs/blank.png.zip create mode 100644 test/fixtures/emoji/packs/default-manifest.json create mode 100644 test/fixtures/emoji/packs/finmoji.json create mode 100644 test/fixtures/emoji/packs/manifest.json create mode 100644 test/tasks/emoji_test.exs diff --git a/coveralls.json b/coveralls.json new file mode 100644 index 000000000..75e845ade --- /dev/null +++ b/coveralls.json @@ -0,0 +1,6 @@ +{ + "skip_files": [ + "test/support", + "lib/mix/tasks/pleroma/benchmark.ex" + ] +} \ No newline at end of file diff --git a/docs/administration/CLI_tasks/emoji.md b/docs/administration/CLI_tasks/emoji.md index efec8222c..3d524a52b 100644 --- a/docs/administration/CLI_tasks/emoji.md +++ b/docs/administration/CLI_tasks/emoji.md @@ -39,8 +39,8 @@ mix pleroma.emoji get-packs [option ...] mix pleroma.emoji gen-pack PACK-URL ``` -Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you. +Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you. - The manifest entry will either be written to a newly created `index.json` file or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously. + The manifest entry will either be written to a newly created `pack_name.json` file (pack name is asked in questions) or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously. The file list will be written to the file specified previously, *replacing* that file. You _should_ check that the file list doesn't contain anything you don't need in the pack, that is, anything that is not an emoji (the whole pack is downloaded, but only emoji files are extracted). diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 429d763c7..cdffa88b2 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -14,8 +14,8 @@ def run(["ls-packs" | args]) do {options, [], []} = parse_global_opts(args) - manifest = - fetch_manifest(if options[:manifest], do: options[:manifest], else: default_manifest()) + url_or_path = options[:manifest] || default_manifest() + manifest = fetch_manifest(url_or_path) Enum.each(manifest, fn {name, info} -> to_print = [ @@ -40,9 +40,9 @@ def run(["get-packs" | args]) do {options, pack_names, []} = parse_global_opts(args) - manifest_url = if options[:manifest], do: options[:manifest], else: default_manifest() + url_or_path = options[:manifest] || default_manifest() - manifest = fetch_manifest(manifest_url) + manifest = fetch_manifest(url_or_path) for pack_name <- pack_names do if Map.has_key?(manifest, pack_name) do @@ -75,7 +75,10 @@ def run(["get-packs" | args]) do end # The url specified in files should be in the same directory - files_url = Path.join(Path.dirname(manifest_url), pack["files"]) + files_url = + url_or_path + |> Path.dirname() + |> Path.join(pack["files"]) IO.puts( IO.ANSI.format([ @@ -133,38 +136,51 @@ def run(["get-packs" | args]) do end end - def run(["gen-pack", src]) do + def run(["gen-pack" | args]) do start_pleroma() - proposed_name = Path.basename(src) |> Path.rootname() - name = String.trim(IO.gets("Pack name [#{proposed_name}]: ")) - # If there's no name, use the default one - name = if String.length(name) > 0, do: name, else: proposed_name - - license = String.trim(IO.gets("License: ")) - homepage = String.trim(IO.gets("Homepage: ")) - description = String.trim(IO.gets("Description: ")) - - proposed_files_name = "#{name}.json" - files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: ")) - files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name - - default_exts = [".png", ".gif"] - default_exts_str = Enum.join(default_exts, " ") - - exts = - String.trim( - IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ") + {opts, [src], []} = + OptionParser.parse( + args, + strict: [ + name: :string, + license: :string, + homepage: :string, + description: :string, + files: :string, + extensions: :string + ] ) + proposed_name = Path.basename(src) |> Path.rootname() + name = get_option(opts, :name, "Pack name:", proposed_name) + license = get_option(opts, :license, "License:") + homepage = get_option(opts, :homepage, "Homepage:") + description = get_option(opts, :description, "Description:") + + proposed_files_name = "#{name}_files.json" + files_name = get_option(opts, :files, "Save file list to:", proposed_files_name) + + default_exts = [".png", ".gif"] + + custom_exts = + get_option( + opts, + :extensions, + "Emoji file extensions (separated with spaces):", + Enum.join(default_exts, " ") + ) + |> String.split(" ", trim: true) + exts = - if String.length(exts) > 0 do - String.split(exts, " ") - |> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end) - else + if MapSet.equal?(MapSet.new(default_exts), MapSet.new(custom_exts)) do default_exts + else + custom_exts end + IO.puts("Using #{Enum.join(exts, " ")} extensions") + IO.puts("Downloading the pack and generating SHA256") binary_archive = Tesla.get!(client(), src).body @@ -194,14 +210,16 @@ def run(["gen-pack", src]) do IO.puts(""" #{files_name} has been created and contains the list of all found emojis in the pack. - Please review the files in the remove those not needed. + Please review the files in the pack and remove those not needed. """) - if File.exists?("index.json") do - existing_data = File.read!("index.json") |> Jason.decode!() + pack_file = "#{name}.json" + + if File.exists?(pack_file) do + existing_data = File.read!(pack_file) |> Jason.decode!() File.write!( - "index.json", + pack_file, Jason.encode!( Map.merge( existing_data, @@ -211,11 +229,11 @@ def run(["gen-pack", src]) do ) ) - IO.puts("index.json file has been update with the #{name} pack") + IO.puts("#{pack_file} has been updated with the #{name} pack") else - File.write!("index.json", Jason.encode!(pack_json, pretty: true)) + File.write!(pack_file, Jason.encode!(pack_json, pretty: true)) - IO.puts("index.json has been created with the #{name} pack") + IO.puts("#{pack_file} has been created with the #{name} pack") end end diff --git a/test/fixtures/emoji/packs/blank.png.zip b/test/fixtures/emoji/packs/blank.png.zip new file mode 100644 index 0000000000000000000000000000000000000000..651daf1271fb95ca1404142441360eed4fbdd45d GIT binary patch literal 284 zcmWIWW@Zs#-~d9a?B)OlD2NBroD2#KNjZsm*?I+e>7gOK4D5#d?QteR45CXbxEUB( zzA`c}0JSqPyyp2({QT*pM@b0@559gW;AFbQt8j)xMIvtZu_cU}%O)x8Phe}?bTVDH z!G_ac{P+Z}V^3I*39acCcBz`h#Jl4TUzUMEfr)`Z-T9eS0!uDmzIr}&Zm^RuLx49s yM|^mrXavv>kfQ>;8JR?w5e`O{134H5mNbG`L_0sgo0Scufe{G9f%JM1hXDXq!$&v( literal 0 HcmV?d00001 diff --git a/test/fixtures/emoji/packs/default-manifest.json b/test/fixtures/emoji/packs/default-manifest.json new file mode 100644 index 000000000..c8433808d --- /dev/null +++ b/test/fixtures/emoji/packs/default-manifest.json @@ -0,0 +1,10 @@ +{ + "finmoji": { + "license": "CC BY-NC-ND 4.0", + "homepage": "https://finland.fi/emoji/", + "description": "Finland is the first country in the world to publish its own set of country themed emojis. The Finland emoji collection contains 56 tongue-in-cheek emotions, which were created to explain some hard-to-describe Finnish emotions, Finnish words and customs.", + "src": "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip", + "src_sha256": "384025A1AC6314473863A11AC7AB38A12C01B851A3F82359B89B4D4211D3291D", + "files": "finmoji.json" + } +} \ No newline at end of file diff --git a/test/fixtures/emoji/packs/finmoji.json b/test/fixtures/emoji/packs/finmoji.json new file mode 100644 index 000000000..279770998 --- /dev/null +++ b/test/fixtures/emoji/packs/finmoji.json @@ -0,0 +1,3 @@ +{ + "blank": "blank.png" +} \ No newline at end of file diff --git a/test/fixtures/emoji/packs/manifest.json b/test/fixtures/emoji/packs/manifest.json new file mode 100644 index 000000000..2d51a459b --- /dev/null +++ b/test/fixtures/emoji/packs/manifest.json @@ -0,0 +1,10 @@ +{ + "blobs.gg": { + "src_sha256": "3a12f3a181678d5b3584a62095411b0d60a335118135910d879920f8ade5a57f", + "src": "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip", + "license": "Apache 2.0", + "homepage": "https://blobs.gg", + "files": "blobs_gg.json", + "description": "Blob Emoji from blobs.gg repacked as apng" + } +} \ No newline at end of file diff --git a/test/tasks/emoji_test.exs b/test/tasks/emoji_test.exs new file mode 100644 index 000000000..f2930652a --- /dev/null +++ b/test/tasks/emoji_test.exs @@ -0,0 +1,226 @@ +defmodule Mix.Tasks.Pleroma.EmojiTest do + use ExUnit.Case, async: true + + import ExUnit.CaptureIO + import Tesla.Mock + + alias Mix.Tasks.Pleroma.Emoji + + describe "ls-packs" do + test "with default manifest as url" do + mock(fn + %{ + method: :get, + url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json" + } -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/emoji/packs/default-manifest.json") + } + end) + + capture_io(fn -> Emoji.run(["ls-packs"]) end) =~ + "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip" + end + + test "with passed manifest as file" do + capture_io(fn -> + Emoji.run(["ls-packs", "-m", "test/fixtures/emoji/packs/manifest.json"]) + end) =~ "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip" + end + end + + describe "get-packs" do + test "download pack from default manifest" do + mock(fn + %{ + method: :get, + url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json" + } -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/emoji/packs/default-manifest.json") + } + + %{ + method: :get, + url: "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip" + } -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/emoji/packs/blank.png.zip") + } + + %{ + method: :get, + url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/finmoji.json" + } -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/emoji/packs/finmoji.json") + } + end) + + assert capture_io(fn -> Emoji.run(["get-packs", "finmoji"]) end) =~ "Writing pack.json for" + + emoji_path = + Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + + assert File.exists?(Path.join([emoji_path, "finmoji", "pack.json"])) + on_exit(fn -> File.rm_rf!("test/instance_static/emoji/finmoji") end) + end + + test "pack not found" do + mock(fn + %{ + method: :get, + url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json" + } -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/emoji/packs/default-manifest.json") + } + end) + + assert capture_io(fn -> Emoji.run(["get-packs", "not_found"]) end) =~ + "No pack named \"not_found\" found" + end + + test "raise on bad sha256" do + mock(fn + %{ + method: :get, + url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip" + } -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/emoji/packs/blank.png.zip") + } + end) + + assert_raise RuntimeError, ~r/^Bad SHA256 for blobs.gg/, fn -> + capture_io(fn -> + Emoji.run(["get-packs", "blobs.gg", "-m", "test/fixtures/emoji/packs/manifest.json"]) + end) + end + end + end + + describe "gen-pack" do + setup do + url = "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip" + + mock(fn %{ + method: :get, + url: ^url + } -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/emoji/packs/blank.png.zip")} + end) + + {:ok, url: url} + end + + test "with default extensions", %{url: url} do + name = "pack1" + pack_json = "#{name}.json" + files_json = "#{name}_file.json" + refute File.exists?(pack_json) + refute File.exists?(files_json) + + captured = + capture_io(fn -> + Emoji.run([ + "gen-pack", + url, + "--name", + name, + "--license", + "license", + "--homepage", + "homepage", + "--description", + "description", + "--files", + files_json, + "--extensions", + ".png .gif" + ]) + end) + + assert captured =~ "#{pack_json} has been created with the pack1 pack" + assert captured =~ "Using .png .gif extensions" + + assert File.exists?(pack_json) + assert File.exists?(files_json) + + on_exit(fn -> + File.rm_rf!(pack_json) + File.rm_rf!(files_json) + end) + end + + test "with custom extensions and update existing files", %{url: url} do + name = "pack2" + pack_json = "#{name}.json" + files_json = "#{name}_file.json" + refute File.exists?(pack_json) + refute File.exists?(files_json) + + captured = + capture_io(fn -> + Emoji.run([ + "gen-pack", + url, + "--name", + name, + "--license", + "license", + "--homepage", + "homepage", + "--description", + "description", + "--files", + files_json, + "--extensions", + " .png .gif .jpeg " + ]) + end) + + assert captured =~ "#{pack_json} has been created with the pack2 pack" + assert captured =~ "Using .png .gif .jpeg extensions" + + assert File.exists?(pack_json) + assert File.exists?(files_json) + + captured = + capture_io(fn -> + Emoji.run([ + "gen-pack", + url, + "--name", + name, + "--license", + "license", + "--homepage", + "homepage", + "--description", + "description", + "--files", + files_json, + "--extensions", + " .png .gif .jpeg " + ]) + end) + + assert captured =~ "#{pack_json} has been updated with the pack2 pack" + + on_exit(fn -> + File.rm_rf!(pack_json) + File.rm_rf!(files_json) + end) + end + end +end From a43e05591639132ce121d2e14258944a53004438 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 6 Apr 2020 14:27:20 +0300 Subject: [PATCH 072/114] using another fn for file deletion --- test/tasks/emoji_test.exs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/tasks/emoji_test.exs b/test/tasks/emoji_test.exs index f2930652a..f5de3ef0e 100644 --- a/test/tasks/emoji_test.exs +++ b/test/tasks/emoji_test.exs @@ -157,8 +157,8 @@ test "with default extensions", %{url: url} do assert File.exists?(files_json) on_exit(fn -> - File.rm_rf!(pack_json) - File.rm_rf!(files_json) + File.rm!(pack_json) + File.rm!(files_json) end) end @@ -218,8 +218,8 @@ test "with custom extensions and update existing files", %{url: url} do assert captured =~ "#{pack_json} has been updated with the pack2 pack" on_exit(fn -> - File.rm_rf!(pack_json) - File.rm_rf!(files_json) + File.rm!(pack_json) + File.rm!(files_json) end) end end From e67cde0ed6b55450b5f309f9ed86f7f8e2a1e73f Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 6 Apr 2020 13:46:34 +0200 Subject: [PATCH 073/114] Transmogrifier: Refactoring / Renaming. --- lib/pleroma/web/activity_pub/transmogrifier.ex | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a4b385cd5..455f51fe0 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -617,9 +617,9 @@ def handle_incoming(%{"type" => "Like"} = data, _options) do data |> LikeValidator.cast_data() |> Ecto.Changeset.apply_action(:insert)}, cast_data = ObjectValidator.stringify_keys(Map.from_struct(cast_data_sym)), :ok <- ObjectValidator.fetch_actor_and_object(cast_data), - {_, {:ok, cast_data}} <- {:maybe_add_context, maybe_add_context_from_object(cast_data)}, + {_, {:ok, cast_data}} <- {:ensure_context_presence, ensure_context_presence(cast_data)}, {_, {:ok, cast_data}} <- - {:maybe_add_recipients, maybe_add_recipients_from_object(cast_data)}, + {:ensure_recipients_presence, ensure_recipients_presence(cast_data)}, {_, {:ok, activity, _meta}} <- {:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do {:ok, activity} @@ -1251,10 +1251,10 @@ def maybe_fix_user_url(data), do: data def maybe_fix_user_object(data), do: maybe_fix_user_url(data) - defp maybe_add_context_from_object(%{"context" => context} = data) when is_binary(context), + defp ensure_context_presence(%{"context" => context} = data) when is_binary(context), do: {:ok, data} - defp maybe_add_context_from_object(%{"object" => object} = data) when is_binary(object) do + defp ensure_context_presence(%{"object" => object} = data) when is_binary(object) do with %{data: %{"context" => context}} when is_binary(context) <- Object.normalize(object) do {:ok, Map.put(data, "context", context)} else @@ -1263,14 +1263,14 @@ defp maybe_add_context_from_object(%{"object" => object} = data) when is_binary( end end - defp maybe_add_context_from_object(_) do + defp ensure_context_presence(_) do {:error, :no_context} end - defp maybe_add_recipients_from_object(%{"to" => [_ | _], "cc" => [_ | _]} = data), + defp ensure_recipients_presence(%{"to" => [_ | _], "cc" => [_ | _]} = data), do: {:ok, data} - defp maybe_add_recipients_from_object(%{"object" => object} = data) do + defp ensure_recipients_presence(%{"object" => object} = data) do case Object.normalize(object) do %{data: %{"actor" => actor}} -> data = @@ -1288,7 +1288,7 @@ defp maybe_add_recipients_from_object(%{"object" => object} = data) do end end - defp maybe_add_recipients_from_object(_) do + defp ensure_recipients_presence(_) do {:error, :no_object} end end From 772bc258cde11b3203ad9420f69321ccd56db91a Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 6 Apr 2020 13:53:24 +0200 Subject: [PATCH 074/114] ObjectID Validator: Refactor. --- .../object_validators/types/object_id.ex | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex index 8e70effe4..ee10be0b0 100644 --- a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex +++ b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex @@ -4,14 +4,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do def type, do: :string def cast(object) when is_binary(object) do - with %URI{ - scheme: scheme, - host: host - } - when scheme in ["https", "http"] and not is_nil(host) <- - URI.parse(object) do - {:ok, object} - else + # Host has to be present and scheme has to be an http scheme (for now) + case URI.parse(object) do + %URI{host: nil} -> + :error + + %URI{scheme: scheme} when scheme in ["https", "http"] -> + {:ok, object} + _ -> :error end From 03eebabe8e5b2e3f96f6ffe51a6f063a42f6a5d2 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 3 Apr 2020 22:52:25 +0400 Subject: [PATCH 075/114] Add Pleroma.Web.ApiSpec.Helpers --- lib/pleroma/web/api_spec/helpers.ex | 27 +++++++++++++++++++ .../web/api_spec/operations/app_operation.ex | 4 +-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 lib/pleroma/web/api_spec/helpers.ex diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex new file mode 100644 index 000000000..35cf4c0d8 --- /dev/null +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Helpers do + def request_body(description, schema_ref, opts \\ []) do + media_types = ["application/json", "multipart/form-data"] + + content = + media_types + |> Enum.map(fn type -> + {type, + %OpenApiSpex.MediaType{ + schema: schema_ref, + example: opts[:example], + examples: opts[:examples] + }} + end) + |> Enum.into(%{}) + + %OpenApiSpex.RequestBody{ + description: description, + content: content, + required: opts[:required] || false + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex index 41d56693a..26d8dbd42 100644 --- a/lib/pleroma/web/api_spec/operations/app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/app_operation.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse @@ -21,8 +22,7 @@ def create_operation do summary: "Create an application", description: "Create a new application to obtain OAuth2 credentials", operationId: "AppController.create", - requestBody: - Operation.request_body("Parameters", "application/json", AppCreateRequest, required: true), + requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true), responses: %{ 200 => Operation.response("App", "application/json", AppCreateResponse), 422 => From 06471940e0cb917bb362cbcb9d872ab1336a04cf Mon Sep 17 00:00:00 2001 From: kPherox Date: Tue, 7 Apr 2020 08:44:53 +0000 Subject: [PATCH 076/114] Apply suggestion to test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs --- .../controllers/account_controller/update_credentials_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs index 8687d7995..d78fbc5a1 100644 --- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -298,7 +298,7 @@ test "update fields", %{conn: conn} do ] end - test "update fields by urlencoded", %{conn: conn} do + test "update fields via x-www-form-urlencoded", %{conn: conn} do fields = [ "fields_attributes[1][name]=link", From 5739c498c029914c446656244cdd213a3e358fec Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 8 Apr 2020 18:46:01 +0300 Subject: [PATCH 077/114] fix for gun connections pool --- CHANGELOG.md | 3 +++ lib/pleroma/gun/conn.ex | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6e5d807c..92d1abc4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
+### Fixed +- Gun connections pool `max_connections` option. + ## [2.0.0] - 2019-03-08 ### Security - Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request. diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex index 20823a765..cd25a2e74 100644 --- a/lib/pleroma/gun/conn.ex +++ b/lib/pleroma/gun/conn.ex @@ -49,8 +49,10 @@ def open(%URI{} = uri, name, opts) do key = "#{uri.scheme}:#{uri.host}:#{uri.port}" + max_connections = pool_opts[:max_connections] || 250 + conn_pid = - if Connections.count(name) < opts[:max_connection] do + if Connections.count(name) < max_connections do do_open(uri, opts) else close_least_used_and_do_open(name, uri, opts) From d067eaa7b3bb76e7fc5ae019d6e00510b657171d Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 8 Apr 2020 22:58:31 +0300 Subject: [PATCH 078/114] formatter.ex: Use Phoenix.HTML for mention/hashtag generation Unlike concatenating strings, this makes sure everything is escaped. Tests had to be changed because Phoenix.HTML runs attributes through Enum.sort before generation for whatever reason. --- lib/pleroma/formatter.ex | 26 ++++++++++++++++--- test/formatter_test.exs | 24 +++++++---------- test/user_test.exs | 2 +- test/web/common_api/common_api_utils_test.exs | 6 ++--- .../update_credentials_test.exs | 4 +-- .../notification_controller_test.exs | 4 +-- test/web/twitter_api/twitter_api_test.exs | 2 +- 7 files changed, 41 insertions(+), 27 deletions(-) diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index e2a658cb3..c44e7fc8b 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -35,9 +35,19 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do nickname_text = get_nickname_text(nickname, opts) link = - ~s(@#{ - nickname_text - }) + Phoenix.HTML.Tag.content_tag( + :span, + Phoenix.HTML.Tag.content_tag( + :a, + ["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)], + "data-user": id, + class: "u-url mention", + href: ap_id, + rel: "ugc" + ), + class: "h-card" + ) + |> Phoenix.HTML.safe_to_string() {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}} @@ -49,7 +59,15 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do tag = String.downcase(tag) url = "#{Pleroma.Web.base_url()}/tag/#{tag}" - link = ~s(#{tag_text}) + + link = + Phoenix.HTML.Tag.content_tag(:a, tag_text, + class: "hashtag", + "data-tag": tag, + href: url, + rel: "tag ugc" + ) + |> Phoenix.HTML.safe_to_string() {link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}} end diff --git a/test/formatter_test.exs b/test/formatter_test.exs index cf8441cf6..93fd8eab7 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -150,13 +150,13 @@ test "gives a replacement for user links, using local nicknames in user links te assert length(mentions) == 3 expected_text = - ~s(@gsimg According to @gsimg According to @archa_eme_, that is @daggsy. Also hello @archa_eme_, that is @daggsy. Also hello @archaeme) + }" href="#{archaeme_remote.ap_id}" rel="ugc">@archaeme) assert expected_text == text end @@ -171,7 +171,7 @@ test "gives a replacement for user links when the user is using Osada" do assert length(mentions) == 1 expected_text = - ~s(@mike test) @@ -187,7 +187,7 @@ test "gives a replacement for single-character local nicknames" do assert length(mentions) == 1 expected_text = - ~s(@o hi) + ~s(@o hi) assert expected_text == text end @@ -209,17 +209,13 @@ test "given the 'safe_mention' option, it will only mention people in the beginn assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}] assert expected_text == - ~s(@#{user.nickname} @#{user.nickname} @#{ - other_user.nickname - } hey dudes i hate @#{other_user.nickname} hey dudes i hate @#{ - third_user.nickname - }) + }" href="#{third_user.ap_id}" rel="ugc">@#{third_user.nickname}) end test "given the 'safe_mention' option, it will still work without any mention" do diff --git a/test/user_test.exs b/test/user_test.exs index 0479f294d..d39787f35 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1404,7 +1404,7 @@ test "preserves hosts in user links text" do bio = "A.k.a. @nick@domain.com" expected_text = - ~s(A.k.a. @nick@domain.com) diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index d383d1714..98cf02d49 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -159,11 +159,11 @@ test "works for text/markdown with mentions" do {output, _, _} = Utils.format_input(text, "text/markdown") assert output == - ~s(

hello world

another @user__test and @user__test and @user__test google.com paragraph

) + }" href="http://foo.com/user__test" rel="ugc">@user__test google.com paragraph

) end end diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs index d78fbc5a1..2d256f63c 100644 --- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -82,9 +82,9 @@ test "updates the user's bio", %{conn: conn} do assert user_data = json_response(conn, 200) assert user_data["note"] == - ~s(I drink #cofe with #cofe with @#{user2.nickname}

suya..) + }" href="#{user2.ap_id}" rel="ugc">@#{user2.nickname}


suya..) end test "updates the user's locking status", %{conn: conn} do diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 344eabb4a..6f1fab069 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -26,7 +26,7 @@ test "list of notifications" do |> get("/api/v1/notifications") expected_response = - "hi @#{user.nickname}" @@ -45,7 +45,7 @@ test "getting a single notification" do conn = get(conn, "/api/v1/notifications/#{notification.id}") expected_response = - "hi @#{user.nickname}" diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 92f9aa0f5..f6e13b661 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -109,7 +109,7 @@ test "it registers a new user and parses mentions in the bio" do {:ok, user2} = TwitterAPI.register_user(data2) expected_text = - ~s(@john test) From c401b00c7885823744183dbd077db9239585d20d Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 9 Apr 2020 04:36:39 +0200 Subject: [PATCH 079/114] ObjectValidators.Types.ObjectID: Fix when URI.parse returns %URL{host: ""} --- .../object_validators/types/object_id.ex | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex index ee10be0b0..f6e749b33 100644 --- a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex +++ b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex @@ -6,14 +6,10 @@ def type, do: :string def cast(object) when is_binary(object) do # Host has to be present and scheme has to be an http scheme (for now) case URI.parse(object) do - %URI{host: nil} -> - :error - - %URI{scheme: scheme} when scheme in ["https", "http"] -> - {:ok, object} - - _ -> - :error + %URI{host: nil} -> :error + %URI{host: ""} -> :error + %URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object} + _ -> :error end end From 73134e248a031613151df87fdd406580d16dc6b9 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 9 Apr 2020 08:03:21 +0300 Subject: [PATCH 080/114] no changelog entry - bug fixed only in develop --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92d1abc4e..b6e5d807c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,9 +20,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
-### Fixed -- Gun connections pool `max_connections` option. - ## [2.0.0] - 2019-03-08 ### Security - Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request. From c8bfbf511eeca2045267ad4792c35648625788cf Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 9 Apr 2020 10:17:24 +0000 Subject: [PATCH 081/114] Apply suggestion to docs/API/admin_api.md --- docs/API/admin_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index 179d8c451..b3cf89818 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -400,7 +400,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret ```json [ { - `error` // error message + "error": "Appropriate error message here" } ] ``` From 4c60fdcbb1ab06183b8e300cbbb84d70ecd3e25b Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 9 Apr 2020 10:17:31 +0000 Subject: [PATCH 082/114] Apply suggestion to lib/pleroma/web/admin_api/admin_api_controller.ex --- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 7b442f6e1..a66db68f3 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -592,7 +592,7 @@ def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) {:registrations_open, _} -> errors( conn, - {:error, "To send invites you need set `registrations_open` option to false."} + {:error, "To send invites you need to set the `registrations_open` option to false."} ) {:invites_enabled, _} -> From 1cf0d5ab0d579ee4a1a779c308fedb0ab8ec3884 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 9 Apr 2020 10:17:36 +0000 Subject: [PATCH 083/114] Apply suggestion to lib/pleroma/web/admin_api/admin_api_controller.ex --- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index a66db68f3..09959b3bf 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -598,7 +598,7 @@ def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) {:invites_enabled, _} -> errors( conn, - {:error, "To send invites you need set `invites_enabled` option to true."} + {:error, "To send invites you need set to set the `invites_enabled` option to true."} ) end end From 365c34a7a96a9cbd5acb30eb6eedf195eeaff131 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 9 Apr 2020 10:17:44 +0000 Subject: [PATCH 084/114] Apply suggestion to test/web/admin_api/admin_api_controller_test.exs --- test/web/admin_api/admin_api_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 32fe69d19..afd894269 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -671,7 +671,7 @@ test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :bad_request) == - "To send invites you need set `invites_enabled` option to true." + "To send invites you need to set the `invites_enabled` option to true." end test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do From 9795ff5b016e74c0e7b94ac2ea28023208d1f8ee Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 9 Apr 2020 10:17:50 +0000 Subject: [PATCH 085/114] Apply suggestion to test/web/admin_api/admin_api_controller_test.exs --- test/web/admin_api/admin_api_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index afd894269..e8d11b88c 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -681,7 +681,7 @@ test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :bad_request) == - "To send invites you need set `registrations_open` option to false." + "To send invites you need to set the `registrations_open` option to false." end end From f20a19de853e8834f7774ee0098a14213bc7427f Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 9 Apr 2020 13:28:54 +0300 Subject: [PATCH 086/114] typo fix --- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 09959b3bf..fdbd24acb 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -598,7 +598,7 @@ def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) {:invites_enabled, _} -> errors( conn, - {:error, "To send invites you need set to set the `invites_enabled` option to true."} + {:error, "To send invites you need to set the `invites_enabled` option to true."} ) end end From d37a102933dbfbb0996546b4d148bbe36fbd4220 Mon Sep 17 00:00:00 2001 From: kPherox Date: Thu, 9 Apr 2020 21:16:29 +0900 Subject: [PATCH 087/114] Fix OTP_VERSION file in docker --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 29931a5e3..c2f3ad98c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,8 @@ RUN apk add git gcc g++ musl-dev make &&\ mkdir release &&\ mix release --path release +RUN echo "${OTP_VERSION}" > release/OTP_VERSION + FROM alpine:3.11 ARG BUILD_DATE From d545b883eb3c5b79b89a49ccaf9256c31b401145 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 9 Apr 2020 17:08:43 +0400 Subject: [PATCH 088/114] Add `/api/v1/notifications/:id/dismiss` endpoint --- .../controllers/notification_controller.ex | 3 ++- lib/pleroma/web/router.ex | 4 +++- .../notification_controller_test.exs | 18 +++++++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 0c9218454..a6b4096ec 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -66,7 +66,8 @@ def clear(%{assigns: %{user: user}} = conn, _params) do json(conn, %{}) end - # POST /api/v1/notifications/dismiss + # POST /api/v1/notifications/:id/dismiss + # POST /api/v1/notifications/dismiss (deprecated) def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do with {:ok, _notif} <- Notification.dismiss(user, id) do json(conn, %{}) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 3ecd59cd1..5f5ec1c81 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -352,9 +352,11 @@ defmodule Pleroma.Web.Router do get("/notifications", NotificationController, :index) get("/notifications/:id", NotificationController, :show) + post("/notifications/:id/dismiss", NotificationController, :dismiss) post("/notifications/clear", NotificationController, :clear) - post("/notifications/dismiss", NotificationController, :dismiss) delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) + # Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead + post("/notifications/dismiss", NotificationController, :dismiss) get("/scheduled_statuses", ScheduledActivityController, :index) get("/scheduled_statuses/:id", ScheduledActivityController, :show) diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 6f1fab069..1557937d8 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -53,7 +53,7 @@ test "getting a single notification" do assert response == expected_response end - test "dismissing a single notification" do + test "dismissing a single notification (deprecated endpoint)" do %{user: user, conn: conn} = oauth_access(["write:notifications"]) other_user = insert(:user) @@ -69,6 +69,22 @@ test "dismissing a single notification" do assert %{} = json_response(conn, 200) end + test "dismissing a single notification" do + %{user: user, conn: conn} = oauth_access(["write:notifications"]) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + + {:ok, [notification]} = Notification.create_notifications(activity) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/notifications/#{notification.id}/dismiss") + + assert %{} = json_response(conn, 200) + end + test "clearing all notifications" do %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"]) other_user = insert(:user) From 0e8f6d24b87812664d3bb021d17f120686cf2401 Mon Sep 17 00:00:00 2001 From: kPherox Date: Fri, 10 Apr 2020 00:19:09 +0900 Subject: [PATCH 089/114] Create OTP_VERSION file by `mix release` --- Dockerfile | 2 -- mix.exs | 11 ++++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index c2f3ad98c..29931a5e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,6 @@ RUN apk add git gcc g++ musl-dev make &&\ mkdir release &&\ mix release --path release -RUN echo "${OTP_VERSION}" > release/OTP_VERSION - FROM alpine:3.11 ARG BUILD_DATE diff --git a/mix.exs b/mix.exs index 3e4c7cbd8..ad2029518 100644 --- a/mix.exs +++ b/mix.exs @@ -37,12 +37,21 @@ def project do pleroma: [ include_executables_for: [:unix], applications: [ex_syslogger: :load, syslog: :load], - steps: [:assemble, ©_files/1, ©_nginx_config/1] + steps: [:assemble, &put_files/1, ©_files/1, ©_nginx_config/1] ] ] ] end + def put_files(%{path: target_path} = release) do + File.write!( + Path.join([target_path, "OTP_VERSION"]), + Pleroma.OTPVersion.version() + ) + + release + end + def copy_files(%{path: target_path} = release) do File.cp_r!("./rel/files", target_path) release From c826d5195f1746449eb369e86a730f14de9fa267 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 9 Apr 2020 23:36:17 +0400 Subject: [PATCH 090/114] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6e5d807c..2f5d8f612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
API Changes - Mastodon API: Support for `include_types` in `/api/v1/notifications`. +- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
## [2.0.0] - 2019-03-08 From 781ac28859596fce5f2fd24ffe1cdf24caaaa2fc Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sun, 15 Mar 2020 17:26:58 +0300 Subject: [PATCH 091/114] changelog.md: add 2.0.1 entry --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f5d8f612..15f0463b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [unreleased] +## [2.0.1] - 2020-03-15 +### Fixed +- 500 errors when no `Accept` header is present if Static-FE is enabled +- Instance panel not being updated immediately due to wrong `Cache-Control` headers +- Statuses posted with BBCode/Markdown having unncessary newlines in Pleroma-FE +- OTP: Fix some settings not being migrated to in-database config properly +- No `Cache-Control` headers on attachment/media proxy requests +- Character limit enforcement being off by 1 +- Mastodon Streaming API: hashtag timelines not working + ### Changed -- **Breaking:** BBCode and Markdown formatters will no longer return any `\n` and only use `
` for newlines +- BBCode and Markdown formatters will no longer return any `\n` and only use `
` for newlines +- Mastodon API: Allow registration without email if email verification is not enabled ### Removed - **Breaking:** removed `with_move` parameter from notifications timeline. From 2a08f44b026bae611064b6ac459e7df16e4a36f9 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Mon, 16 Mar 2020 00:50:03 +0300 Subject: [PATCH 092/114] CHANGELOG.md: Add upgrade notes for 2.0.1 --- CHANGELOG.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15f0463b2..8c976228c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [unreleased] +### Removed +- **Breaking:** removed `with_move` parameter from notifications timeline. + +### Added +- NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. +- NodeInfo: `pleroma_emoji_reactions` to the `features` list. +- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses. +- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required. +
+ API Changes +- Mastodon API: Support for `include_types` in `/api/v1/notifications`. +- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. +
+ ## [2.0.1] - 2020-03-15 ### Fixed - 500 errors when no `Accept` header is present if Static-FE is enabled @@ -17,19 +32,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - BBCode and Markdown formatters will no longer return any `\n` and only use `
` for newlines - Mastodon API: Allow registration without email if email verification is not enabled -### Removed -- **Breaking:** removed `with_move` parameter from notifications timeline. +### Upgrade notes +#### Nginx only +1. Remove `proxy_ignore_headers Cache-Control;` and `proxy_hide_header Cache-Control;` from your config. -### Added -- NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. -- NodeInfo: `pleroma_emoji_reactions` to the `features` list. -- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses. -- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required. -
- API Changes -- Mastodon API: Support for `include_types` in `/api/v1/notifications`. -- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. -
+#### Everyone +1. Run database migrations (inside Pleroma directory): + - OTP: `./bin/pleroma_ctl migrate` + - From Source: `mix ecto.migrate` +2. Restart Pleroma ## [2.0.0] - 2019-03-08 ### Security From 7306d2d06942f7912fd42809b1feb9ac43089012 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 31 Mar 2020 13:59:26 +0300 Subject: [PATCH 093/114] CHANGELOG.md: Add 2.0.2 entry --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c976228c..6942ad0bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. +## [2.0.2] - 2020-03-31 +### Fixed +- Blocked/muted users still generating push notifications +- Input textbox for bio ignoring newlines +- OTP: Inability to use PostgreSQL databases with SSL +- `user delete_activities` breaking when trying to delete already deleted posts + +### Added +- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials` + ## [2.0.1] - 2020-03-15 +### Security +- Static-FE: Fix remote posts not being sanitized + ### Fixed - 500 errors when no `Accept` header is present if Static-FE is enabled - Instance panel not being updated immediately due to wrong `Cache-Control` headers From 0b8f9a66aefdf4c9e2b7c1fa931e19cd724b6b4b Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 2 Apr 2020 23:37:14 +0300 Subject: [PATCH 094/114] CHANGELOG.md: add entries for funkwhale-related changes --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6942ad0bf..8eed9cf7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,14 +19,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [2.0.2] - 2020-03-31 +### Added +- Support for Funkwhale's `Audio` activity +- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials` + ### Fixed - Blocked/muted users still generating push notifications - Input textbox for bio ignoring newlines - OTP: Inability to use PostgreSQL databases with SSL - `user delete_activities` breaking when trying to delete already deleted posts - -### Added -- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials` +- Incorrect URL for Funkwhale channels ## [2.0.1] - 2020-03-15 ### Security From adeb82e4966a505e9ac65743e6336db27558e38f Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 8 Apr 2020 00:38:48 +0300 Subject: [PATCH 095/114] CHANGELOG.md: add 2.0.2 update notes --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eed9cf7d..408b932b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `user delete_activities` breaking when trying to delete already deleted posts - Incorrect URL for Funkwhale channels +### Upgrade notes +1. Restart Pleroma + ## [2.0.1] - 2020-03-15 ### Security - Static-FE: Fix remote posts not being sanitized From 9abf13abe05f3f53bdf21d4d97242e571b1767c6 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 8 Apr 2020 00:39:55 +0300 Subject: [PATCH 096/114] CHANGELOG.md: update 2.0.2 release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 408b932b8..bac69ad6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. -## [2.0.2] - 2020-03-31 +## [2.0.2] - 2020-04-08 ### Added - Support for Funkwhale's `Audio` activity - Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials` From c2aad36aa86694d4131adb2ed47441beca2ab2e8 Mon Sep 17 00:00:00 2001 From: kPherox Date: Thu, 9 Apr 2020 23:19:41 +0000 Subject: [PATCH 097/114] Rename function --- mix.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index ad2029518..a1fcde564 100644 --- a/mix.exs +++ b/mix.exs @@ -37,13 +37,13 @@ def project do pleroma: [ include_executables_for: [:unix], applications: [ex_syslogger: :load, syslog: :load], - steps: [:assemble, &put_files/1, ©_files/1, ©_nginx_config/1] + steps: [:assemble, &put_otp_version/1, ©_files/1, ©_nginx_config/1] ] ] ] end - def put_files(%{path: target_path} = release) do + def put_otp_version(%{path: target_path} = release) do File.write!( Path.join([target_path, "OTP_VERSION"]), Pleroma.OTPVersion.version() From 6ff8812ea3403a2f4a31206a96a58fad93fff51f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 10 Apr 2020 11:37:02 -0500 Subject: [PATCH 098/114] Add a section for changelog entries that pertain to the next patch release. This will make it easier to keep changelogs synced between develop and stable branches. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd5d5f800..36897503a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Support pagination in conversations API +## [unreleased-patch] + ## [2.0.2] - 2020-04-08 ### Added - Support for Funkwhale's `Audio` activity From ad92cef844d4f4211a65fd37b08f8bd8abea8dda Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 10 Apr 2020 21:27:50 +0300 Subject: [PATCH 099/114] fix Oban migration --- .../repo/migrations/20200402063221_update_oban_jobs_table.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs index c8ee12192..e7ff04008 100644 --- a/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs +++ b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs @@ -2,10 +2,10 @@ defmodule Pleroma.Repo.Migrations.UpdateObanJobsTable do use Ecto.Migration def up do - Oban.Migrations.up() + Oban.Migrations.up(version: 8) end def down do - Oban.Migrations.down(version: 1) + Oban.Migrations.down(version: 7) end end From 2ba754ffe11b98305e0c0607fec7ca4d510aa67f Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sun, 12 Apr 2020 18:49:31 +0300 Subject: [PATCH 100/114] Fix mix tasks failing on OTP releases No idea why this was even added. Closes #1678 --- lib/mix/pleroma.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 4dfcc32e7..3ad6edbfb 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -5,7 +5,6 @@ defmodule Mix.Pleroma do @doc "Common functions to be reused in mix tasks" def start_pleroma do - Mix.Task.run("app.start") Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) if Pleroma.Config.get(:env) != :test do From a050f3e015a6c5c8d38d535692d4da7a6b1e9c60 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 10 Apr 2020 14:42:52 +0300 Subject: [PATCH 101/114] fix for logger configuration through admin-fe --- lib/pleroma/config/transfer_task.ex | 104 +++++++++++------- .../admin_api/admin_api_controller_test.exs | 17 ++- 2 files changed, 71 insertions(+), 50 deletions(-) diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 936bc9ab1..3871e1cbb 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -54,10 +54,19 @@ def load_and_update_env(deleted_settings \\ [], restart_pleroma? \\ true) do [:pleroma, nil, :prometheus] end + {logger, other} = + (Repo.all(ConfigDB) ++ deleted_settings) + |> Enum.map(&transform_and_merge/1) + |> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end) + + logger + |> Enum.sort() + |> Enum.each(&configure/1) + started_applications = Application.started_applications() - (Repo.all(ConfigDB) ++ deleted_settings) - |> Enum.map(&merge_and_update/1) + other + |> Enum.map(&update/1) |> Enum.uniq() |> Enum.reject(&(&1 in reject_restart)) |> maybe_set_pleroma_last() @@ -81,51 +90,66 @@ defp maybe_set_pleroma_last(apps) do end end - defp group_for_restart(:logger, key, _, merged_value) do - # change logger configuration in runtime, without restart - if Keyword.keyword?(merged_value) and - key not in [:compile_time_application, :backends, :compile_time_purge_matching] do - Logger.configure_backend(key, merged_value) - else - Logger.configure([{key, merged_value}]) - end + defp transform_and_merge(%{group: group, key: key, value: value} = setting) do + group = ConfigDB.from_string(group) + key = ConfigDB.from_string(key) + value = ConfigDB.from_binary(value) - nil + default = Config.Holder.default_config(group, key) + + merged = + cond do + Ecto.get_meta(setting, :state) == :deleted -> default + can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value) + true -> value + end + + {group, key, value, merged} end - defp group_for_restart(group, _, _, _) when group != :pleroma, do: group - - defp group_for_restart(group, key, value, _) do - if pleroma_need_restart?(group, key, value), do: group + # change logger configuration in runtime, without restart + defp configure({:quack, key, _, merged}) do + Logger.configure_backend(Quack.Logger, [{key, merged}]) + :ok = update_env(:quack, key, merged) end - defp merge_and_update(setting) do + defp configure({_, :backends, _, merged}) do + # removing current backends + Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1) + + Enum.each(merged, &Logger.add_backend/1) + + :ok = update_env(:logger, :backends, merged) + end + + defp configure({group, key, _, merged}) do + merged = + if key == :console do + put_in(merged[:format], merged[:format] <> "\n") + else + merged + end + + backend = + if key == :ex_syslogger, + do: {ExSyslogger, :ex_syslogger}, + else: key + + Logger.configure_backend(backend, merged) + :ok = update_env(:logger, group, merged) + end + + defp update({group, key, value, merged}) do try do - key = ConfigDB.from_string(setting.key) - group = ConfigDB.from_string(setting.group) + :ok = update_env(group, key, merged) - default = Config.Holder.default_config(group, key) - value = ConfigDB.from_binary(setting.value) - - merged_value = - cond do - Ecto.get_meta(setting, :state) == :deleted -> default - can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value) - true -> value - end - - :ok = update_env(group, key, merged_value) - - group_for_restart(group, key, value, merged_value) + if group != :pleroma or pleroma_need_restart?(group, key, value), do: group rescue error -> error_msg = - "updating env causes error, group: " <> - inspect(setting.group) <> - " key: " <> - inspect(setting.key) <> - " value: " <> - inspect(ConfigDB.from_binary(setting.value)) <> " error: " <> inspect(error) + "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{ + inspect(value) + } error: #{inspect(error)}" Logger.warn(error_msg) @@ -133,6 +157,9 @@ defp merge_and_update(setting) do end end + defp update_env(group, key, nil), do: Application.delete_env(group, key) + defp update_env(group, key, value), do: Application.put_env(group, key, value) + @spec pleroma_need_restart?(atom(), atom(), any()) :: boolean() def pleroma_need_restart?(group, key, value) do group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value) @@ -150,9 +177,6 @@ defp group_and_subkey_need_reboot?(group, key, value) do end) end - defp update_env(group, key, nil), do: Application.delete_env(group, key) - defp update_env(group, key, value), do: Application.put_env(group, key, value) - defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env) defp restart(started_applications, app, _) do diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index f02f6ae7a..60ec895f5 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -2273,13 +2273,17 @@ test "saving full setting if value is in full_key_update list", %{conn: conn} do value: :erlang.term_to_binary([]) ) + Pleroma.Config.TransferTask.load_and_update_env([], false) + + assert Application.get_env(:logger, :backends) == [] + conn = post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ group: config.group, key: config.key, - value: [":console", %{"tuple" => ["ExSyslogger", ":ex_syslogger"]}] + value: [":console"] } ] }) @@ -2290,8 +2294,7 @@ test "saving full setting if value is in full_key_update list", %{conn: conn} do "group" => ":logger", "key" => ":backends", "value" => [ - ":console", - %{"tuple" => ["ExSyslogger", ":ex_syslogger"]} + ":console" ], "db" => [":backends"] } @@ -2299,14 +2302,8 @@ test "saving full setting if value is in full_key_update list", %{conn: conn} do } assert Application.get_env(:logger, :backends) == [ - :console, - {ExSyslogger, :ex_syslogger} + :console ] - - capture_log(fn -> - require Logger - Logger.warn("Ooops...") - end) =~ "Ooops..." end test "saving full setting if value is not keyword", %{conn: conn} do From de34c4ee6b0487941bfcf02a48b45a578ae329af Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 13 Apr 2020 08:59:06 +0300 Subject: [PATCH 102/114] changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36897503a..e29be28d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Support pagination in conversations API ## [unreleased-patch] +### Fixed +- Logger configuration through AdminFE ## [2.0.2] - 2020-04-08 ### Added From dc2637c18880160286f50505b1140a58fdfdf7d1 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 13 Apr 2020 09:16:35 +0300 Subject: [PATCH 103/114] [#2342] Removed changelog entry for temporary configuration option. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b41502a27..7d9b10b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses. - New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required. -- Configuration: `:extensions/:output_relationships_in_statuses_by_default` option (if `false`, disables the output of account/pleroma/relationship for statuses and notifications by default, breaking the compatibility with older PleromaFE versions).
API Changes - Mastodon API: Support for `include_types` in `/api/v1/notifications`. From 5c76afb06c731557b537f928296e0b5c259f8d5e Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 13 Apr 2020 15:38:50 +0300 Subject: [PATCH 104/114] [#2342] Removed description.exs entry for temporary configuration option. --- config/description.exs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/config/description.exs b/config/description.exs index 1b450db58..642f1a3ce 100644 --- a/config/description.exs +++ b/config/description.exs @@ -121,22 +121,6 @@ } ] }, - %{ - group: :pleroma, - key: :extensions, - type: :group, - description: "Pleroma-specific extensions", - children: [ - %{ - key: :output_relationships_in_statuses_by_default, - type: :beeolean, - description: - "If `true`, outputs account/pleroma/relationship map for each rendered status / notification (for all clients). " <> - "If `false`, outputs the above only if `with_relationships` param is tru-ish " <> - "(that breaks compatibility with older PleromaFE versions which do not send this param but expect the output)." - } - ] - }, %{ group: :pleroma, key: Pleroma.Uploaders.Local, From 4dca712e90a81a1a754608eb4f8e22dae99eb755 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 13 Apr 2020 22:44:52 +0400 Subject: [PATCH 105/114] Add OpenAPI spec for DomainBlockController --- lib/pleroma/web/api_spec.ex | 2 +- .../operations/domain_block_operation.ex | 64 +++++++++++++++++++ .../api_spec/schemas/domain_block_request.ex | 20 ++++++ .../schemas/domain_blocks_response.ex | 16 +++++ .../controllers/domain_block_controller.ex | 7 +- .../domain_block_controller_test.exs | 20 +++++- 6 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/domain_block_operation.ex create mode 100644 lib/pleroma/web/api_spec/schemas/domain_block_request.ex create mode 100644 lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 41e48a085..3890489e3 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -31,7 +31,7 @@ def spec do password: %OpenApiSpex.OAuthFlow{ authorizationUrl: "/oauth/authorize", tokenUrl: "/oauth/token", - scopes: %{"read" => "read"} + scopes: %{"read" => "read", "write" => "write", "follow" => "follow"} } } } diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex new file mode 100644 index 000000000..dd14837c3 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest + alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["domain_blocks"], + summary: "Fetch domain blocks", + description: "View domains the user has blocked.", + security: [%{"oAuth" => ["follow", "read:blocks"]}], + operationId: "DomainBlockController.index", + responses: %{ + 200 => Operation.response("Domain blocks", "application/json", DomainBlocksResponse) + } + } + end + + def create_operation do + %Operation{ + tags: ["domain_blocks"], + summary: "Block a domain", + description: """ + Block a domain to: + + - hide all public posts from it + - hide all notifications from it + - remove all followers from it + - prevent following new users from it (but does not remove existing follows) + """, + operationId: "DomainBlockController.create", + requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true), + security: [%{"oAuth" => ["follow", "write:blocks"]}], + responses: %{ + 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) + } + } + end + + def delete_operation do + %Operation{ + tags: ["domain_blocks"], + summary: "Unblock a domain", + description: "Remove a domain block, if it exists in the user's array of blocked domains.", + operationId: "DomainBlockController.delete", + requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true), + security: [%{"oAuth" => ["follow", "write:blocks"]}], + responses: %{ + 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) + } + } + end +end diff --git a/lib/pleroma/web/api_spec/schemas/domain_block_request.ex b/lib/pleroma/web/api_spec/schemas/domain_block_request.ex new file mode 100644 index 000000000..ee9238361 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/domain_block_request.ex @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest do + alias OpenApiSpex.Schema + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "DomainBlockRequest", + type: :object, + properties: %{ + domain: %Schema{type: :string} + }, + required: [:domain], + example: %{ + "domain" => "facebook.com" + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex b/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex new file mode 100644 index 000000000..d895aca4e --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex @@ -0,0 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse do + require OpenApiSpex + alias OpenApiSpex.Schema + + OpenApiSpex.schema(%{ + title: "DomainBlocksResponse", + description: "Response schema for domain blocks", + type: :array, + items: %Schema{type: :string}, + example: ["google.com", "facebook.com"] + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index e4156cbe6..84de79413 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -8,6 +8,9 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User + plug(OpenApiSpex.Plug.CastAndValidate) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation + plug( OAuthScopesPlug, %{scopes: ["follow", "read:blocks"]} when action == :index @@ -26,13 +29,13 @@ def index(%{assigns: %{user: user}} = conn, _) do end @doc "POST /api/v1/domain_blocks" - def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do + def create(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do User.block_domain(blocker, domain) json(conn, %{}) end @doc "DELETE /api/v1/domain_blocks" - def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do + def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do User.unblock_domain(blocker, domain) json(conn, %{}) end diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/web/mastodon_api/controllers/domain_block_controller_test.exs index 8d24b3b88..d66190c90 100644 --- a/test/web/mastodon_api/controllers/domain_block_controller_test.exs +++ b/test/web/mastodon_api/controllers/domain_block_controller_test.exs @@ -6,20 +6,29 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do use Pleroma.Web.ConnCase alias Pleroma.User + alias Pleroma.Web.ApiSpec + alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse import Pleroma.Factory + import OpenApiSpex.TestAssertions test "blocking / unblocking a domain" do %{user: user, conn: conn} = oauth_access(["write:blocks"]) other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) - ret_conn = post(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + ret_conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) assert %{} = json_response(ret_conn, 200) user = User.get_cached_by_ap_id(user.ap_id) assert User.blocks?(user, other_user) - ret_conn = delete(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + ret_conn = + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) assert %{} = json_response(ret_conn, 200) user = User.get_cached_by_ap_id(user.ap_id) @@ -41,5 +50,12 @@ test "getting a list of domain blocks" do assert "bad.site" in domain_blocks assert "even.worse.site" in domain_blocks + assert_schema(domain_blocks, "DomainBlocksResponse", ApiSpec.spec()) + end + + test "DomainBlocksResponse example matches schema" do + api_spec = ApiSpec.spec() + schema = DomainBlocksResponse.schema() + assert_schema(schema.example, "DomainBlocksResponse", api_spec) end end From f3725b8fc4506e4bb400878214b9886ed954590f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 13 Apr 2020 17:04:43 -0500 Subject: [PATCH 106/114] Fix spelling --- lib/pleroma/pool/connections.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/pool/connections.ex b/lib/pleroma/pool/connections.ex index 4d4ba913c..acafe1bea 100644 --- a/lib/pleroma/pool/connections.ex +++ b/lib/pleroma/pool/connections.ex @@ -243,7 +243,7 @@ def handle_info({:gun_down, conn_pid, _protocol, _reason, _killed}, state) do @impl true def handle_info({:DOWN, _ref, :process, conn_pid, reason}, state) do - Logger.debug("received DOWM message for #{inspect(conn_pid)} reason -> #{inspect(reason)}") + Logger.debug("received DOWN message for #{inspect(conn_pid)} reason -> #{inspect(reason)}") state = with {key, conn} <- find_conn(state.conns, conn_pid) do From c4e7ed660c0c561d7b014664ca393d39dc7ee29a Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 14 Apr 2020 08:43:47 +0300 Subject: [PATCH 107/114] fix logger message --- lib/pleroma/plugs/mapped_signature_to_identity_plug.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex index 4f124ed4d..84b7c5d83 100644 --- a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex @@ -42,13 +42,13 @@ def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = con else {:user_match, false} -> Logger.debug("Failed to map identity from signature (payload actor mismatch)") - Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}") + Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}") assign(conn, :valid_signature, false) # remove me once testsuite uses mapped capabilities instead of what we do now {:user, nil} -> Logger.debug("Failed to map identity from signature (lookup failure)") - Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}") + Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}") conn end end @@ -60,7 +60,7 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do else _ -> Logger.debug("Failed to map identity from signature (no payload actor mismatch)") - Logger.debug("key_id=#{key_id_from_conn(conn)}") + Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}") assign(conn, :valid_signature, false) end end From d8b12ffd5909a2698cce50d81b69f00b8893d41b Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 14 Apr 2020 15:06:09 +0200 Subject: [PATCH 108/114] Marker update migration: Don't try to update virtual field. --- priv/repo/migrations/20200210050658_update_markers.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/repo/migrations/20200210050658_update_markers.exs b/priv/repo/migrations/20200210050658_update_markers.exs index b280e156c..db7a355ec 100644 --- a/priv/repo/migrations/20200210050658_update_markers.exs +++ b/priv/repo/migrations/20200210050658_update_markers.exs @@ -32,7 +32,7 @@ defp update_markers do end) Repo.insert_all("markers", markers_attrs, - on_conflict: {:replace, [:last_read_id, :unread_count]}, + on_conflict: {:replace, [:last_read_id]}, conflict_target: [:user_id, :timeline] ) end From 7c060432fcac294269742ac3f452beb3f9a2fdab Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 14 Apr 2020 16:31:30 +0000 Subject: [PATCH 109/114] Revert "Merge branch 'marker-update-fix' into 'develop'" This reverts merge request !2380 --- priv/repo/migrations/20200210050658_update_markers.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/repo/migrations/20200210050658_update_markers.exs b/priv/repo/migrations/20200210050658_update_markers.exs index db7a355ec..b280e156c 100644 --- a/priv/repo/migrations/20200210050658_update_markers.exs +++ b/priv/repo/migrations/20200210050658_update_markers.exs @@ -32,7 +32,7 @@ defp update_markers do end) Repo.insert_all("markers", markers_attrs, - on_conflict: {:replace, [:last_read_id]}, + on_conflict: {:replace, [:last_read_id, :unread_count]}, conflict_target: [:user_id, :timeline] ) end From 4576520461e2e3a1c78133aaf31cb742a2a1a689 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 14 Apr 2020 16:32:22 +0000 Subject: [PATCH 110/114] Revert "Merge branch 'issue/1276' into 'develop'" This reverts merge request !1877 --- CHANGELOG.md | 1 - docs/API/differences_in_mastoapi_responses.md | 17 ++----- lib/pleroma/marker.ex | 45 +---------------- lib/pleroma/notification.ex | 47 +++++------------ .../web/mastodon_api/views/marker_view.ex | 5 +- mix.lock | 50 +++++++++---------- .../20200210050658_update_markers.exs | 39 --------------- test/marker_test.exs | 29 +---------- test/notification_test.exs | 13 ----- .../controllers/marker_controller_test.exs | 10 ++-- .../mastodon_api/views/marker_view_test.exs | 8 ++- 11 files changed, 51 insertions(+), 213 deletions(-) delete mode 100644 priv/repo/migrations/20200210050658_update_markers.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f7fc1802..56b235f6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,7 +123,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: `pleroma.thread_muted` to the Status entity - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message - Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload. -- Mastodon API: Add `pleroma.unread_count` to the Marker entity - Admin API: Render whole status in grouped reports - Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise). - Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try. diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 0a7520f9e..1059155cf 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -185,15 +185,8 @@ Post here request with `grant_type=refresh_token` to obtain new access token. Re Has theses additional parameters (which are the same as in Pleroma-API): - `fullname`: optional - `bio`: optional - `captcha_solution`: optional, contains provider-specific captcha solution, - `captcha_token`: optional, contains provider-specific captcha token - `token`: invite token required when the registrations aren't public. - - -## Markers - -Has these additional fields under the `pleroma` object: - -- `unread_count`: contains number unread notifications +- `fullname`: optional +- `bio`: optional +- `captcha_solution`: optional, contains provider-specific captcha solution, +- `captcha_token`: optional, contains provider-specific captcha token +- `token`: invite token required when the registrations aren't public. diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index 4d82860f5..443927392 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -9,34 +9,24 @@ defmodule Pleroma.Marker do import Ecto.Query alias Ecto.Multi - alias Pleroma.Notification alias Pleroma.Repo alias Pleroma.User - alias __MODULE__ @timelines ["notifications"] - @type t :: %__MODULE__{} schema "markers" do field(:last_read_id, :string, default: "") field(:timeline, :string, default: "") field(:lock_version, :integer, default: 0) - field(:unread_count, :integer, default: 0, virtual: true) belongs_to(:user, User, type: FlakeId.Ecto.CompatType) timestamps() end - @doc "Gets markers by user and timeline." - @spec get_markers(User.t(), list(String)) :: list(t()) def get_markers(user, timelines \\ []) do - user - |> get_query(timelines) - |> unread_count_query() - |> Repo.all() + Repo.all(get_query(user, timelines)) end - @spec upsert(User.t(), map()) :: {:ok | :error, any()} def upsert(%User{} = user, attrs) do attrs |> Map.take(@timelines) @@ -55,27 +45,6 @@ def upsert(%User{} = user, attrs) do |> Repo.transaction() end - @spec multi_set_last_read_id(Multi.t(), User.t(), String.t()) :: Multi.t() - def multi_set_last_read_id(multi, %User{} = user, "notifications") do - multi - |> Multi.run(:counters, fn _repo, _changes -> - {:ok, %{last_read_id: Repo.one(Notification.last_read_query(user))}} - end) - |> Multi.insert( - :marker, - fn %{counters: attrs} -> - %Marker{timeline: "notifications", user_id: user.id} - |> struct(attrs) - |> Ecto.Changeset.change() - end, - returning: true, - on_conflict: {:replace, [:last_read_id]}, - conflict_target: [:user_id, :timeline] - ) - end - - def multi_set_last_read_id(multi, _, _), do: multi - defp get_marker(user, timeline) do case Repo.find_resource(get_query(user, timeline)) do {:ok, marker} -> %__MODULE__{marker | user: user} @@ -102,16 +71,4 @@ defp get_query(user, timelines) do |> by_user_id(user.id) |> by_timeline(timelines) end - - defp unread_count_query(query) do - from( - q in query, - left_join: n in "notifications", - on: n.user_id == q.user_id and n.seen == false, - group_by: [:id], - select_merge: %{ - unread_count: fragment("count(?)", n.id) - } - ) - end end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 3084bac3b..04ee510b9 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -5,9 +5,7 @@ defmodule Pleroma.Notification do use Ecto.Schema - alias Ecto.Multi alias Pleroma.Activity - alias Pleroma.Marker alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Pagination @@ -40,17 +38,6 @@ def changeset(%Notification{} = notification, attrs) do |> cast(attrs, [:seen]) end - @spec last_read_query(User.t()) :: Ecto.Queryable.t() - def last_read_query(user) do - from(q in Pleroma.Notification, - where: q.user_id == ^user.id, - where: q.seen == true, - select: type(q.id, :string), - limit: 1, - order_by: [desc: :id] - ) - end - defp for_user_query_ap_id_opts(user, opts) do ap_id_relationships = [:block] ++ @@ -199,23 +186,25 @@ def for_user_since(user, date) do |> Repo.all() end - def set_read_up_to(%{id: user_id} = user, id) do + def set_read_up_to(%{id: user_id} = _user, id) do query = from( n in Notification, where: n.user_id == ^user_id, where: n.id <= ^id, where: n.seen == false, + update: [ + set: [ + seen: true, + updated_at: ^NaiveDateTime.utc_now() + ] + ], # Ideally we would preload object and activities here # but Ecto does not support preloads in update_all select: n.id ) - {:ok, %{ids: {_, notification_ids}}} = - Multi.new() - |> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()]) - |> Marker.multi_set_last_read_id(user, "notifications") - |> Repo.transaction() + {_, notification_ids} = Repo.update_all(query, []) Notification |> where([n], n.id in ^notification_ids) @@ -232,18 +221,11 @@ def set_read_up_to(%{id: user_id} = user, id) do |> Repo.all() end - @spec read_one(User.t(), String.t()) :: - {:ok, Notification.t()} | {:error, Ecto.Changeset.t()} | nil def read_one(%User{} = user, notification_id) do with {:ok, %Notification{} = notification} <- get(user, notification_id) do - Multi.new() - |> Multi.update(:update, changeset(notification, %{seen: true})) - |> Marker.multi_set_last_read_id(user, "notifications") - |> Repo.transaction() - |> case do - {:ok, %{update: notification}} -> {:ok, notification} - {:error, :update, changeset, _} -> {:error, changeset} - end + notification + |> changeset(%{seen: true}) + |> Repo.update() end end @@ -325,11 +307,8 @@ defp do_create_notifications(%Activity{} = activity) do # TODO move to sql, too. def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do unless skip?(activity, user) do - {:ok, %{notification: notification}} = - Multi.new() - |> Multi.insert(:notification, %Notification{user_id: user.id, activity: activity}) - |> Marker.multi_set_last_read_id(user, "notifications") - |> Repo.transaction() + notification = %Notification{user_id: user.id, activity: activity} + {:ok, notification} = Repo.insert(notification) if do_send do Streamer.stream(["user", "user:notification"], notification) diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex index 415dae93b..985368fe5 100644 --- a/lib/pleroma/web/mastodon_api/views/marker_view.ex +++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex @@ -10,10 +10,7 @@ def render("markers.json", %{markers: markers}) do Map.put_new(acc, m.timeline, %{ last_read_id: m.last_read_id, version: m.lock_version, - updated_at: NaiveDateTime.to_iso8601(m.updated_at), - pleroma: %{ - unread_count: m.unread_count - } + updated_at: NaiveDateTime.to_iso8601(m.updated_at) }) end) end diff --git a/mix.lock b/mix.lock index 23467bbb4..ba4e3ac44 100644 --- a/mix.lock +++ b/mix.lock @@ -2,8 +2,8 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"}, - "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"}, - "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5a981b98ac7d366a9b6bf40eac389aaf4d6e623c631e6b6f8a6b571efaafd338"}, + "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, + "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, @@ -19,47 +19,47 @@ "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"}, - "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "48e513299cd28b12c77266c0ed5b1c844368e5c1823724994ae84834f43d6bbe"}, + "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm", "5e8806285d8a3a8999bd38e4a73c58d28534c856bc38c44818e5ba85bbda16fb"}, - "ecto": {:hex, :ecto, "3.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3959b8a83e086202a4bd86b4b5e6e71f9f1840813de14a57d502d3fc2ef7132"}, + "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, + "ecto": {:hex, :ecto, "3.4.0", "a7a83ab8359bf816ce729e5e65981ce25b9fc5adfc89c2ea3980f4fed0bfd7c1", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5eed18252f5b5bbadec56a24112b531343507dbe046273133176b12190ce19cc"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"}, - "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm", "98d0f3c6f4b8a0333170df770c6fe772b3d04564fb514c1a09504cf5ab2f48a5"}, + "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"}, + "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "b84f6af156264530b312a8ab98ac6088f6b77ae5fe2058305c81434aa01fbaf9"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.0", "bc936ee3fd13d9e592cb4c3a1e8a55fccd33b05e3aa7b185f211f3ed263ff8f0", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.0.5", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "f3b4b184dcdd5f356b7c26c6cd72ab0918ba9dfb4061ccfaf519e562942af87b"}, "excoveralls": {:hex, :excoveralls, "0.12.2", "a513defac45c59e310ac42fcf2b8ae96f1f85746410f30b1ff2b710a4b6cd44b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "151c476331d49b45601ffc45f43cb3a8beb396b02a34e3777fea0ad34ae57d89"}, - "fast_html": {:hex, :fast_html, "1.0.1", "5bc7df4dc4607ec2c314c16414e4111d79a209956c4f5df96602d194c61197f9", [:make, :mix], [], "hexpm", "18e627dd62051a375ef94b197f41e8027c3e8eef0180ab8f81e0543b3dc6900a"}, - "fast_sanitize": {:hex, :fast_sanitize, "0.1.6", "60a5ae96879956dea409a91a77f5dd2994c24cc10f80eefd8f9892ee4c0c7b25", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b73f50f0cb522dd0331ea8e8c90b408de42c50f37641219d6364f0e3e7efd22c"}, + "fast_html": {:hex, :fast_html, "1.0.3", "2cc0d4b68496266a1530e0c852cafeaede0bd10cfdee26fda50dc696c203162f", [:make, :mix], [], "hexpm", "ab3d782b639d3c4655fbaec0f9d032c91f8cab8dd791ac7469c2381bc7c32f85"}, + "fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, - "floki": {:hex, :floki, "0.26.0", "4df88977e2e357c6720e1b650f613444bfb48c5acfc6a0c646ab007d08ad13bf", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e7b66ce7feef5518a9cd9fc7b52dd62a64028bd9cb6d6ad282a0f0fc90a4ae52"}, + "floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, - "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm", "8453e2289d94c3199396eb517d65d6715ef26bcae0ee83eb5ff7a84445458d76"}, - "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm", "5cacd405e72b2609a7e1f891bddb80c53d0b3b7b0036d1648e7382ca108c41c8"}, - "gettext": {:hex, :gettext, "0.17.1", "8baab33482df4907b3eae22f719da492cee3981a26e649b9c2be1c0192616962", [:mix], [], "hexpm", "f7d97341e536f95b96eef2988d6d4230f7262cf239cda0e2e63123ee0b717222"}, + "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"}, + "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, + "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"}, "gun": {:git, "https://github.com/ninenines/gun.git", "e1a69b36b180a574c0ac314ced9613fdd52312cc", [ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc"]}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]}, - "httpoison": {:hex, :httpoison, "1.6.1", "2ce5bf6e535cd0ab02e905ba8c276580bab80052c5c549f53ddea52d72e81f33", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "89149056039084024a284cd703b2d1900d584958dba432132cb21ef35aed7487"}, + "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, "jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"}, - "joken": {:hex, :joken, "2.1.0", "bf21a73105d82649f617c5e59a7f8919aa47013d2519ebcc39d998d8d12adda9", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "eb02df7d5526df13063397e051b926b7006d5986d66f399eefc474f560cdad6a"}, - "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm", "6429c4fee52b2dda7861ee19a4f09c8c1ffa213bee3a1ec187828fde95d447ed"}, + "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"}, + "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, - "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm", "1feaf05ee886815ad047cad7ede17d6910710986148ae09cf73eee2989717b81"}, + "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, @@ -71,37 +71,35 @@ "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm", "3bc928d817974fa10cc11e6c89b9a9361e37e96dbbf3d868c41094ec05745dcd"}, "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm", "052346cf322311c49a0f22789f3698eea030eec09b8c47367f0686ef2634ae14"}, "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm", "00e3ebdc821fb3a36957320d49e8f4bfa310d73ea31c90e5f925dc75e030da8f"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"}, "open_api_spex": {:hex, :open_api_spex, "3.6.0", "64205aba9f2607f71b08fd43e3351b9c5e9898ec5ef49fc0ae35890da502ade9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "126ba3473966277132079cb1d5bf1e3df9e36fe2acd00166e75fd125cecb59c5"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"}, - "phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "256ad7a140efadc3f0290470369da5bd3de985ec7c706eba07c2641b228974be"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "fe15d9fee5b82f5e64800502011ffe530650d42e1710ae9b14bc4c9be38bf303"}, - "phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8b01b3d6d39731ab18aa548d928b5796166d2500755f553725cfe967bafba7d9"}, + "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, + "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"}, "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6cd8ddd1bd1fbfa54d3fc61d4719c2057dae67615395d58d40437a919a46f132"}, - "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm", "73c1682f0e414cfb5d9b95c8e8cd6ffcfdae699e3b05e1db744e58b7be857759"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"}, + "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"}, - "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm", "d39f2ce1f3f29f3bf04f915aa3cf9c7cd4d2cee2f975e05f526e06cae9b7c902"}, + "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"}, - "quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm", "6de553ba9ac0668d3728b699d5065543f3e40c854154017461ee8c09038752da"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "825dc00aaba5a1b7c4202a532b696b595dd3bcb3", [ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"]}, "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, - "swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "94884f84783fc1ba027aba8fe8a7dae4aad78c98e9f9c76667ec3471585c08c6"}, "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, "swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"}, "syslog": {:hex, :syslog, "1.0.6", "995970c9aa7feb380ac493302138e308d6e04fd57da95b439a6df5bb3bf75076", [:rebar3], [], "hexpm", "769ddfabd0d2a16f3f9c17eb7509951e0ca4f68363fb26f2ee51a8ec4a49881a"}, diff --git a/priv/repo/migrations/20200210050658_update_markers.exs b/priv/repo/migrations/20200210050658_update_markers.exs deleted file mode 100644 index b280e156c..000000000 --- a/priv/repo/migrations/20200210050658_update_markers.exs +++ /dev/null @@ -1,39 +0,0 @@ -defmodule Pleroma.Repo.Migrations.UpdateMarkers do - use Ecto.Migration - import Ecto.Query - alias Pleroma.Repo - - def up do - update_markers() - end - - def down do - :ok - end - - defp update_markers do - now = NaiveDateTime.utc_now() - - markers_attrs = - from(q in "notifications", - select: %{ - timeline: "notifications", - user_id: q.user_id, - last_read_id: - type(fragment("MAX( CASE WHEN seen = true THEN id ELSE null END )"), :string) - }, - group_by: [q.user_id] - ) - |> Repo.all() - |> Enum.map(fn attrs -> - attrs - |> Map.put_new(:inserted_at, now) - |> Map.put_new(:updated_at, now) - end) - - Repo.insert_all("markers", markers_attrs, - on_conflict: {:replace, [:last_read_id, :unread_count]}, - conflict_target: [:user_id, :timeline] - ) - end -end diff --git a/test/marker_test.exs b/test/marker_test.exs index 5b6d0b4a4..c80ae16b6 100644 --- a/test/marker_test.exs +++ b/test/marker_test.exs @@ -8,39 +8,12 @@ defmodule Pleroma.MarkerTest do import Pleroma.Factory - describe "multi_set_unread_count/3" do - test "returns multi" do - user = insert(:user) - - assert %Ecto.Multi{ - operations: [marker: {:run, _}, counters: {:run, _}] - } = - Marker.multi_set_last_read_id( - Ecto.Multi.new(), - user, - "notifications" - ) - end - - test "return empty multi" do - user = insert(:user) - multi = Ecto.Multi.new() - assert Marker.multi_set_last_read_id(multi, user, "home") == multi - end - end - describe "get_markers/2" do test "returns user markers" do user = insert(:user) marker = insert(:marker, user: user) - insert(:notification, user: user) - insert(:notification, user: user) insert(:marker, timeline: "home", user: user) - - assert Marker.get_markers( - user, - ["notifications"] - ) == [%Marker{refresh_record(marker) | unread_count: 2}] + assert Marker.get_markers(user, ["notifications"]) == [refresh_record(marker)] end end diff --git a/test/notification_test.exs b/test/notification_test.exs index f78a47af6..837a9dacd 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -45,9 +45,6 @@ test "notifies someone when they are directly addressed" do assert notified_ids == [other_user.id, third_user.id] assert notification.activity_id == activity.id assert other_notification.activity_id == activity.id - - assert [%Pleroma.Marker{unread_count: 2}] = - Pleroma.Marker.get_markers(other_user, ["notifications"]) end test "it creates a notification for subscribed users" do @@ -413,16 +410,6 @@ test "it sets all notifications as read up to a specified notification ID" do assert n1.seen == true assert n2.seen == true assert n3.seen == false - - assert %Pleroma.Marker{} = - m = - Pleroma.Repo.get_by( - Pleroma.Marker, - user_id: other_user.id, - timeline: "notifications" - ) - - assert m.last_read_id == to_string(n2.id) end end diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 7280abd10..919f295bd 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -11,7 +11,6 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do test "gets markers with correct scopes", %{conn: conn} do user = insert(:user) token = insert(:oauth_token, user: user, scopes: ["read:statuses"]) - insert_list(7, :notification, user: user) {:ok, %{"notifications" => marker}} = Pleroma.Marker.upsert( @@ -30,8 +29,7 @@ test "gets markers with correct scopes", %{conn: conn} do "notifications" => %{ "last_read_id" => "69420", "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), - "version" => 0, - "pleroma" => %{"unread_count" => 7} + "version" => 0 } } end @@ -72,8 +70,7 @@ test "creates a marker with correct scopes", %{conn: conn} do "notifications" => %{ "last_read_id" => "69420", "updated_at" => _, - "version" => 0, - "pleroma" => %{"unread_count" => 0} + "version" => 0 } } = response end @@ -102,8 +99,7 @@ test "updates exist marker", %{conn: conn} do "notifications" => %{ "last_read_id" => "69888", "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), - "version" => 0, - "pleroma" => %{"unread_count" => 0} + "version" => 0 } } end diff --git a/test/web/mastodon_api/views/marker_view_test.exs b/test/web/mastodon_api/views/marker_view_test.exs index 48a0a6d33..893cf8857 100644 --- a/test/web/mastodon_api/views/marker_view_test.exs +++ b/test/web/mastodon_api/views/marker_view_test.exs @@ -8,21 +8,19 @@ defmodule Pleroma.Web.MastodonAPI.MarkerViewTest do import Pleroma.Factory test "returns markers" do - marker1 = insert(:marker, timeline: "notifications", last_read_id: "17", unread_count: 5) + marker1 = insert(:marker, timeline: "notifications", last_read_id: "17") marker2 = insert(:marker, timeline: "home", last_read_id: "42") assert MarkerView.render("markers.json", %{markers: [marker1, marker2]}) == %{ "home" => %{ last_read_id: "42", updated_at: NaiveDateTime.to_iso8601(marker2.updated_at), - version: 0, - pleroma: %{unread_count: 0} + version: 0 }, "notifications" => %{ last_read_id: "17", updated_at: NaiveDateTime.to_iso8601(marker1.updated_at), - version: 0, - pleroma: %{unread_count: 5} + version: 0 } } end From 3bf78f2be7c151cd64ed954570dbf8592e836f56 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 14 Apr 2020 11:43:53 -0500 Subject: [PATCH 111/114] Fix Oban not receiving :ok from RichMediaHelper job --- lib/pleroma/web/rich_media/helpers.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 0314535d2..9d3d7f978 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -64,5 +64,8 @@ def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) d def fetch_data_for_activity(_), do: %{} - def perform(:fetch, %Activity{} = activity), do: fetch_data_for_activity(activity) + def perform(:fetch, %Activity{} = activity) do + fetch_data_for_activity(activity) + :ok + end end From cc4ff19e34fae2c4ba944e235861b6cb800b7c86 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 15 Apr 2020 00:49:21 +0300 Subject: [PATCH 112/114] openapi: add application/x-www-form-urlencoded to body types Closes #1683 --- lib/pleroma/web/api_spec/helpers.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 35cf4c0d8..7348dcbee 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Helpers do def request_body(description, schema_ref, opts \\ []) do - media_types = ["application/json", "multipart/form-data"] + media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"] content = media_types From 6bc76df287d7f4beb35c3a55b784b07ce9d833ff Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 15 Apr 2020 12:05:22 +0200 Subject: [PATCH 113/114] Uploads: Sandbox them in the CSP. --- lib/pleroma/plugs/uploaded_media.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/plugs/uploaded_media.ex index 36ff024a7..94147e0c4 100644 --- a/lib/pleroma/plugs/uploaded_media.ex +++ b/lib/pleroma/plugs/uploaded_media.ex @@ -41,6 +41,7 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do conn -> conn end + |> merge_resp_headers([{"content-security-policy", "sandbox"}]) config = Pleroma.Config.get(Pleroma.Upload) From 22bde21c4f1a84a1fbe733070e8926366a3c01dc Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 15 Apr 2020 15:27:34 +0300 Subject: [PATCH 114/114] remote_ip plug adds remote_ip_found flag --- .../plugs/rate_limiter/rate_limiter.ex | 17 ++-------- lib/pleroma/plugs/remote_ip.ex | 7 ++--- mix.exs | 2 +- mix.lock | 2 +- test/plugs/rate_limiter_test.exs | 31 ++++++++++++------- 5 files changed, 27 insertions(+), 32 deletions(-) diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex index 1529da717..c51e2c634 100644 --- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex +++ b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex @@ -110,20 +110,9 @@ defp handle(conn, action_settings) do end def disabled?(conn) do - localhost_or_socket = - case Config.get([Pleroma.Web.Endpoint, :http, :ip]) do - {127, 0, 0, 1} -> true - {0, 0, 0, 0, 0, 0, 0, 1} -> true - {:local, _} -> true - _ -> false - end - - remote_ip_not_found = - if Map.has_key?(conn.assigns, :remote_ip_found), - do: !conn.assigns.remote_ip_found, - else: false - - localhost_or_socket and remote_ip_not_found + if Map.has_key?(conn.assigns, :remote_ip_found), + do: !conn.assigns.remote_ip_found, + else: false end @inspect_bucket_not_found {:error, :not_found} diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex index 0ac9050d0..2eca4f8f6 100644 --- a/lib/pleroma/plugs/remote_ip.ex +++ b/lib/pleroma/plugs/remote_ip.ex @@ -7,8 +7,6 @@ defmodule Pleroma.Plugs.RemoteIp do This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. """ - import Plug.Conn - @behaviour Plug @headers ~w[ @@ -28,12 +26,11 @@ defmodule Pleroma.Plugs.RemoteIp do def init(_), do: nil - def call(%{remote_ip: original_remote_ip} = conn, _) do + def call(conn, _) do config = Pleroma.Config.get(__MODULE__, []) if Keyword.get(config, :enabled, false) do - %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config)) - assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) + RemoteIp.call(conn, remote_ip_opts(config)) else conn end diff --git a/mix.exs b/mix.exs index c781995e0..c5e5fd432 100644 --- a/mix.exs +++ b/mix.exs @@ -183,7 +183,7 @@ defp deps do {:flake_id, "~> 0.1.0"}, {:remote_ip, git: "https://git.pleroma.social/pleroma/remote_ip.git", - ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"}, + ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"}, {:captcha, git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"}, diff --git a/mix.lock b/mix.lock index ba4e3ac44..2b9c54548 100644 --- a/mix.lock +++ b/mix.lock @@ -97,7 +97,7 @@ "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"}, - "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "825dc00aaba5a1b7c4202a532b696b595dd3bcb3", [ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"]}, + "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs index 0ce9f3a0a..4d3d694f4 100644 --- a/test/plugs/rate_limiter_test.exs +++ b/test/plugs/rate_limiter_test.exs @@ -5,8 +5,10 @@ defmodule Pleroma.Plugs.RateLimiterTest do use Pleroma.Web.ConnCase + alias Phoenix.ConnTest alias Pleroma.Config alias Pleroma.Plugs.RateLimiter + alias Plug.Conn import Pleroma.Factory import Pleroma.Tests.Helpers, only: [clear_config: 1, clear_config: 2] @@ -36,8 +38,15 @@ test "config is required for plug to work" do end test "it is disabled if it remote ip plug is enabled but no remote ip is found" do - Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1}) - assert RateLimiter.disabled?(Plug.Conn.assign(build_conn(), :remote_ip_found, false)) + assert RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, false)) + end + + test "it is enabled if remote ip found" do + refute RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, true)) + end + + test "it is enabled if remote_ip_found flag doesn't exist" do + refute RateLimiter.disabled?(build_conn()) end test "it restricts based on config values" do @@ -58,7 +67,7 @@ test "it restricts based on config values" do end conn = RateLimiter.call(conn, plug_opts) - assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) + assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests) assert conn.halted Process.sleep(50) @@ -68,7 +77,7 @@ test "it restricts based on config values" do conn = RateLimiter.call(conn, plug_opts) assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) - refute conn.status == Plug.Conn.Status.code(:too_many_requests) + refute conn.status == Conn.Status.code(:too_many_requests) refute conn.resp_body refute conn.halted end @@ -98,7 +107,7 @@ test "`params` option allows different queries to be tracked independently" do plug_opts = RateLimiter.init(name: limiter_name, params: ["id"]) conn = build_conn(:get, "/?id=1") - conn = Plug.Conn.fetch_query_params(conn) + conn = Conn.fetch_query_params(conn) conn_2 = build_conn(:get, "/?id=2") RateLimiter.call(conn, plug_opts) @@ -119,7 +128,7 @@ test "it supports combination of options modifying bucket name" do id = "100" conn = build_conn(:get, "/?id=#{id}") - conn = Plug.Conn.fetch_query_params(conn) + conn = Conn.fetch_query_params(conn) conn_2 = build_conn(:get, "/?id=#{101}") RateLimiter.call(conn, plug_opts) @@ -147,13 +156,13 @@ test "are restricted based on remote IP" do conn = RateLimiter.call(conn, plug_opts) - assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) + assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests) assert conn.halted conn_2 = RateLimiter.call(conn_2, plug_opts) assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts) - refute conn_2.status == Plug.Conn.Status.code(:too_many_requests) + refute conn_2.status == Conn.Status.code(:too_many_requests) refute conn_2.resp_body refute conn_2.halted end @@ -187,7 +196,7 @@ test "can have limits separate from unauthenticated connections" do conn = RateLimiter.call(conn, plug_opts) - assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) + assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests) assert conn.halted end @@ -210,12 +219,12 @@ test "different users are counted independently" do end conn = RateLimiter.call(conn, plug_opts) - assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) + assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests) assert conn.halted conn_2 = RateLimiter.call(conn_2, plug_opts) assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts) - refute conn_2.status == Plug.Conn.Status.code(:too_many_requests) + refute conn_2.status == Conn.Status.code(:too_many_requests) refute conn_2.resp_body refute conn_2.halted end