Cleanup `Pleroma.Activity` and add more tests See merge request pleroma/pleroma!1623tags/v1.1.4
@@ -6,6 +6,7 @@ defmodule Pleroma.Activity do | |||
use Ecto.Schema | |||
alias Pleroma.Activity | |||
alias Pleroma.Activity.Queries | |||
alias Pleroma.ActivityExpiration | |||
alias Pleroma.Bookmark | |||
alias Pleroma.Notification | |||
@@ -65,8 +66,8 @@ defmodule Pleroma.Activity do | |||
timestamps() | |||
end | |||
def with_joined_object(query) do | |||
join(query, :inner, [activity], o in Object, | |||
def with_joined_object(query, join_type \\ :inner) do | |||
join(query, join_type, [activity], o in Object, | |||
on: | |||
fragment( | |||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", | |||
@@ -78,10 +79,10 @@ defmodule Pleroma.Activity do | |||
) | |||
end | |||
def with_preloaded_object(query) do | |||
def with_preloaded_object(query, join_type \\ :inner) do | |||
query | |||
|> has_named_binding?(:object) | |||
|> if(do: query, else: with_joined_object(query)) | |||
|> if(do: query, else: with_joined_object(query, join_type)) | |||
|> preload([activity, object: object], object: object) | |||
end | |||
@@ -107,12 +108,9 @@ defmodule Pleroma.Activity do | |||
def with_set_thread_muted_field(query, _), do: query | |||
def get_by_ap_id(ap_id) do | |||
Repo.one( | |||
from( | |||
activity in Activity, | |||
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)) | |||
) | |||
) | |||
ap_id | |||
|> Queries.by_ap_id() | |||
|> Repo.one() | |||
end | |||
def get_bookmark(%Activity{} = activity, %User{} = user) do | |||
@@ -133,21 +131,10 @@ defmodule Pleroma.Activity do | |||
end | |||
def get_by_ap_id_with_object(ap_id) do | |||
Repo.one( | |||
from( | |||
activity in Activity, | |||
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)), | |||
left_join: o in Object, | |||
on: | |||
fragment( | |||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", | |||
o.data, | |||
activity.data, | |||
activity.data | |||
), | |||
preload: [object: o] | |||
) | |||
) | |||
ap_id | |||
|> Queries.by_ap_id() | |||
|> with_preloaded_object(:left) | |||
|> Repo.one() | |||
end | |||
def get_by_id(id) do | |||
@@ -158,18 +145,9 @@ defmodule Pleroma.Activity do | |||
end | |||
def get_by_id_with_object(id) do | |||
from(activity in Activity, | |||
where: activity.id == ^id, | |||
inner_join: o in Object, | |||
on: | |||
fragment( | |||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", | |||
o.data, | |||
activity.data, | |||
activity.data | |||
), | |||
preload: [object: o] | |||
) | |||
Activity | |||
|> where(id: ^id) | |||
|> with_preloaded_object() | |||
|> Repo.one() | |||
end | |||
@@ -180,51 +158,21 @@ defmodule Pleroma.Activity do | |||
|> Repo.all() | |||
end | |||
def by_object_ap_id(ap_id) do | |||
from( | |||
activity in Activity, | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||
activity.data, | |||
activity.data, | |||
^to_string(ap_id) | |||
) | |||
) | |||
end | |||
def create_by_object_ap_id(ap_ids) when is_list(ap_ids) do | |||
from( | |||
activity in Activity, | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)", | |||
activity.data, | |||
activity.data, | |||
^ap_ids | |||
), | |||
where: fragment("(?)->>'type' = 'Create'", activity.data) | |||
) | |||
end | |||
def create_by_object_ap_id(ap_id) when is_binary(ap_id) do | |||
from( | |||
activity in Activity, | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||
activity.data, | |||
activity.data, | |||
^to_string(ap_id) | |||
), | |||
where: fragment("(?)->>'type' = 'Create'", activity.data) | |||
) | |||
@doc """ | |||
Accepts `ap_id` or list of `ap_id`. | |||
Returns a query. | |||
""" | |||
@spec create_by_object_ap_id(String.t() | [String.t()]) :: Ecto.Queryable.t() | |||
def create_by_object_ap_id(ap_id) do | |||
ap_id | |||
|> Queries.by_object_id() | |||
|> Queries.by_type("Create") | |||
end | |||
def create_by_object_ap_id(_), do: nil | |||
def get_all_create_by_object_ap_id(ap_id) do | |||
Repo.all(create_by_object_ap_id(ap_id)) | |||
ap_id | |||
|> create_by_object_ap_id() | |||
|> Repo.all() | |||
end | |||
def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do | |||
@@ -235,54 +183,17 @@ defmodule Pleroma.Activity do | |||
def get_create_by_object_ap_id(_), do: nil | |||
def create_by_object_ap_id_with_object(ap_ids) when is_list(ap_ids) do | |||
from( | |||
activity in Activity, | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)", | |||
activity.data, | |||
activity.data, | |||
^ap_ids | |||
), | |||
where: fragment("(?)->>'type' = 'Create'", activity.data), | |||
inner_join: o in Object, | |||
on: | |||
fragment( | |||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", | |||
o.data, | |||
activity.data, | |||
activity.data | |||
), | |||
preload: [object: o] | |||
) | |||
end | |||
def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do | |||
from( | |||
activity in Activity, | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||
activity.data, | |||
activity.data, | |||
^to_string(ap_id) | |||
), | |||
where: fragment("(?)->>'type' = 'Create'", activity.data), | |||
inner_join: o in Object, | |||
on: | |||
fragment( | |||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", | |||
o.data, | |||
activity.data, | |||
activity.data | |||
), | |||
preload: [object: o] | |||
) | |||
@doc """ | |||
Accepts `ap_id` or list of `ap_id`. | |||
Returns a query. | |||
""" | |||
@spec create_by_object_ap_id_with_object(String.t() | [String.t()]) :: Ecto.Queryable.t() | |||
def create_by_object_ap_id_with_object(ap_id) do | |||
ap_id | |||
|> create_by_object_ap_id() | |||
|> with_preloaded_object() | |||
end | |||
def create_by_object_ap_id_with_object(_), do: nil | |||
def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do | |||
ap_id | |||
|> create_by_object_ap_id_with_object() | |||
@@ -306,7 +217,8 @@ defmodule Pleroma.Activity do | |||
def normalize(_), do: nil | |||
def delete_by_ap_id(id) when is_binary(id) do | |||
by_object_ap_id(id) | |||
id | |||
|> Queries.by_object_id() | |||
|> select([u], u) | |||
|> Repo.delete_all() | |||
|> elem(1) | |||
@@ -350,31 +262,10 @@ defmodule Pleroma.Activity do | |||
end | |||
def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do | |||
from( | |||
a in Activity, | |||
where: | |||
fragment( | |||
"? ->> 'type' = 'Follow'", | |||
a.data | |||
), | |||
where: | |||
fragment( | |||
"? ->> 'state' = 'pending'", | |||
a.data | |||
), | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||
a.data, | |||
a.data, | |||
^ap_id | |||
) | |||
) | |||
end | |||
@spec query_by_actor(actor()) :: Ecto.Query.t() | |||
def query_by_actor(actor) do | |||
from(a in Activity, where: a.actor == ^actor) | |||
ap_id | |||
|> Queries.by_object_id() | |||
|> Queries.by_type("Follow") | |||
|> where([a], fragment("? ->> 'state' = 'pending'", a.data)) | |||
end | |||
def restrict_deactivated_users(query) do | |||
@@ -13,6 +13,14 @@ defmodule Pleroma.Activity.Queries do | |||
alias Pleroma.Activity | |||
@spec by_ap_id(query, String.t()) :: query | |||
def by_ap_id(query \\ Activity, ap_id) do | |||
from( | |||
activity in query, | |||
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)) | |||
) | |||
end | |||
@spec by_actor(query, String.t()) :: query | |||
def by_actor(query \\ Activity, actor) do | |||
from( | |||
@@ -21,8 +29,23 @@ defmodule Pleroma.Activity.Queries do | |||
) | |||
end | |||
@spec by_object_id(query, String.t()) :: query | |||
def by_object_id(query \\ Activity, object_id) do | |||
@spec by_object_id(query, String.t() | [String.t()]) :: query | |||
def by_object_id(query \\ Activity, object_id) | |||
def by_object_id(query, object_ids) when is_list(object_ids) do | |||
from( | |||
activity in query, | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)", | |||
activity.data, | |||
activity.data, | |||
^object_ids | |||
) | |||
) | |||
end | |||
def by_object_id(query, object_id) when is_binary(object_id) do | |||
from(activity in query, | |||
where: | |||
fragment( | |||
@@ -41,9 +64,4 @@ defmodule Pleroma.Activity.Queries do | |||
where: fragment("(?)->>'type' = ?", activity.data, ^activity_type) | |||
) | |||
end | |||
@spec limit(query, pos_integer()) :: query | |||
def limit(query \\ Activity, limit) do | |||
from(activity in query, limit: ^limit) | |||
end | |||
end |
@@ -1219,7 +1219,7 @@ defmodule Pleroma.User do | |||
def delete_user_activities(%User{ap_id: ap_id} = user) do | |||
ap_id | |||
|> Activity.query_by_actor() | |||
|> Activity.Queries.by_actor() | |||
|> RepoStreamer.chunk_stream(50) | |||
|> Stream.each(fn activities -> | |||
Enum.each(activities, &delete_activity(&1)) | |||
@@ -85,15 +85,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
defp extract_list(_), do: [] | |||
def maybe_splice_recipient(ap_id, params) do | |||
need_splice = | |||
need_splice? = | |||
!recipient_in_collection(ap_id, params["to"]) && | |||
!recipient_in_collection(ap_id, params["cc"]) | |||
cc_list = extract_list(params["cc"]) | |||
if need_splice do | |||
params | |||
|> Map.put("cc", [ap_id | cc_list]) | |||
if need_splice? do | |||
cc_list = extract_list(params["cc"]) | |||
Map.put(params, "cc", [ap_id | cc_list]) | |||
else | |||
params | |||
end | |||
@@ -139,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
"object" => object | |||
} | |||
Notification.get_notified_from_activity(%Activity{data: fake_create_activity}, false) | |||
get_notified_from_object(fake_create_activity) | |||
end | |||
def get_notified_from_object(object) do | |||
@@ -188,9 +186,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
Adds an id and a published data if they aren't there, | |||
also adds it to an included object | |||
""" | |||
def lazy_put_activity_defaults(map, fake \\ false) do | |||
def lazy_put_activity_defaults(map, fake? \\ false) do | |||
map = | |||
unless fake do | |||
if not fake? do | |||
%{data: %{"id" => context}, id: context_id} = create_context(map["context"]) | |||
map | |||
@@ -207,7 +205,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
end | |||
if is_map(map["object"]) do | |||
object = lazy_put_object_defaults(map["object"], map, fake) | |||
object = lazy_put_object_defaults(map["object"], map, fake?) | |||
%{map | "object" => object} | |||
else | |||
map | |||
@@ -217,9 +215,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
@doc """ | |||
Adds an id and published date if they aren't there. | |||
""" | |||
def lazy_put_object_defaults(map, activity \\ %{}, fake) | |||
def lazy_put_object_defaults(map, activity \\ %{}, fake?) | |||
def lazy_put_object_defaults(map, activity, true = _fake) do | |||
def lazy_put_object_defaults(map, activity, true = _fake?) do | |||
map | |||
|> Map.put_new_lazy("published", &make_date/0) | |||
|> Map.put_new("id", "pleroma:fake_object_id") | |||
@@ -228,7 +226,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
|> Map.put_new("context_id", activity["context_id"]) | |||
end | |||
def lazy_put_object_defaults(map, activity, _fake) do | |||
def lazy_put_object_defaults(map, activity, _fake?) do | |||
map | |||
|> Map.put_new_lazy("id", &generate_object_id/0) | |||
|> Map.put_new_lazy("published", &make_date/0) | |||
@@ -242,9 +240,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
def insert_full_object(%{"object" => %{"type" => type} = object_data} = map) | |||
when is_map(object_data) and type in @supported_object_types do | |||
with {:ok, object} <- Object.create(object_data) do | |||
map = | |||
map | |||
|> Map.put("object", object.data["id"]) | |||
map = Map.put(map, "object", object.data["id"]) | |||
{:ok, map, object} | |||
end | |||
@@ -263,7 +259,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
|> Activity.Queries.by_actor() | |||
|> Activity.Queries.by_object_id(id) | |||
|> Activity.Queries.by_type("Like") | |||
|> Activity.Queries.limit(1) | |||
|> limit(1) | |||
|> Repo.one() | |||
end | |||
@@ -380,12 +376,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
%Activity{data: %{"actor" => actor, "object" => object}} = activity, | |||
state | |||
) do | |||
with new_data <- | |||
activity.data | |||
|> Map.put("state", state), | |||
changeset <- Changeset.change(activity, data: new_data), | |||
{:ok, activity} <- Repo.update(changeset), | |||
_ <- User.set_follow_state_cache(actor, object, state) do | |||
new_data = Map.put(activity.data, "state", state) | |||
changeset = Changeset.change(activity, data: new_data) | |||
with {:ok, activity} <- Repo.update(changeset) do | |||
User.set_follow_state_cache(actor, object, state) | |||
{:ok, activity} | |||
end | |||
end | |||
@@ -410,28 +405,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
end | |||
def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do | |||
query = | |||
from( | |||
activity in Activity, | |||
where: | |||
fragment( | |||
"? ->> 'type' = 'Follow'", | |||
activity.data | |||
), | |||
where: activity.actor == ^follower_id, | |||
# this is to use the index | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||
activity.data, | |||
activity.data, | |||
^followed_id | |||
), | |||
order_by: [fragment("? desc nulls last", activity.id)], | |||
limit: 1 | |||
) | |||
Repo.one(query) | |||
"Follow" | |||
|> Activity.Queries.by_type() | |||
|> where(actor: ^follower_id) | |||
# this is to use the index | |||
|> Activity.Queries.by_object_id(followed_id) | |||
|> order_by([activity], fragment("? desc nulls last", activity.id)) | |||
|> limit(1) | |||
|> Repo.one() | |||
end | |||
#### Announce-related helpers | |||
@@ -439,23 +420,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
@doc """ | |||
Retruns an existing announce activity if the notice has already been announced | |||
""" | |||
def get_existing_announce(actor, %{data: %{"id" => id}}) do | |||
query = | |||
from( | |||
activity in Activity, | |||
where: activity.actor == ^actor, | |||
# this is to use the index | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||
activity.data, | |||
activity.data, | |||
^id | |||
), | |||
where: fragment("(?)->>'type' = 'Announce'", activity.data) | |||
) | |||
Repo.one(query) | |||
def get_existing_announce(actor, %{data: %{"id" => ap_id}}) do | |||
"Announce" | |||
|> Activity.Queries.by_type() | |||
|> where(actor: ^actor) | |||
# this is to use the index | |||
|> Activity.Queries.by_object_id(ap_id) | |||
|> Repo.one() | |||
end | |||
@doc """ | |||
@@ -538,11 +509,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
object | |||
) do | |||
announcements = | |||
if is_list(object.data["announcements"]), do: object.data["announcements"], else: [] | |||
if is_list(object.data["announcements"]) do | |||
Enum.uniq([actor | object.data["announcements"]]) | |||
else | |||
[actor] | |||
end | |||
with announcements <- [actor | announcements] |> Enum.uniq() do | |||
update_element_in_object("announcement", announcements, object) | |||
end | |||
update_element_in_object("announcement", announcements, object) | |||
end | |||
def add_announce_to_object(_, object), do: {:ok, object} | |||
@@ -570,28 +543,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
#### Block-related helpers | |||
def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do | |||
query = | |||
from( | |||
activity in Activity, | |||
where: | |||
fragment( | |||
"? ->> 'type' = 'Block'", | |||
activity.data | |||
), | |||
where: activity.actor == ^blocker_id, | |||
# this is to use the index | |||
where: | |||
fragment( | |||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?", | |||
activity.data, | |||
activity.data, | |||
^blocked_id | |||
), | |||
order_by: [fragment("? desc nulls last", activity.id)], | |||
limit: 1 | |||
) | |||
Repo.one(query) | |||
"Block" | |||
|> Activity.Queries.by_type() | |||
|> where(actor: ^blocker_id) | |||
# this is to use the index | |||
|> Activity.Queries.by_object_id(blocked_id) | |||
|> order_by([activity], fragment("? desc nulls last", activity.id)) | |||
|> limit(1) | |||
|> Repo.one() | |||
end | |||
def make_block_data(blocker, blocked, activity_id) do | |||
@@ -695,11 +654,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
#### Report-related helpers | |||
def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do | |||
with new_data <- Map.put(activity.data, "state", state), | |||
changeset <- Changeset.change(activity, data: new_data), | |||
{:ok, activity} <- Repo.update(changeset) do | |||
{:ok, activity} | |||
end | |||
new_data = Map.put(activity.data, "state", state) | |||
activity | |||
|> Changeset.change(data: new_data) | |||
|> Repo.update() | |||
end | |||
def update_report_state(_, _), do: {:error, "Unsupported state"} | |||
@@ -766,21 +725,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
end | |||
def get_existing_votes(actor, %{data: %{"id" => id}}) do | |||
query = | |||
from( | |||
[activity, object: object] in Activity.with_preloaded_object(Activity), | |||
where: fragment("(?)->>'type' = 'Create'", activity.data), | |||
where: fragment("(?)->>'actor' = ?", activity.data, ^actor), | |||
where: | |||
fragment( | |||
"(?)->>'inReplyTo' = ?", | |||
object.data, | |||
^to_string(id) | |||
), | |||
where: fragment("(?)->>'type' = 'Answer'", object.data) | |||
) | |||
Repo.all(query) | |||
actor | |||
|> Activity.Queries.by_actor() | |||
|> Activity.Queries.by_type("Create") | |||
|> Activity.with_preloaded_object() | |||
|> where([a, object: o], fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(id))) | |||
|> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data)) | |||
|> Repo.all() | |||
end | |||
defp maybe_put(map, _key, nil), do: map | |||
@@ -185,4 +185,35 @@ defmodule Pleroma.ActivityTest do | |||
assert [%{id: ^id1, object: %Object{}}, %{id: ^id2, object: %Object{}}] = activities | |||
end | |||
test "get_by_id_with_object/1" do | |||
%{id: id} = insert(:note_activity) | |||
assert %Activity{id: ^id, object: %Object{}} = Activity.get_by_id_with_object(id) | |||
end | |||
test "get_by_ap_id_with_object/1" do | |||
%{data: %{"id" => ap_id}} = insert(:note_activity) | |||
assert %Activity{data: %{"id" => ^ap_id}, object: %Object{}} = | |||
Activity.get_by_ap_id_with_object(ap_id) | |||
end | |||
test "get_by_id/1" do | |||
%{id: id} = insert(:note_activity) | |||
assert %Activity{id: ^id} = Activity.get_by_id(id) | |||
end | |||
test "all_by_actor_and_id/2" do | |||
user = insert(:user) | |||
{:ok, %{id: id1}} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofe"}) | |||
{:ok, %{id: id2}} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofefe"}) | |||
assert [] == Activity.all_by_actor_and_id(user, []) | |||
assert [%Activity{id: ^id2}, %Activity{id: ^id1}] = | |||
Activity.all_by_actor_and_id(user.ap_id, [id1, id2]) | |||
end | |||
end |
@@ -1081,7 +1081,7 @@ defmodule Pleroma.UserTest do | |||
user_activities = | |||
user.ap_id | |||
|> Activity.query_by_actor() | |||
|> Activity.Queries.by_actor() | |||
|> Repo.all() | |||
|> Enum.map(fn act -> act.data["type"] end) | |||