Page MenuHomePhorge

No OneTemporary

Size
53 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index cb11a3f4a..d6f2dd3a3 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -1,461 +1,454 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
import Ecto.Query
import Ecto.Changeset
alias Pleroma.Marker
alias Pleroma.Notification
alias Pleroma.Pagination
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.CommonAPI
@notification_group_sample_limit 8
@spec follow(User.t(), User.t(), map) :: {:ok, User.t()} | {:error, String.t()}
def follow(follower, followed, params \\ %{}) do
result =
if not User.following?(follower, followed) do
CommonAPI.follow(followed, follower)
else
{:ok, followed, follower, nil}
end
with {:ok, _followed, follower, _} <- result do
options = cast_params(params)
set_reblogs_visibility(options[:reblogs], result)
set_subscription(options[:notify], result)
{:ok, follower}
end
end
defp set_reblogs_visibility(false, {:ok, followed, follower, _}) do
CommonAPI.hide_reblogs(followed, follower)
end
defp set_reblogs_visibility(_, {:ok, followed, follower, _}) do
CommonAPI.show_reblogs(followed, follower)
end
defp set_subscription(true, {:ok, followed, follower, _}) do
User.subscribe(follower, followed)
end
defp set_subscription(false, {:ok, followed, follower, _}) do
User.unsubscribe(follower, followed)
end
defp set_subscription(_, _), do: {:ok, nil}
@spec get_followers(User.t(), map()) :: list(User.t())
def get_followers(user, params \\ %{}) do
user
|> User.get_followers_query()
|> Pagination.fetch_paginated(params)
end
def get_friends(user, params \\ %{}) do
user
|> User.get_friends_query()
|> Pagination.fetch_paginated(params)
end
def get_notifications(user, params \\ %{}) do
user
|> notifications_query(params)
|> Pagination.fetch_paginated(params)
end
def get_grouped_notification_page(user, params \\ %{}) do
grouped_types =
params
- |> Map.get("grouped_types", Map.get(params, :grouped_types))
+ |> Map.get("grouped_types")
|> Notification.normalize_grouped_types()
query = notifications_query(user, params)
{order, cursor_filters} = group_pagination(params)
group_rows =
query
|> notification_group_rows(grouped_types, grouped_limit(params), order, cursor_filters)
group_rows = if order == :asc, do: Enum.reverse(group_rows), else: group_rows
page_notifications = representative_notifications(query, group_rows)
notification_groups =
Enum.map(group_rows, &notification_group_sample(user, params, &1.group_key))
{
notification_groups,
page_notifications,
notification_group_counts(group_rows),
notification_group_bounds(group_rows)
}
end
- def get_grouped_notification_groups(user, params \\ %{}) do
- {groups, _notifications, _notification_group_counts, _notification_group_bounds} =
- get_grouped_notification_page(user, params)
-
- groups
- end
-
def get_notification_group_result(user, group_key, params \\ %{}) do
notifications = notification_group_sample(user, params, group_key)
metadata = notification_group_metadata(user, group_key, params)
if Enum.empty?(notifications) or is_nil(metadata) do
{[], %{}, %{}}
else
{
notifications,
%{group_key => metadata.notifications_count},
%{group_key => Map.drop(metadata, [:notifications_count])}
}
end
end
def get_notification_group(user, group_key, params \\ %{})
def get_notification_group(user, "ungrouped-" <> notification_id, _params) do
case Notification.get(user, notification_id) do
{:ok, notification} -> [notification]
_ -> []
end
end
def get_notification_group(user, group_key, params) do
notification_group_sample(user, params, group_key)
end
def get_notification_group_accounts(user, "ungrouped-" <> notification_id) do
with {:ok, notification} <- Notification.get(user, notification_id),
%User{} = actor <- User.get_cached_by_ap_id(notification.activity.data["actor"]) do
[actor]
else
_ -> []
end
end
def get_notification_group_accounts(user, group_key) do
user
|> notification_group_query(group_key, %{})
|> exclude(:preload)
|> distinct(true)
|> select([user_actor: user_actor], user_actor)
|> Repo.all()
end
def dismiss_notification_group(user, "ungrouped-" <> notification_id) do
Notification.destroy_multiple(user, [notification_id])
end
def dismiss_notification_group(%User{id: user_id}, group_key) do
Notification
|> where([n], n.user_id == ^user_id and n.group_key == ^group_key)
|> Repo.delete_all()
end
defp group_pagination(params) do
cond do
- min_id = Map.get(params, "min_id", Map.get(params, :min_id)) ->
+ min_id = Map.get(params, "min_id") ->
cursor_filters = [{:gt, min_id}]
cursor_filters =
- case Map.get(params, "max_id", Map.get(params, :max_id)) do
+ case Map.get(params, "max_id") do
nil -> cursor_filters
max_id -> [{:lt, max_id} | cursor_filters]
end
{:asc, cursor_filters}
- since_id = Map.get(params, "since_id", Map.get(params, :since_id)) ->
+ since_id = Map.get(params, "since_id") ->
{:desc, [{:gt, since_id}]}
- max_id = Map.get(params, "max_id", Map.get(params, :max_id)) ->
+ max_id = Map.get(params, "max_id") ->
{:desc, [{:lt, max_id}]}
true ->
{:desc, []}
end
end
defp grouped_limit(params) do
params
- |> Map.get("limit", Map.get(params, :limit, 40))
+ |> Map.get("limit", 40)
|> parse_limit(40)
|> min(80)
end
def unread_notification_group_count(user, params \\ %{}) do
grouped_types =
params
- |> Map.get("grouped_types", Map.get(params, :grouped_types))
+ |> Map.get("grouped_types")
|> Notification.normalize_grouped_types()
limit = unread_count_limit(params)
user
|> notifications_query(params)
# The grouped API docs define unread by the notifications marker, not by Pleroma's per-row
# seen flag used by the v1 unread count. Keep this marker-based for Mastodon clients.
|> restrict_after_marker(notification_marker_last_read_id(user))
|> notification_group_rows(grouped_types, limit, :desc, [])
|> length()
end
defp notification_group_rows(query, grouped_types, group_limit, :desc, cursor_filters) do
query
|> notification_group_keyed_query(grouped_types)
|> group_by([n], n.group_key)
|> apply_group_cursor_filters(cursor_filters)
|> select([n], %{
group_key: n.group_key,
representative_id: max(n.id),
notifications_count: count(n.id),
page_min_id: min(n.id),
page_max_id: max(n.id),
latest_page_notification_at: max(n.inserted_at)
})
|> order_by([n], desc: max(n.id))
|> limit(^group_limit)
|> Repo.all()
end
defp notification_group_rows(query, grouped_types, group_limit, :asc, cursor_filters) do
query
|> notification_group_keyed_query(grouped_types)
|> group_by([n], n.group_key)
|> apply_group_cursor_filters(cursor_filters)
|> select([n], %{
group_key: n.group_key,
representative_id: max(n.id),
notifications_count: count(n.id),
page_min_id: min(n.id),
page_max_id: max(n.id),
latest_page_notification_at: max(n.inserted_at)
})
|> order_by([n], asc: max(n.id))
|> limit(^group_limit)
|> Repo.all()
end
defp apply_group_cursor_filters(query, []), do: query
defp apply_group_cursor_filters(query, [{:gt, id} | rest]) do
query
|> having([n], max(n.id) > ^id)
|> apply_group_cursor_filters(rest)
end
defp apply_group_cursor_filters(query, [{:lt, id} | rest]) do
query
|> having([n], max(n.id) < ^id)
|> apply_group_cursor_filters(rest)
end
defp notification_group_keyed_query(query, grouped_types) do
query
|> exclude(:preload)
|> select([n], %{
id: n.id,
inserted_at: n.inserted_at,
group_key:
fragment(
"CASE WHEN ? IS NOT NULL AND ?::text = ANY(?) THEN ? ELSE 'ungrouped-' || ?::text END",
n.group_key,
n.type,
type(^grouped_types, {:array, :string}),
n.group_key,
n.id
)
})
|> subquery()
end
defp representative_notifications(_query, []), do: []
defp representative_notifications(query, group_rows) do
representative_ids = Enum.map(group_rows, & &1.representative_id)
notifications_by_id =
query
|> where([n], n.id in ^representative_ids)
|> Repo.all()
|> Map.new(&{to_string(&1.id), &1})
group_rows
|> Enum.map(&Map.get(notifications_by_id, to_string(&1.representative_id)))
|> Enum.filter(& &1)
end
defp notification_group_counts(group_rows) do
Map.new(group_rows, &{&1.group_key, &1.notifications_count})
end
defp notification_group_bounds(group_rows) do
Map.new(group_rows, fn row ->
{row.group_key,
%{
page_min_id: row.page_min_id,
page_max_id: row.page_max_id,
latest_page_notification_at: row.latest_page_notification_at
}}
end)
end
defp notification_group_sample(user, _params, "ungrouped-" <> notification_id) do
case Notification.get(user, notification_id) do
{:ok, notification} -> [notification]
_ -> []
end
end
defp notification_group_sample(user, params, group_key) do
user
|> notification_group_query(group_key, params)
|> order_by([n], desc: n.id)
|> limit(^@notification_group_sample_limit)
|> Repo.all()
end
defp notification_group_metadata(user, "ungrouped-" <> notification_id, _params) do
case Notification.get(user, notification_id) do
{:ok, notification} ->
%{
notifications_count: 1,
page_min_id: notification.id,
page_max_id: notification.id,
latest_page_notification_at: notification.inserted_at
}
_ ->
nil
end
end
defp notification_group_metadata(user, group_key, params) do
user
|> notification_group_query(group_key, params)
|> exclude(:preload)
|> select([n], %{
notifications_count: count(n.id),
page_min_id: min(n.id),
page_max_id: max(n.id),
latest_page_notification_at: max(n.inserted_at)
})
|> Repo.one()
|> case do
%{notifications_count: 0} -> nil
metadata -> metadata
end
end
defp notification_group_query(user, group_key, params) do
user
|> notifications_query(params)
|> where([n], n.group_key == ^group_key)
end
defp notification_marker_last_read_id(user) do
Marker
|> where([m], m.user_id == ^user.id and m.timeline == "notifications")
|> select([m], m.last_read_id)
|> Repo.one()
end
defp restrict_after_marker(query, last_read_id)
when is_binary(last_read_id) and last_read_id != "" do
where(query, [n], n.id > ^last_read_id)
end
defp restrict_after_marker(query, _last_read_id), do: query
defp notifications_query(user, params) do
options = notification_options(user, params)
user
|> Notification.for_user_query(options)
|> restrict(:types, options)
|> restrict(:exclude_types, options)
|> restrict(:account_ap_id, options)
end
defp notification_options(user, params) do
options =
params
|> cast_params()
|> Map.update(:include_types, [], fn include_types -> include_types end)
if ("pleroma:report" not in options.include_types and
User.privileged?(user, :reports_manage_reports)) or
User.privileged?(user, :reports_manage_reports) do
options
else
options
|> Map.update(:exclude_types, ["pleroma:report"], fn current_exclude_types ->
current_exclude_types ++ ["pleroma:report"]
end)
end
end
defp unread_count_limit(params) do
params
- |> Map.get("limit", Map.get(params, :limit, 100))
+ |> Map.get("limit", 100)
|> parse_limit(100)
|> min(1000)
end
defp parse_limit(limit, _default) when is_integer(limit) and limit > 0, do: limit
defp parse_limit(limit, default) when is_binary(limit) do
case Integer.parse(limit) do
{limit, _} when limit > 0 -> limit
_ -> default
end
end
defp parse_limit(_, default), do: default
def get_scheduled_activities(user, params \\ %{}) do
user
|> ScheduledActivity.for_user_query()
|> Pagination.fetch_paginated(params)
end
defp cast_params(params) do
param_types = %{
exclude_types: {:array, :string},
types: {:array, :string},
exclude_visibilities: {:array, :string},
grouped_types: {:array, :string},
limit: :integer,
reblogs: :boolean,
with_muted: :boolean,
account_ap_id: :string,
notify: :boolean
}
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
changeset.changes
end
defp restrict(query, :types, %{types: mastodon_types = [_ | _]}) do
where(query, [n], n.type in ^mastodon_types)
end
defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do
where(query, [n], n.type not in ^mastodon_types)
end
defp restrict(query, :account_ap_id, %{account_ap_id: account_ap_id}) do
where(query, [n, a], a.actor == ^account_ap_id)
end
defp restrict(query, _, _), do: query
end
diff --git a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs
index e9c521d6b..55df41ec6 100644
--- a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs
@@ -1,1021 +1,1032 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
use Pleroma.Web.ConnCase, async: false
alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
setup do
Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Test.StaticConfig)
:ok
end
test "does NOT render account/pleroma/relationship by default" do
%{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_and_validate_schema(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)
{:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/notifications")
expected_response =
"hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{user.ap_id}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
assert [%{"status" => %{"content" => response}} | _rest] =
json_response_and_validate_schema(conn, 200)
assert response == expected_response
end
test "by default, does not contain pleroma:chat_mention" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{:ok, _activity} = CommonAPI.post_chat_message(other_user, user, "hey")
result =
conn
|> get("/api/v1/notifications")
|> json_response_and_validate_schema(200)
assert [] == result
result =
conn
|> get("/api/v1/notifications?include_types[]=pleroma:chat_mention")
|> json_response_and_validate_schema(200)
assert [_] = result
end
test "by default, does not contain pleroma:report" do
clear_config([:instance, :moderator_privileges], [:reports_manage_reports])
user = insert(:user)
other_user = insert(:user)
third_user = insert(:user)
{:ok, user} = user |> User.admin_api_update(%{is_moderator: true})
%{conn: conn} = oauth_access(["read:notifications"], user: user)
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
{:ok, _report} =
CommonAPI.report(third_user, %{account_id: other_user.id, status_ids: [activity.id]})
result =
conn
|> get("/api/v1/notifications")
|> json_response_and_validate_schema(200)
assert [] == result
result =
conn
|> get("/api/v1/notifications?include_types[]=pleroma:report")
|> json_response_and_validate_schema(200)
assert [_] = result
end
test "Pleroma:report is hidden for non-privileged users" do
clear_config([:instance, :moderator_privileges], [:reports_manage_reports])
user = insert(:user)
other_user = insert(:user)
third_user = insert(:user)
{:ok, user} = user |> User.admin_api_update(%{is_moderator: true})
%{conn: conn} = oauth_access(["read:notifications"], user: user)
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
{:ok, _report} =
CommonAPI.report(third_user, %{account_id: other_user.id, status_ids: [activity.id]})
result =
conn
|> get("/api/v1/notifications?include_types[]=pleroma:report")
|> json_response_and_validate_schema(200)
assert [_] = result
clear_config([:instance, :moderator_privileges], [])
result =
conn
|> get("/api/v1/notifications?include_types[]=pleroma:report")
|> json_response_and_validate_schema(200)
assert [] == result
end
test "excludes mentions from blockers when blockers_visible is false" do
clear_config([:activitypub, :blockers_visible], false)
%{user: user, conn: conn} = oauth_access(["read:notifications"])
blocker = insert(:user)
{:ok, _} = CommonAPI.block(user, blocker)
{:ok, activity} = CommonAPI.post(blocker, %{status: "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/notifications")
assert [] == json_response_and_validate_schema(conn, 200)
end
test "getting a single notification" do
%{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)
conn = get(conn, "/api/v1/notifications/#{notification.id}")
expected_response =
"hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{user.ap_id}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
assert %{"status" => %{"content" => response}} = json_response_and_validate_schema(conn, 200)
assert response == expected_response
end
test "dismissing a single notification (deprecated endpoint)" 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)
|> put_req_header("content-type", "application/json")
|> post("/api/v1/notifications/dismiss", %{"id" => to_string(notification.id)})
assert %{} = json_response_and_validate_schema(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_and_validate_schema(conn, 200)
end
test "clearing all notifications" do
%{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"])
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
ret_conn = post(conn, "/api/v1/notifications/clear")
assert %{} = json_response_and_validate_schema(ret_conn, 200)
ret_conn = get(conn, "/api/v1/notifications")
assert all = json_response_and_validate_schema(ret_conn, 200)
assert all == []
end
test "paginates notifications using min_id, since_id, max_id, and limit" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{:ok, activity1} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
{:ok, activity2} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
{:ok, activity3} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
{:ok, activity4} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
notification1_id = get_notification_id_by_activity(activity1)
notification2_id = get_notification_id_by_activity(activity2)
notification3_id = get_notification_id_by_activity(activity3)
notification4_id = get_notification_id_by_activity(activity4)
conn = assign(conn, :user, user)
# min_id
result =
conn
|> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
|> json_response_and_validate_schema(:ok)
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
# since_id
result =
conn
|> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
|> json_response_and_validate_schema(:ok)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
# max_id
result =
conn
|> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
|> json_response_and_validate_schema(:ok)
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
end
describe "exclude_visibilities" do
test "filters notifications for mentions" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{:ok, public_activity} =
CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "public"})
{:ok, direct_activity} =
CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "direct"})
{:ok, unlisted_activity} =
CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "unlisted"})
{:ok, private_activity} =
CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "private"})
query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "private"]})
conn_res = get(conn, "/api/v1/notifications?" <> query)
assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
assert id == direct_activity.id
query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "direct"]})
conn_res = get(conn, "/api/v1/notifications?" <> query)
assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
assert id == private_activity.id
query = params_to_query(%{exclude_visibilities: ["public", "private", "direct"]})
conn_res = get(conn, "/api/v1/notifications?" <> query)
assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
assert id == unlisted_activity.id
query = params_to_query(%{exclude_visibilities: ["unlisted", "private", "direct"]})
conn_res = get(conn, "/api/v1/notifications?" <> query)
assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
assert id == public_activity.id
end
test "filters notifications for Like activities" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["read:notifications"])
{:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(other_user, user)
{:ok, _, _, %{data: %{"state" => "accept"}}} = CommonAPI.follow(user, other_user)
{:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
{:ok, direct_activity} =
CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "direct"})
{:ok, unlisted_activity} =
CommonAPI.post(other_user, %{status: ".", visibility: "unlisted"})
{:ok, private_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "private"})
{:ok, _} = CommonAPI.favorite(public_activity.id, user)
{:ok, _} = CommonAPI.favorite(direct_activity.id, user)
{:ok, _} = CommonAPI.favorite(unlisted_activity.id, user)
{:ok, _} = CommonAPI.favorite(private_activity.id, user)
activity_ids =
conn
|> get("/api/v1/notifications?exclude_visibilities[]=direct")
|> json_response_and_validate_schema(200)
|> Enum.map(& &1["status"]["id"])
assert public_activity.id in activity_ids
assert unlisted_activity.id in activity_ids
assert private_activity.id in activity_ids
refute direct_activity.id in activity_ids
activity_ids =
conn
|> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
|> json_response_and_validate_schema(200)
|> Enum.map(& &1["status"]["id"])
assert public_activity.id in activity_ids
refute unlisted_activity.id in activity_ids
assert private_activity.id in activity_ids
assert direct_activity.id in activity_ids
activity_ids =
conn
|> get("/api/v1/notifications?exclude_visibilities[]=private")
|> json_response_and_validate_schema(200)
|> Enum.map(& &1["status"]["id"])
assert public_activity.id in activity_ids
assert unlisted_activity.id in activity_ids
refute private_activity.id in activity_ids
assert direct_activity.id in activity_ids
activity_ids =
conn
|> get("/api/v1/notifications?exclude_visibilities[]=public")
|> json_response_and_validate_schema(200)
|> Enum.map(& &1["status"]["id"])
refute public_activity.id in activity_ids
assert unlisted_activity.id in activity_ids
assert private_activity.id in activity_ids
assert direct_activity.id in activity_ids
end
test "filters notifications for Announce activities" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["read:notifications"])
{:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
{:ok, unlisted_activity} =
CommonAPI.post(other_user, %{status: ".", visibility: "unlisted"})
{:ok, _} = CommonAPI.repeat(public_activity.id, user)
{:ok, _} = CommonAPI.repeat(unlisted_activity.id, user)
activity_ids =
conn
|> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
|> json_response_and_validate_schema(200)
|> Enum.map(& &1["status"]["id"])
assert public_activity.id in activity_ids
refute unlisted_activity.id in activity_ids
end
test "doesn't return less than the requested amount of records when the user's reply is liked" do
user = insert(:user)
%{user: other_user, conn: conn} = oauth_access(["read:notifications"])
{:ok, mention} =
CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "public"})
{:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
{:ok, reply} =
CommonAPI.post(other_user, %{
status: ".",
visibility: "public",
in_reply_to_status_id: activity.id
})
{:ok, _favorite} = CommonAPI.favorite(reply.id, user)
activity_ids =
conn
|> get("/api/v1/notifications?exclude_visibilities[]=direct&limit=2")
|> json_response_and_validate_schema(200)
|> Enum.map(& &1["status"]["id"])
assert [reply.id, mention.id] == activity_ids
end
end
test "filters notifications using exclude_types" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{: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, reblog_activity} = CommonAPI.repeat(create_activity.id, other_user)
{:ok, _, _, follow_activity} = CommonAPI.follow(user, other_user)
mention_notification_id = get_notification_id_by_activity(mention_activity)
favorite_notification_id = get_notification_id_by_activity(favorite_activity)
reblog_notification_id = get_notification_id_by_activity(reblog_activity)
follow_notification_id = get_notification_id_by_activity(follow_activity)
query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]})
conn_res = get(conn, "/api/v1/notifications?" <> query)
assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
query = params_to_query(%{exclude_types: ["favourite", "reblog", "follow"]})
conn_res = get(conn, "/api/v1/notifications?" <> query)
assert [%{"id" => ^mention_notification_id}] =
json_response_and_validate_schema(conn_res, 200)
query = params_to_query(%{exclude_types: ["reblog", "follow", "mention"]})
conn_res = get(conn, "/api/v1/notifications?" <> query)
assert [%{"id" => ^favorite_notification_id}] =
json_response_and_validate_schema(conn_res, 200)
query = params_to_query(%{exclude_types: ["follow", "mention", "favourite"]})
conn_res = get(conn, "/api/v1/notifications?" <> query)
assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
end
test "filters notifications using types" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{: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, reblog_activity} = CommonAPI.repeat(create_activity.id, other_user)
{:ok, _, _, follow_activity} = CommonAPI.follow(user, other_user)
mention_notification_id = get_notification_id_by_activity(mention_activity)
favorite_notification_id = get_notification_id_by_activity(favorite_activity)
reblog_notification_id = get_notification_id_by_activity(reblog_activity)
follow_notification_id = get_notification_id_by_activity(follow_activity)
conn_res = get(conn, "/api/v1/notifications?types[]=follow")
assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
conn_res = get(conn, "/api/v1/notifications?types[]=mention")
assert [%{"id" => ^mention_notification_id}] =
json_response_and_validate_schema(conn_res, 200)
conn_res = get(conn, "/api/v1/notifications?types[]=favourite")
assert [%{"id" => ^favorite_notification_id}] =
json_response_and_validate_schema(conn_res, 200)
conn_res = get(conn, "/api/v1/notifications?types[]=reblog")
assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
result = conn |> get("/api/v1/notifications") |> json_response_and_validate_schema(200)
assert length(result) == 4
query = params_to_query(%{types: ["follow", "mention", "favourite", "reblog"]})
result =
conn
|> get("/api/v1/notifications?" <> query)
|> json_response_and_validate_schema(200)
assert length(result) == 4
end
test "filtering falls back to include_types" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{:ok, _activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"})
{:ok, create_activity} = CommonAPI.post(user, %{status: "hey"})
{:ok, _activity} = CommonAPI.favorite(create_activity.id, other_user)
{:ok, _activity} = CommonAPI.repeat(create_activity.id, other_user)
{:ok, _, _, follow_activity} = CommonAPI.follow(user, other_user)
follow_notification_id = get_notification_id_by_activity(follow_activity)
conn_res = get(conn, "/api/v1/notifications?include_types[]=follow")
assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
end
test "destroy multiple" do
%{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"])
other_user = insert(:user)
{:ok, activity1} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
{:ok, activity2} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
{:ok, activity3} = CommonAPI.post(user, %{status: "hi @#{other_user.nickname}"})
{:ok, activity4} = CommonAPI.post(user, %{status: "hi @#{other_user.nickname}"})
notification1_id = get_notification_id_by_activity(activity1)
notification2_id = get_notification_id_by_activity(activity2)
notification3_id = get_notification_id_by_activity(activity3)
notification4_id = get_notification_id_by_activity(activity4)
result =
conn
|> get("/api/v1/notifications")
|> json_response_and_validate_schema(:ok)
assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
conn2 =
conn
|> assign(:user, other_user)
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:notifications"]))
result =
conn2
|> get("/api/v1/notifications")
|> json_response_and_validate_schema(:ok)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
query = params_to_query(%{ids: [notification1_id, notification2_id]})
conn_destroy = delete(conn, "/api/v1/notifications/destroy_multiple?" <> query)
assert json_response_and_validate_schema(conn_destroy, 200) == %{}
result =
conn2
|> get("/api/v1/notifications")
|> json_response_and_validate_schema(:ok)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
end
describe "GET /api/v2/notifications" do
test "groups favourite notifications for the same status" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user1 = insert(:user)
other_user2 = insert(:user)
{:ok, status} = CommonAPI.post(user, %{status: "hello"})
{:ok, _} = CommonAPI.favorite(status.id, other_user1)
{:ok, _} = CommonAPI.favorite(status.id, other_user2)
notifications = Repo.all(Notification)
notification_ids = Enum.map(notifications, &to_string(&1.id))
assert [persisted_group_key] = notifications |> Enum.map(& &1.group_key) |> Enum.uniq()
assert is_binary(persisted_group_key)
result =
conn
|> get("/api/v2/notifications")
|> json_response_and_validate_schema(200)
assert [%{"id" => account_id1}, %{"id" => account_id2}] = result["accounts"]
assert account_id1 in [other_user1.id, other_user2.id]
assert account_id2 in [other_user1.id, other_user2.id]
assert [%{"id" => status_id}] = result["statuses"]
assert status_id == status.id
assert [group] = result["notification_groups"]
assert group["type"] == "favourite"
assert group["notifications_count"] == 2
assert group["status_id"] == status.id
assert group["most_recent_notification_id"] in notification_ids
assert group["page_min_id"] in notification_ids
assert group["page_max_id"] in notification_ids
assert group["latest_page_notification_at"]
assert Enum.sort(group["sample_account_ids"]) == Enum.sort([other_user1.id, other_user2.id])
group_key = group["group_key"]
assert group_key == persisted_group_key
assert [%{"group_key" => ^group_key}, %{"group_key" => ^group_key}] =
conn
|> get("/api/v1/notifications")
|> json_response_and_validate_schema(200)
assert %{"notification_groups" => [shown_group]} =
conn
|> get("/api/v2/notifications/#{group_key}")
|> json_response_and_validate_schema(200)
assert shown_group["group_key"] == group_key
assert shown_group["notifications_count"] == 2
refute Map.has_key?(shown_group, "page_min_id")
refute Map.has_key?(shown_group, "page_max_id")
refute Map.has_key?(shown_group, "latest_page_notification_at")
end
test "round-trips ungrouped group keys when grouped_types excludes a type" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{:ok, status} = CommonAPI.post(user, %{status: "hello"})
{:ok, _} = CommonAPI.favorite(status.id, other_user)
%{"notification_groups" => [%{"group_key" => "ungrouped-" <> _ = group_key}]} =
conn
|> get("/api/v2/notifications?grouped_types[]=reblog")
|> json_response_and_validate_schema(200)
assert %{
"notification_groups" => [%{"group_key" => ^group_key, "notifications_count" => 1}]
} =
conn
|> get("/api/v2/notifications/#{group_key}")
|> json_response_and_validate_schema(200)
end
test "keeps legacy notifications without persisted group keys ungrouped" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user1 = insert(:user)
other_user2 = insert(:user)
{:ok, status} = CommonAPI.post(user, %{status: "hello"})
{:ok, favorite1} = CommonAPI.favorite(status.id, other_user1)
{:ok, favorite2} = CommonAPI.favorite(status.id, other_user2)
notification_ids =
[favorite1, favorite2]
|> Enum.map(&get_notification_id_by_activity/1)
Repo.update_all(Notification, set: [group_key: nil])
expected_group_keys = Enum.map(notification_ids, &"ungrouped-#{&1}")
assert [%{"group_key" => group_key1}, %{"group_key" => group_key2}] =
conn
|> get("/api/v1/notifications")
|> json_response_and_validate_schema(200)
assert Enum.sort([group_key1, group_key2]) == Enum.sort(expected_group_keys)
%{"notification_groups" => groups} =
conn
|> get("/api/v2/notifications")
|> json_response_and_validate_schema(200)
assert Enum.sort(Enum.map(groups, & &1["group_key"])) == Enum.sort(expected_group_keys)
assert Enum.all?(groups, &(&1["notifications_count"] == 1))
group_key = List.first(expected_group_keys)
assert %{
"notification_groups" => [%{"group_key" => ^group_key, "notifications_count" => 1}]
} =
conn
|> get("/api/v2/notifications/#{group_key}")
|> json_response_and_validate_schema(200)
end
test "paginates notification groups instead of raw notifications" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user1 = insert(:user)
other_user2 = insert(:user)
other_user3 = insert(:user)
{:ok, older_status} = CommonAPI.post(user, %{status: "older"})
{:ok, _} = CommonAPI.favorite(older_status.id, other_user3)
{:ok, newer_status} = CommonAPI.post(user, %{status: "newer"})
{:ok, _} = CommonAPI.favorite(newer_status.id, other_user1)
{:ok, _} = CommonAPI.favorite(newer_status.id, other_user2)
older_status_id = older_status.id
newer_status_id = newer_status.id
%{"notification_groups" => groups} =
conn
|> get("/api/v2/notifications?limit=2")
|> json_response_and_validate_schema(200)
assert [
%{"status_id" => ^newer_status_id, "notifications_count" => 2},
%{"status_id" => ^older_status_id, "notifications_count" => 1}
] = groups
end
test "uses group-level cursors when paginating notification groups" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user1 = insert(:user)
other_user2 = insert(:user)
other_user3 = insert(:user)
{:ok, older_status} = CommonAPI.post(user, %{status: "older"})
{:ok, _} = CommonAPI.favorite(older_status.id, other_user3)
{:ok, newer_status} = CommonAPI.post(user, %{status: "newer"})
{:ok, _} = CommonAPI.favorite(newer_status.id, other_user1)
{:ok, _} = CommonAPI.favorite(newer_status.id, other_user2)
older_status_id = older_status.id
newer_status_id = newer_status.id
assert %{
"notification_groups" => [
%{
"status_id" => ^newer_status_id,
"notifications_count" => 2,
"most_recent_notification_id" => cursor
}
]
} =
conn
|> get("/api/v2/notifications?limit=1")
|> json_response_and_validate_schema(200)
assert %{
"notification_groups" => [
%{"status_id" => ^older_status_id, "notifications_count" => 1}
]
} =
conn
|> get("/api/v2/notifications?limit=1&max_id=#{cursor}")
|> json_response_and_validate_schema(200)
end
test "returns total notification count for a partially represented group" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user1 = insert(:user)
other_user2 = insert(:user)
other_user3 = insert(:user)
{:ok, grouped_status} = CommonAPI.post(user, %{status: "grouped"})
{:ok, _} = CommonAPI.favorite(grouped_status.id, other_user1)
{:ok, other_status} = CommonAPI.post(user, %{status: "other"})
{:ok, _} = CommonAPI.favorite(other_status.id, other_user3)
{:ok, _} = CommonAPI.favorite(grouped_status.id, other_user2)
grouped_status_id = grouped_status.id
assert %{
"notification_groups" => [
%{"status_id" => ^grouped_status_id, "notifications_count" => 2}
]
} =
conn
|> get("/api/v2/notifications?limit=1")
|> json_response_and_validate_schema(200)
end
test "counts unread notification groups" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user1 = insert(:user)
other_user2 = insert(:user)
mentioner = insert(:user)
{:ok, status} = CommonAPI.post(user, %{status: "hello"})
{:ok, _} = CommonAPI.favorite(status.id, other_user1)
{:ok, _} = CommonAPI.favorite(status.id, other_user2)
{:ok, _} = CommonAPI.post(mentioner, %{status: "hi @#{user.nickname}"})
assert %{"count" => 2} =
conn
|> get("/api/v2/notifications/unread_count")
|> json_response_and_validate_schema(200)
end
test "counts unread notification groups newer than the notification marker" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
mentioner = insert(:user)
{:ok, status} = CommonAPI.post(user, %{status: "older"})
{:ok, favorite_activity} = CommonAPI.favorite(status.id, other_user)
marker_id = get_notification_id_by_activity(favorite_activity)
{:ok, _} = CommonAPI.post(mentioner, %{status: "newer @#{user.nickname}"})
{:ok, _} =
Pleroma.Marker.upsert(user, %{"notifications" => %{"last_read_id" => marker_id}})
assert %{"count" => 1} =
conn
|> get("/api/v2/notifications/unread_count")
|> json_response_and_validate_schema(200)
end
test "does not preserve stale cursor params in grouped notification link headers" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{:ok, activity1} = CommonAPI.post(other_user, %{status: "one @#{user.nickname}"})
{:ok, _activity2} = CommonAPI.post(other_user, %{status: "two @#{user.nickname}"})
{:ok, activity3} = CommonAPI.post(other_user, %{status: "three @#{user.nickname}"})
notification1_id = get_notification_id_by_activity(activity1)
notification3_id = get_notification_id_by_activity(activity3)
conn = get(conn, "/api/v2/notifications?since_id=#{notification1_id}&limit=1")
assert [link_header] = get_resp_header(conn, "link")
assert link_header =~ ~r/max_id=#{notification3_id}/
refute link_header =~ "since_id="
end
- test "lists accounts from and dismisses a notification group" do
- %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"])
- other_users = insert_list(9, :user)
-
- {:ok, status} = CommonAPI.post(user, %{status: "hello"})
+ test "lists accounts from a notification group" do
+ %{user: user, conn: conn} = oauth_access(["read:notifications"])
- Enum.each(other_users, fn other_user ->
- {:ok, _} = CommonAPI.favorite(status.id, other_user)
- end)
-
- %{
- "notification_groups" => [
- %{
- "group_key" => group_key,
- "notifications_count" => 9,
- "sample_account_ids" => sample_account_ids
- }
- ]
- } =
- conn
- |> get("/api/v2/notifications")
- |> json_response_and_validate_schema(200)
+ {other_users, %{"group_key" => group_key, "sample_account_ids" => sample_account_ids}} =
+ create_favourite_notification_group(user, conn)
assert length(sample_account_ids) == 8
- %{conn: read_conn} = oauth_access(["read:notifications"], user: user)
-
account_ids =
- read_conn
+ conn
|> get("/api/v2/notifications/#{group_key}/accounts")
|> json_response_and_validate_schema(200)
|> Enum.map(& &1["id"])
assert Enum.sort(account_ids) == Enum.sort(Enum.map(other_users, & &1.id))
+ end
+
+ test "dismisses a notification group" do
+ %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"])
+
+ {_other_users, %{"group_key" => group_key}} =
+ create_favourite_notification_group(user, conn)
assert %{} =
conn
|> post("/api/v2/notifications/#{group_key}/dismiss")
|> json_response_and_validate_schema(200)
assert [] = Notification.for_user(user)
end
end
test "doesn't see notifications after muting user with notifications" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
user2 = insert(:user)
{:ok, _, _, _} = CommonAPI.follow(user2, user)
{:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
ret_conn = get(conn, "/api/v1/notifications")
assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
{:ok, _user_relationships} = User.mute(user, user2)
conn = get(conn, "/api/v1/notifications")
assert json_response_and_validate_schema(conn, 200) == []
end
test "see notifications after muting user without notifications" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
user2 = insert(:user)
{:ok, _, _, _} = CommonAPI.follow(user2, user)
{:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
ret_conn = get(conn, "/api/v1/notifications")
assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
{:ok, _user_relationships} = User.mute(user, user2, %{notifications: false})
conn = get(conn, "/api/v1/notifications")
assert length(json_response_and_validate_schema(conn, 200)) == 1
end
test "see notifications after muting user with notifications and with_muted parameter" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
user2 = insert(:user)
{:ok, _, _, _} = CommonAPI.follow(user2, user)
{:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
ret_conn = get(conn, "/api/v1/notifications")
assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
{:ok, _user_relationships} = User.mute(user, user2)
conn = get(conn, "/api/v1/notifications?with_muted=true")
assert length(json_response_and_validate_schema(conn, 200)) == 1
end
test "see move notifications" do
old_user = insert(:user)
new_user = insert(:user, also_known_as: [old_user.ap_id])
%{user: follower, conn: conn} = oauth_access(["read:notifications"])
User.follow(follower, old_user)
Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
Pleroma.Tests.ObanHelpers.perform_all()
conn = get(conn, "/api/v1/notifications")
assert length(json_response_and_validate_schema(conn, 200)) == 1
end
describe "link headers" do
test "preserves parameters in link headers" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{:ok, activity1} =
CommonAPI.post(other_user, %{
status: "hi @#{user.nickname}",
visibility: "public"
})
{:ok, activity2} =
CommonAPI.post(other_user, %{
status: "hi @#{user.nickname}",
visibility: "public"
})
notification1 = Repo.get_by(Notification, activity_id: activity1.id)
notification2 = Repo.get_by(Notification, activity_id: activity2.id)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/notifications?limit=5")
assert [link_header] = get_resp_header(conn, "link")
assert link_header =~ ~r/limit=5/
assert link_header =~ ~r/min_id=#{notification2.id}/
assert link_header =~ ~r/max_id=#{notification1.id}/
end
end
describe "from specified user" do
test "account_id" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
%{id: account_id} = other_user1 = insert(:user)
other_user2 = insert(:user)
{:ok, _activity} = CommonAPI.post(other_user1, %{status: "hi @#{user.nickname}"})
{:ok, _activity} = CommonAPI.post(other_user2, %{status: "bye @#{user.nickname}"})
assert [%{"account" => %{"id" => ^account_id}}] =
conn
|> assign(:user, user)
|> get("/api/v1/notifications?account_id=#{account_id}")
|> json_response_and_validate_schema(200)
assert %{"error" => "Account is not found"} =
conn
|> assign(:user, user)
|> get("/api/v1/notifications?account_id=cofe")
|> json_response_and_validate_schema(404)
end
end
defp get_notification_id_by_activity(%{id: id}) do
Notification
|> Repo.get_by(activity_id: id)
|> Map.get(:id)
|> to_string()
end
+ defp create_favourite_notification_group(user, conn) do
+ other_users = insert_list(9, :user)
+
+ {:ok, status} = CommonAPI.post(user, %{status: "hello"})
+
+ Enum.each(other_users, fn other_user ->
+ {:ok, _} = CommonAPI.favorite(status.id, other_user)
+ end)
+
+ %{
+ "notification_groups" => [
+ %{
+ "notifications_count" => 9
+ } = group
+ ]
+ } =
+ conn
+ |> get("/api/v2/notifications")
+ |> json_response_and_validate_schema(200)
+
+ {other_users, group}
+ end
+
defp params_to_query(%{} = params) do
Enum.map_join(params, "&", fn
{k, v} when is_list(v) -> Enum.map_join(v, "&", &"#{k}[]=#{&1}")
{k, v} -> k <> "=" <> v
end)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Aug 8, 8:13 AM (16 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1722997
Default Alt Text
(53 KB)

Event Timeline