aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--docs/api/admin_api.md289
-rw-r--r--lib/mix/tasks/pleroma/database.ex28
-rw-r--r--lib/mix/tasks/pleroma/user.ex19
-rw-r--r--lib/pleroma/activity.ex21
-rw-r--r--lib/pleroma/bbs/handler.ex1
-rw-r--r--lib/pleroma/conversation.ex21
-rw-r--r--lib/pleroma/conversation/participation.ex10
-rw-r--r--lib/pleroma/emails/admin_email.ex2
-rw-r--r--lib/pleroma/filter.ex3
-rw-r--r--lib/pleroma/user.ex27
-rw-r--r--lib/pleroma/user/info.ex2
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex43
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex78
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex35
-rw-r--r--lib/pleroma/web/admin_api/admin_api_controller.ex81
-rw-r--r--lib/pleroma/web/admin_api/views/report_view.ex41
-rw-r--r--lib/pleroma/web/common_api/common_api.ex57
-rw-r--r--lib/pleroma/web/common_api/utils.ex10
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex1
-rw-r--r--lib/pleroma/web/router.ex8
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api_controller.ex4
-rw-r--r--priv/repo/migrations/20190511191044_set_default_state_to_reports.exs19
-rw-r--r--priv/repo/migrations/20190515222404_add_thread_visibility_function.exs73
-rw-r--r--test/conversation_test.exs20
-rw-r--r--test/support/factory.ex5
-rw-r--r--test/tasks/database_test.exs49
-rw-r--r--test/tasks/user_test.exs28
-rw-r--r--test/user_test.exs51
-rw-r--r--test/web/activity_pub/activity_pub_test.exs35
-rw-r--r--test/web/activity_pub/visibilty_test.exs10
-rw-r--r--test/web/admin_api/admin_api_controller_test.exs327
-rw-r--r--test/web/common_api/common_api_test.exs33
-rw-r--r--test/web/mastodon_api/mastodon_api_controller_test.exs4
34 files changed, 1356 insertions, 84 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea1f29304..12c439135 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc.
- [Prometheus](https://prometheus.io/) metrics
- Support for Mastodon's remote interaction
+- Mix Tasks: `mix pleroma.database bump_all_conversations`
- Mix Tasks: `mix pleroma.database remove_embedded_objects`
+- Mix Tasks: `mix pleroma.database update_users_following_followers_counts`
+- Mix Tasks: `mix pleroma.user toggle_confirmed`
- Federation: Support for reports
- Configuration: `safe_dm_mentions` option
- Configuration: `link_name` option
@@ -23,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Admin API: Endpoints for listing/revoking invite tokens
- Admin API: Endpoints for making users follow/unfollow each other
- Admin API: added filters (role, tags, email, name) for users endpoint
+- Admin API: Endpoints for managing reports
+- Admin API: Endpoints for deleting and changing the scope of individual reported statuses
- AdminFE: initial release with basic user management accessible at /pleroma/admin/
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md
index 59578f8d1..b45c5e285 100644
--- a/docs/api/admin_api.md
+++ b/docs/api/admin_api.md
@@ -24,7 +24,7 @@ Authentication is required and the user must be an admin.
- Example: `https://mypleroma.org/api/pleroma/admin/users?query=john&filters=local,active&page=1&page_size=10&tags[]=some_tag&tags[]=another_tag&name=display_name&email=email@example.com`
- Response:
-```JSON
+```json
{
"page_size": integer,
"count": integer,
@@ -92,7 +92,7 @@ Authentication is required and the user must be an admin.
- `nickname`
- Response: User’s object
-```JSON
+```json
{
"deactivated": bool,
"id": integer,
@@ -124,7 +124,7 @@ Authentication is required and the user must be an admin.
- Params: none
- Response:
-```JSON
+```json
{
"is_moderator": bool,
"is_admin": bool
@@ -141,7 +141,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- Params: none
- Response:
-```JSON
+```json
{
"is_moderator": bool,
"is_admin": bool
@@ -223,7 +223,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- Params: none
- Response:
-```JSON
+```json
{
"invites": [
@@ -250,7 +250,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- `token`
- Response:
-```JSON
+```json
{
"id": integer,
"token": string,
@@ -280,3 +280,280 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- Methods: `GET`
- Params: none
- Response: password reset token (base64 string)
+
+## `/api/pleroma/admin/reports`
+### Get a list of reports
+- Method `GET`
+- Params:
+ - `state`: optional, the state of reports. Valid values are `open`, `closed` and `resolved`
+ - `limit`: optional, the number of records to retrieve
+ - `since_id`: optional, returns results that are more recent than the specified id
+ - `max_id`: optional, returns results that are older than the specified id
+- Response:
+ - On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
+ - On success: JSON, returns a list of reports, where:
+ - `account`: the user who has been reported
+ - `actor`: the user who has sent the report
+ - `statuses`: list of statuses that have been included to the report
+
+```json
+{
+ "reports": [
+ {
+ "account": {
+ "acct": "user",
+ "avatar": "https://pleroma.example.org/images/avi.png",
+ "avatar_static": "https://pleroma.example.org/images/avi.png",
+ "bot": false,
+ "created_at": "2019-04-23T17:32:04.000Z",
+ "display_name": "User",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 1,
+ "following_count": 1,
+ "header": "https://pleroma.example.org/images/banner.png",
+ "header_static": "https://pleroma.example.org/images/banner.png",
+ "id": "9i6dAJqSGSKMzLG2Lo",
+ "locked": false,
+ "note": "",
+ "pleroma": {
+ "confirmation_pending": false,
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_follows": false,
+ "is_admin": false,
+ "is_moderator": false,
+ "relationship": {},
+ "tags": []
+ },
+ "source": {
+ "note": "",
+ "pleroma": {},
+ "sensitive": false
+ },
+ "statuses_count": 3,
+ "url": "https://pleroma.example.org/users/user",
+ "username": "user"
+ },
+ "actor": {
+ "acct": "lain",
+ "avatar": "https://pleroma.example.org/images/avi.png",
+ "avatar_static": "https://pleroma.example.org/images/avi.png",
+ "bot": false,
+ "created_at": "2019-03-28T17:36:03.000Z",
+ "display_name": "Roger Braun",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 1,
+ "following_count": 1,
+ "header": "https://pleroma.example.org/images/banner.png",
+ "header_static": "https://pleroma.example.org/images/banner.png",
+ "id": "9hEkA5JsvAdlSrocam",
+ "locked": false,
+ "note": "",
+ "pleroma": {
+ "confirmation_pending": false,
+ "hide_favorites": false,
+ "hide_followers": false,
+ "hide_follows": false,
+ "is_admin": false,
+ "is_moderator": false,
+ "relationship": {},
+ "tags": []
+ },
+ "source": {
+ "note": "",
+ "pleroma": {},
+ "sensitive": false
+ },
+ "statuses_count": 1,
+ "url": "https://pleroma.example.org/users/lain",
+ "username": "lain"
+ },
+ "content": "Please delete it",
+ "created_at": "2019-04-29T19:48:15.000Z",
+ "id": "9iJGOv1j8hxuw19bcm",
+ "state": "open",
+ "statuses": [
+ {
+ "account": { ... },
+ "application": {
+ "name": "Web",
+ "website": null
+ },
+ "bookmarked": false,
+ "card": null,
+ "content": "<span class=\"h-card\"><a data-user=\"9hEkA5JsvAdlSrocam\" class=\"u-url mention\" href=\"https://pleroma.example.org/users/lain\">@<span>lain</span></a></span> click on my link <a href=\"https://www.google.com/\">https://www.google.com/</a>",
+ "created_at": "2019-04-23T19:15:47.000Z",
+ "emojis": [],
+ "favourited": false,
+ "favourites_count": 0,
+ "id": "9i6mQ9uVrrOmOime8m",
+ "in_reply_to_account_id": null,
+ "in_reply_to_id": null,
+ "language": null,
+ "media_attachments": [],
+ "mentions": [
+ {
+ "acct": "lain",
+ "id": "9hEkA5JsvAdlSrocam",
+ "url": "https://pleroma.example.org/users/lain",
+ "username": "lain"
+ },
+ {
+ "acct": "user",
+ "id": "9i6dAJqSGSKMzLG2Lo",
+ "url": "https://pleroma.example.org/users/user",
+ "username": "user"
+ }
+ ],
+ "muted": false,
+ "pinned": false,
+ "pleroma": {
+ "content": {
+ "text/plain": "@lain click on my link https://www.google.com/"
+ },
+ "conversation_id": 28,
+ "in_reply_to_account_acct": null,
+ "local": true,
+ "spoiler_text": {
+ "text/plain": ""
+ }
+ },
+ "reblog": null,
+ "reblogged": false,
+ "reblogs_count": 0,
+ "replies_count": 0,
+ "sensitive": false,
+ "spoiler_text": "",
+ "tags": [],
+ "uri": "https://pleroma.example.org/objects/8717b90f-8e09-4b58-97b0-e3305472b396",
+ "url": "https://pleroma.example.org/notice/9i6mQ9uVrrOmOime8m",
+ "visibility": "direct"
+ }
+ ]
+ }
+ ]
+}
+```
+
+## `/api/pleroma/admin/reports/:id`
+### Get an individual report
+- Method `GET`
+- Params:
+ - `id`
+- Response:
+ - On failure:
+ - 403 Forbidden `{"error": "error_msg"}`
+ - 404 Not Found `"Not found"`
+ - On success: JSON, Report object (see above)
+
+## `/api/pleroma/admin/reports/:id`
+### Change the state of the report
+- Method `PUT`
+- Params:
+ - `id`
+ - `state`: required, the new state. Valid values are `open`, `closed` and `resolved`
+- Response:
+ - On failure:
+ - 400 Bad Request `"Unsupported state"`
+ - 403 Forbidden `{"error": "error_msg"}`
+ - 404 Not Found `"Not found"`
+ - On success: JSON, Report object (see above)
+
+## `/api/pleroma/admin/reports/:id/respond`
+### Respond to a report
+- Method `POST`
+- Params:
+ - `id`
+ - `status`: required, the message
+- Response:
+ - On failure:
+ - 400 Bad Request `"Invalid parameters"` when `status` is missing
+ - 403 Forbidden `{"error": "error_msg"}`
+ - 404 Not Found `"Not found"`
+ - On success: JSON, created Mastodon Status entity
+
+```json
+{
+ "account": { ... },
+ "application": {
+ "name": "Web",
+ "website": null
+ },
+ "bookmarked": false,
+ "card": null,
+ "content": "Your claim is going to be closed",
+ "created_at": "2019-05-11T17:13:03.000Z",
+ "emojis": [],
+ "favourited": false,
+ "favourites_count": 0,
+ "id": "9ihuiSL1405I65TmEq",
+ "in_reply_to_account_id": null,
+ "in_reply_to_id": null,
+ "language": null,
+ "media_attachments": [],
+ "mentions": [
+ {
+ "acct": "user",
+ "id": "9i6dAJqSGSKMzLG2Lo",
+ "url": "https://pleroma.example.org/users/user",
+ "username": "user"
+ },
+ {
+ "acct": "admin",
+ "id": "9hEkA5JsvAdlSrocam",
+ "url": "https://pleroma.example.org/users/admin",
+ "username": "admin"
+ }
+ ],
+ "muted": false,
+ "pinned": false,
+ "pleroma": {
+ "content": {
+ "text/plain": "Your claim is going to be closed"
+ },
+ "conversation_id": 35,
+ "in_reply_to_account_acct": null,
+ "local": true,
+ "spoiler_text": {
+ "text/plain": ""
+ }
+ },
+ "reblog": null,
+ "reblogged": false,
+ "reblogs_count": 0,
+ "replies_count": 0,
+ "sensitive": false,
+ "spoiler_text": "",
+ "tags": [],
+ "uri": "https://pleroma.example.org/objects/cab0836d-9814-46cd-a0ea-529da9db5fcb",
+ "url": "https://pleroma.example.org/notice/9ihuiSL1405I65TmEq",
+ "visibility": "direct"
+}
+```
+
+## `/api/pleroma/admin/statuses/:id`
+### Change the scope of an individual reported status
+- Method `PUT`
+- Params:
+ - `id`
+ - `sensitive`: optional, valid values are `true` or `false`
+ - `visibility`: optional, valid values are `public`, `private` and `unlisted`
+- Response:
+ - On failure:
+ - 400 Bad Request `"Unsupported visibility"`
+ - 403 Forbidden `{"error": "error_msg"}`
+ - 404 Not Found `"Not found"`
+ - On success: JSON, Mastodon Status entity
+
+## `/api/pleroma/admin/statuses/:id`
+### Delete an individual reported status
+- Method `DELETE`
+- Params:
+ - `id`
+- Response:
+ - On failure:
+ - 403 Forbidden `{"error": "error_msg"}`
+ - 404 Not Found `"Not found"`
+ - On success: 200 OK `{}`
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index ab9a3a7ff..f650b447d 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -4,6 +4,9 @@
defmodule Mix.Tasks.Pleroma.Database do
alias Mix.Tasks.Pleroma.Common
+ alias Pleroma.Conversation
+ alias Pleroma.Repo
+ alias Pleroma.User
require Logger
use Mix.Task
@@ -19,6 +22,14 @@ defmodule Mix.Tasks.Pleroma.Database do
Options:
- `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
+
+ ## Create a conversation for all existing DMs. Can be safely re-run.
+
+ mix pleroma.database bump_all_conversations
+
+ ## Remove duplicated items from following and update followers count for all users
+
+ mix pleroma.database update_users_following_followers_counts
"""
def run(["remove_embedded_objects" | args]) do
{options, [], []} =
@@ -32,7 +43,7 @@ defmodule Mix.Tasks.Pleroma.Database do
Common.start_pleroma()
Logger.info("Removing embedded objects")
- Pleroma.Repo.query!(
+ Repo.query!(
"update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
[],
timeout: :infinity
@@ -41,11 +52,24 @@ defmodule Mix.Tasks.Pleroma.Database do
if Keyword.get(options, :vacuum) do
Logger.info("Runnning VACUUM FULL")
- Pleroma.Repo.query!(
+ Repo.query!(
"vacuum full;",
[],
timeout: :infinity
)
end
end
+
+ def run(["bump_all_conversations"]) do
+ Common.start_pleroma()
+ Conversation.bump_for_all_activities()
+ end
+
+ def run(["update_users_following_followers_counts"]) do
+ Common.start_pleroma()
+
+ users = Repo.all(User)
+ Enum.each(users, &User.remove_duplicated_following/1)
+ Enum.each(users, &User.update_follower_count/1)
+ end
end
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index d130ff8c9..25fc40ea7 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -77,6 +77,10 @@ defmodule Mix.Tasks.Pleroma.User do
## Delete tags from a user.
mix pleroma.user untag NICKNAME TAGS
+
+ ## Toggle confirmation of the user's account.
+
+ mix pleroma.user toggle_confirmed NICKNAME
"""
def run(["new", nickname, email | rest]) do
{options, [], []} =
@@ -388,6 +392,21 @@ defmodule Mix.Tasks.Pleroma.User do
end
end
+ def run(["toggle_confirmed", nickname]) do
+ Common.start_pleroma()
+
+ with %User{} = user <- User.get_cached_by_nickname(nickname) do
+ {:ok, user} = User.toggle_confirmation(user)
+
+ message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
+
+ Mix.shell().info("#{nickname} #{message} confirmation.")
+ else
+ _ ->
+ Mix.shell().error("No local user #{nickname}")
+ end
+ end
+
defp set_moderator(user, value) do
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 4a0919478..4e54b15ba 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -60,21 +60,24 @@ defmodule Pleroma.Activity do
timestamps()
end
- def with_preloaded_object(query) do
- query
- |> join(
- :inner,
- [activity],
- o in Object,
+ def with_joined_object(query) do
+ join(query, :inner, [activity], o in Object,
on:
fragment(
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
o.data,
activity.data,
activity.data
- )
+ ),
+ as: :object
)
- |> preload([activity, object], object: object)
+ end
+
+ def with_preloaded_object(query) do
+ query
+ |> has_named_binding?(:object)
+ |> if(do: query, else: with_joined_object(query))
+ |> preload([activity, object: object], object: object)
end
def with_preloaded_bookmark(query, %User{} = user) do
@@ -108,7 +111,7 @@ defmodule Pleroma.Activity do
def change(struct, params \\ %{}) do
struct
- |> cast(params, [:data])
+ |> cast(params, [:data, :recipients])
|> validate_required([:data])
|> unique_constraint(:ap_id, name: :activities_unique_apid_index)
end
diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex
index 106fe5d18..f34be961f 100644
--- a/lib/pleroma/bbs/handler.ex
+++ b/lib/pleroma/bbs/handler.ex
@@ -95,7 +95,6 @@ defmodule Pleroma.BBS.Handler do
activities =
[user.ap_id | user.following]
|> ActivityPub.fetch_activities(params)
- |> ActivityPub.contain_timeline(user)
Enum.each(activities, fn activity ->
puts_activity(activity)
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
index 0db195988..238c1acf2 100644
--- a/lib/pleroma/conversation.ex
+++ b/lib/pleroma/conversation.ex
@@ -45,7 +45,7 @@ defmodule Pleroma.Conversation do
2. Create a participation for all the people involved who don't have one already
3. Bump all relevant participations to 'unread'
"""
- def create_or_bump_for(activity) do
+ def create_or_bump_for(activity, opts \\ []) do
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
"Create" <- activity.data["type"],
object <- Pleroma.Object.normalize(activity),
@@ -58,7 +58,7 @@ defmodule Pleroma.Conversation do
participations =
Enum.map(users, fn user ->
{:ok, participation} =
- Participation.create_for_user_and_conversation(user, conversation)
+ Participation.create_for_user_and_conversation(user, conversation, opts)
participation
end)
@@ -72,4 +72,21 @@ defmodule Pleroma.Conversation do
e -> {:error, e}
end
end
+
+ @doc """
+ This is only meant to be run by a mix task. It creates conversations/participations for all direct messages in the database.
+ """
+ def bump_for_all_activities do
+ stream =
+ Pleroma.Web.ActivityPub.ActivityPub.fetch_direct_messages_query()
+ |> Repo.stream()
+
+ Repo.transaction(
+ fn ->
+ stream
+ |> Enum.each(fn a -> create_or_bump_for(a, read: true) end)
+ end,
+ timeout: :infinity
+ )
+ end
end
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index 61021fb18..2a11f9069 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -22,15 +22,17 @@ defmodule Pleroma.Conversation.Participation do
def creation_cng(struct, params) do
struct
- |> cast(params, [:user_id, :conversation_id])
+ |> cast(params, [:user_id, :conversation_id, :read])
|> validate_required([:user_id, :conversation_id])
end
- def create_for_user_and_conversation(user, conversation) do
+ def create_for_user_and_conversation(user, conversation, opts \\ []) do
+ read = !!opts[:read]
+
%__MODULE__{}
- |> creation_cng(%{user_id: user.id, conversation_id: conversation.id})
+ |> creation_cng(%{user_id: user.id, conversation_id: conversation.id, read: read})
|> Repo.insert(
- on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]],
+ on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],
returning: true,
conflict_target: [:user_id, :conversation_id]
)
diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
index df0f72f96..d0e254362 100644
--- a/lib/pleroma/emails/admin_email.ex
+++ b/lib/pleroma/emails/admin_email.ex
@@ -29,7 +29,7 @@ defmodule Pleroma.Emails.AdminEmail do
end
statuses_html =
- if length(statuses) > 0 do
+ if is_list(statuses) && length(statuses) > 0 do
statuses_list_html =
statuses
|> Enum.map(fn
diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex
index 79efc29f0..90457dadf 100644
--- a/lib/pleroma/filter.ex
+++ b/lib/pleroma/filter.ex
@@ -38,7 +38,8 @@ defmodule Pleroma.Filter do
query =
from(
f in Pleroma.Filter,
- where: f.user_id == ^user_id
+ where: f.user_id == ^user_id,
+ order_by: [desc: :id]
)
Repo.all(query)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index c6a562a61..9ffb61300 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -166,7 +166,7 @@ defmodule Pleroma.User do
def update_changeset(struct, params \\ %{}) do
struct
- |> cast(params, [:bio, :name, :avatar])
+ |> cast(params, [:bio, :name, :avatar, :following])
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: 5000)
@@ -709,6 +709,18 @@ defmodule Pleroma.User do
end
end
+ def remove_duplicated_following(%User{following: following} = user) do
+ uniq_following = Enum.uniq(following)
+
+ if length(following) == length(uniq_following) do
+ {:ok, user}
+ else
+ user
+ |> update_changeset(%{following: uniq_following})
+ |> update_and_set_cache()
+ end
+ end
+
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
def get_users_from_set(ap_ids, local_only \\ true) do
criteria = %{ap_id: ap_ids, deactivated: false}
@@ -1378,4 +1390,17 @@ defmodule Pleroma.User do
def showing_reblogs?(%User{} = user, %User{} = target) do
target.ap_id not in user.info.muted_reblogs
end
+
+ @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
+ def toggle_confirmation(%User{} = user) do
+ need_confirmation? = !user.info.confirmation_pending
+
+ info_changeset =
+ User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
+
+ user
+ |> change()
+ |> put_embed(:info, info_changeset)
+ |> update_and_set_cache()
+ end
end
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 5a50ee639..5f0cefc00 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -212,7 +212,7 @@ defmodule Pleroma.User.Info do
])
end
- @spec confirmation_changeset(Info.t(), keyword()) :: Ecto.Changerset.t()
+ @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
def confirmation_changeset(info, opts) do
need_confirmation? = Keyword.get(opts, :need_confirmation)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 233fee4fa..5c3156978 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -540,8 +540,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
)
)
- Ecto.Adapters.SQL.to_sql(:all, Repo, query)
-
query
else
Logger.error("Could not restrict visibility to #{visibility}")
@@ -557,8 +555,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
)
- Ecto.Adapters.SQL.to_sql(:all, Repo, query)
-
query
end
@@ -569,6 +565,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_visibility(query, _visibility), do: query
+ defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do
+ query =
+ from(
+ a in query,
+ where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
+ )
+
+ query
+ end
+
+ defp restrict_thread_visibility(query, _), do: query
+
def fetch_user_activities(user, reading_user, params \\ %{}) do
params =
params
@@ -695,6 +703,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_type(query, _), do: query
+ defp restrict_state(query, %{"state" => state}) do
+ from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state))
+ end
+
+ defp restrict_state(query, _), do: query
+
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
from(
activity in query,
@@ -750,8 +764,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
blocks = info.blocks || []
domain_blocks = info.domain_blocks || []
+ query =
+ if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
+
from(
- activity in query,
+ [activity, object: o] in query,
where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
where: fragment("not (? && ?)", activity.recipients, ^blocks),
where:
@@ -761,7 +778,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
activity.data,
^blocks
),
- where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
+ where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
+ where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
)
end
@@ -843,11 +861,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_local(opts)
|> restrict_actor(opts)
|> restrict_type(opts)
+ |> restrict_state(opts)
|> restrict_favorited_by(opts)
|> restrict_blocked(opts)
|> restrict_muted(opts)
|> restrict_media(opts)
|> restrict_visibility(opts)
+ |> restrict_thread_visibility(opts)
|> restrict_replies(opts)
|> restrict_reblogs(opts)
|> restrict_pinned(opts)
@@ -966,11 +986,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
contain_broken_threads(activity, user)
end
- # do post-processing on a timeline
- def contain_timeline(timeline, user) do
- timeline
- |> Enum.filter(fn activity ->
- contain_activity(activity, user)
- end)
+ def fetch_direct_messages_query do
+ Activity
+ |> restrict_type(%{"type" => "Create"})
+ |> restrict_visibility(%{visibility: "direct"})
+ |> order_by([activity], asc: activity.id)
end
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 236d1b4ac..ca8a0844b 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -20,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
require Logger
@supported_object_types ["Article", "Note", "Video", "Page"]
+ @supported_report_states ~w(open closed resolved)
+ @valid_visibilities ~w(public unlisted private direct)
# Some implementations send the actor URI as the actor field, others send the entire actor object,
# so figure out what the actor's URI is based on what we have.
@@ -670,7 +672,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"actor" => params.actor.ap_id,
"content" => params.content,
"object" => object,
- "context" => params.context
+ "context" => params.context,
+ "state" => "open"
}
|> Map.merge(additional)
end
@@ -713,4 +716,77 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
end
end
+
+ #### 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
+ end
+
+ def update_report_state(_, _), do: {:error, "Unsupported state"}
+
+ def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
+ [to, cc, recipients] =
+ activity
+ |> get_updated_targets(visibility)
+ |> Enum.map(&Enum.uniq/1)
+
+ object_data =
+ activity.object.data
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+
+ {:ok, object} =
+ activity.object
+ |> Object.change(%{data: object_data})
+ |> Object.update_and_set_cache()
+
+ activity_data =
+ activity.data
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+
+ activity
+ |> Map.put(:object, object)
+ |> Activity.change(%{data: activity_data, recipients: recipients})
+ |> Repo.update()
+ end
+
+ def update_activity_visibility(_, _), do: {:error, "Unsupported visibility"}
+
+ defp get_updated_targets(
+ %Activity{data: %{"to" => to} = data, recipients: recipients},
+ visibility
+ ) do
+ cc = Map.get(data, "cc", [])
+ follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
+ public = "https://www.w3.org/ns/activitystreams#Public"
+
+ case visibility do
+ "public" ->
+ to = [public | List.delete(to, follower_address)]
+ cc = [follower_address | List.delete(cc, public)]
+ recipients = [public | recipients]
+ [to, cc, recipients]
+
+ "private" ->
+ to = [follower_address | List.delete(to, public)]
+ cc = List.delete(cc, public)
+ recipients = List.delete(recipients, public)
+ [to, cc, recipients]
+
+ "unlisted" ->
+ to = [follower_address | List.delete(to, public)]
+ cc = [public | List.delete(cc, follower_address)]
+ recipients = recipients ++ [follower_address, public]
+ [to, cc, recipients]
+
+ _ ->
+ [to, cc, recipients]
+ end
+ end
end
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index b38ee0442..93b50ee47 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -1,6 +1,7 @@
defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Activity
alias Pleroma.Object
+ alias Pleroma.Repo
alias Pleroma.User
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
@@ -13,11 +14,12 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
end
def is_private?(activity) do
- unless is_public?(activity) do
- follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
- Enum.any?(activity.data["to"], &(&1 == follower_address))
+ with false <- is_public?(activity),
+ %User{follower_address: follower_address} <-
+ User.get_cached_by_ap_id(activity.data["actor"]) do
+ follower_address in activity.data["to"]
else
- false
+ _ -> false
end
end
@@ -38,25 +40,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
end
- # guard
- def entire_thread_visible_for_user?(nil, _user), do: false
+ def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
+ {:ok, %{rows: [[result]]}} =
+ Ecto.Adapters.SQL.query(Repo, "SELECT thread_visibility($1, $2)", [
+ user.ap_id,
+ activity.data["id"]
+ ])
- # XXX: Probably even more inefficient than the previous implementation intended to be a placeholder untill https://git.pleroma.social/pleroma/pleroma/merge_requests/971 is in develop
- # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
-
- def entire_thread_visible_for_user?(
- %Activity{} = tail,
- # %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
- user
- ) do
- case Object.normalize(tail) do
- %{data: %{"inReplyTo" => parent_id}} when is_binary(parent_id) ->
- parent = Activity.get_in_reply_to_activity(tail)
- visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
-
- _ ->
- visible_for_user?(tail, user)
- end
+ result
end
def get_visibility(object) do
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index e00b33aba..de2a13c01 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -4,11 +4,16 @@
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
use Pleroma.Web, :controller
+ alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.UserInviteToken
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.AdminAPI.AccountView
+ alias Pleroma.Web.AdminAPI.ReportView
alias Pleroma.Web.AdminAPI.Search
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.StatusView
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
@@ -287,12 +292,88 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> json(token.token)
end
+ def list_reports(conn, params) do
+ params =
+ params
+ |> Map.put("type", "Flag")
+ |> Map.put("skip_preload", true)
+
+ reports =
+ []
+ |> ActivityPub.fetch_activities(params)
+ |> Enum.reverse()
+
+ conn
+ |> put_view(ReportView)
+ |> render("index.json", %{reports: reports})
+ end
+
+ def report_show(conn, %{"id" => id}) do
+ with %Activity{} = report <- Activity.get_by_id(id) do
+ conn
+ |> put_view(ReportView)
+ |> render("show.json", %{report: report})
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
+ def report_update_state(conn, %{"id" => id, "state" => state}) do
+ with {:ok, report} <- CommonAPI.update_report_state(id, state) do
+ conn
+ |> put_view(ReportView)
+ |> render("show.json", %{report: report})
+ end
+ end
+
+ def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
+ with false <- is_nil(params["status"]),
+ %Activity{} <- Activity.get_by_id(id) do
+ params =
+ params
+ |> Map.put("in_reply_to_status_id", id)
+ |> Map.put("visibility", "direct")
+
+ {:ok, activity} = CommonAPI.post(user, params)
+
+ conn
+ |> put_view(StatusView)
+ |> render("status.json", %{activity: activity})
+ else
+ true ->
+ {:param_cast, nil}
+
+ nil ->
+ {:error, :not_found}
+ end
+ end
+
+ def status_update(conn, %{"id" => id} = params) do
+ with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
+ conn
+ |> put_view(StatusView)
+ |> render("status.json", %{activity: activity})
+ end
+ end
+
+ def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
+ json(conn, %{})
+ end
+ end
+
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)
|> json("Not found")
end
+ def errors(conn, {:error, reason}) do
+ conn
+ |> put_status(400)
+ |> json(reason)
+ end
+
def errors(conn, {:param_cast, _}) do
conn
|> put_status(400)
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
new file mode 100644
index 000000000..47a73dc7e
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ReportView do
+ use Pleroma.Web, :view
+ alias Pleroma.Activity
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ def render("index.json", %{reports: reports}) do
+ %{
+ reports: render_many(reports, __MODULE__, "show.json", as: :report)
+ }
+ end
+
+ def render("show.json", %{report: report}) do
+ user = User.get_cached_by_ap_id(report.data["actor"])
+ created_at = Utils.to_masto_date(report.data["published"])
+
+ [account_ap_id | status_ap_ids] = report.data["object"]
+ account = User.get_cached_by_ap_id(account_ap_id)
+
+ statuses =
+ Enum.map(status_ap_ids, fn ap_id ->
+ Activity.get_by_ap_id_with_object(ap_id)
+ end)
+
+ %{
+ id: report.id,
+ account: AccountView.render("account.json", %{user: account}),
+ actor: AccountView.render("account.json", %{user: user}),
+ content: report.data["content"],
+ created_at: created_at,
+ statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
+ state: report.data["state"]
+ }
+ end
+end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 29c4c1014..208c12c7b 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -71,6 +71,9 @@ defmodule Pleroma.Web.CommonAPI do
{:ok, _} <- unpin(activity_id, user),
{:ok, delete} <- ActivityPub.delete(object) do
{:ok, delete}
+ else
+ _ ->
+ {:error, "Could not delete"}
end
end
@@ -315,6 +318,60 @@ defmodule Pleroma.Web.CommonAPI do
end
end
+ def update_report_state(activity_id, state) do
+ with %Activity{} = activity <- Activity.get_by_id(activity_id),
+ {:ok, activity} <- Utils.update_report_state(activity, state) do
+ {:ok, activity}
+ else
+ nil ->
+ {:error, :not_found}
+
+ {:error, reason} ->
+ {:error, reason}
+
+ _ ->
+ {:error, "Could not update state"}
+ end
+ end
+
+ def update_activity_scope(activity_id, opts \\ %{}) do
+ with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
+ {:ok, activity} <- toggle_sensitive(activity, opts),
+ {:ok, activity} <- set_visibility(activity, opts) do
+ {:ok, activity}
+ else
+ nil ->
+ {:error, :not_found}
+
+ {:error, reason} ->
+ {:error, reason}
+ end
+ end
+
+ defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do
+ toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)})
+ end
+
+ defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive})
+ when is_boolean(sensitive) do
+ new_data = Map.put(object.data, "sensitive", sensitive)
+
+ {:ok, object} =
+ object
+ |> Object.change(%{data: new_data})
+ |> Object.update_and_set_cache()
+
+ {:ok, Map.put(activity, :object, object)}
+ end
+
+ defp toggle_sensitive(activity, _), do: {:ok, activity}
+
+ defp set_visibility(activity, %{"visibility" => visibility}) do
+ Utils.update_activity_visibility(activity, visibility)
+ end
+
+ defp set_visibility(activity, _), do: {:ok, activity}
+
def hide_reblogs(user, muted) do
ap_id = muted.ap_id
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 1dfe50b40..bee2fd159 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -237,13 +237,11 @@ defmodule Pleroma.Web.CommonAPI.Utils do
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
}
- if in_reply_to do
- in_reply_to_object = Object.normalize(in_reply_to)
-
- object
- |> Map.put("inReplyTo", in_reply_to_object.data["id"])
+ with false <- is_nil(in_reply_to),
+ %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
+ Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
else
- object
+ _ -> object
end
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 87e597074..66056a846 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -303,7 +303,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
activities =
[user.ap_id | user.following]
|> ActivityPub.fetch_activities(params)
- |> ActivityPub.contain_timeline(user)
|> Enum.reverse()
conn
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 7fef82f82..6a4e4a1d4 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -194,6 +194,14 @@ defmodule Pleroma.Web.Router do
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)
+
+ get("/reports", AdminAPIController, :list_reports)
+ get("/reports/:id", AdminAPIController, :report_show)
+ put("/reports/:id", AdminAPIController, :report_update_state)
+ post("/reports/:id/respond", AdminAPIController, :report_respond)
+
+ put("/statuses/:id", AdminAPIController, :status_update)
+ delete("/statuses/:id", AdminAPIController, :status_delete)
end
scope "/", Pleroma.Web.TwitterAPI do
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 3c5a70be9..31e86685a 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -101,9 +101,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|> Map.put("blocking_user", user)
|> Map.put("user", user)
- activities =
- ActivityPub.fetch_activities([user.ap_id | user.following], params)
- |> ActivityPub.contain_timeline(user)
+ activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
conn
|> put_view(ActivityView)
diff --git a/priv/repo/migrations/20190511191044_set_default_state_to_reports.exs b/priv/repo/migrations/20190511191044_set_default_state_to_reports.exs
new file mode 100644
index 000000000..0d3d253b6
--- /dev/null
+++ b/priv/repo/migrations/20190511191044_set_default_state_to_reports.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.SetDefaultStateToReports do
+ use Ecto.Migration
+
+ def up do
+ execute """
+ UPDATE activities AS a
+ SET data = jsonb_set(data, '{state}', '"open"', true)
+ WHERE data->>'type' = 'Flag'
+ """
+ end
+
+ def down do
+ execute """
+ UPDATE activities AS a
+ SET data = data #- '{state}'
+ WHERE data->>'type' = 'Flag'
+ """
+ end
+end
diff --git a/priv/repo/migrations/20190515222404_add_thread_visibility_function.exs b/priv/repo/migrations/20190515222404_add_thread_visibility_function.exs
new file mode 100644
index 000000000..dc9abc998
--- /dev/null
+++ b/priv/repo/migrations/20190515222404_add_thread_visibility_function.exs
@@ -0,0 +1,73 @@
+defmodule Pleroma.Repo.Migrations.AddThreadVisibilityFunction do
+ use Ecto.Migration
+ @disable_ddl_transaction true
+
+ def up do
+ statement = """
+ CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
+ DECLARE
+ public varchar := 'https://www.w3.org/ns/activitystreams#Public';
+ child objects%ROWTYPE;
+ activity activities%ROWTYPE;
+ actor_user users%ROWTYPE;
+ author_fa varchar;
+ valid_recipients varchar[];
+ BEGIN
+ --- Fetch our actor.
+ SELECT * INTO actor_user FROM users WHERE users.ap_id = actor;
+
+ --- Fetch our initial activity.
+ SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
+
+ LOOP
+ --- Ensure that we have an activity before continuing.
+ --- If we don't, the thread is not satisfiable.
+ IF activity IS NULL THEN
+ RETURN false;
+ END IF;
+
+ --- We only care about Create activities.
+ IF activity.data->>'type' != 'Create' THEN
+ RETURN true;
+ END IF;
+
+ --- Normalize the child object into child.
+ SELECT * INTO child FROM objects
+ INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
+ WHERE COALESCE(activity.data->'object'->>'id', activity.data->>'object') = objects.data->>'id';
+
+ --- Fetch the author's AS2 following collection.
+ SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = activity.actor;
+
+ --- Prepare valid recipients array.
+ valid_recipients := ARRAY[actor, public];
+ IF ARRAY[author_fa] && actor_user.following THEN
+ valid_recipients := valid_recipients || author_fa;
+ END IF;
+
+ --- Check visibility.
+ IF NOT valid_recipients && activity.recipients THEN
+ --- activity not visible, break out of the loop
+ RETURN false;
+ END IF;
+
+ --- If there's a parent, load it and do this all over again.
+ IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN
+ SELECT * INTO activity FROM activities
+ INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
+ WHERE child.data->>'inReplyTo' = objects.data->>'id';
+ ELSE
+ RETURN true;
+ END IF;
+ END LOOP;
+ END;
+ $$ LANGUAGE plpgsql IMMUTABLE;
+ """
+
+ execute(statement)
+ end
+
+ def down do
+ execute("drop function thread_visibility(actor varchar, activity_id varchar)")
+ end
+end
diff --git a/test/conversation_test.exs b/test/conversation_test.exs
index 864b2eb03..5903d10ff 100644
--- a/test/conversation_test.exs
+++ b/test/conversation_test.exs
@@ -11,6 +11,26 @@ defmodule Pleroma.ConversationTest do
import Pleroma.Factory
+ test "it goes through old direct conversations" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{"visibility" => "direct", "status" => "hey @#{other_user.nickname}"})
+
+ Repo.delete_all(Conversation)
+ Repo.delete_all(Conversation.Participation)
+
+ refute Repo.one(Conversation)
+
+ Conversation.bump_for_all_activities()
+
+ assert Repo.one(Conversation)
+ [participation, _p2] = Repo.all(Conversation.Participation)
+
+ assert participation.read
+ end
+
test "it creates a conversation for given ap_id" do
assert {:ok, %Conversation{} = conversation} =
Conversation.create_for_ap_id("https://some_ap_id")
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 2a2954ad6..90c7d80f2 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -43,7 +43,7 @@ defmodule Pleroma.Factory do
def note_factory(attrs \\ %{}) do
text = sequence(:text, &"This is :moominmamma: note #{&1}")
- user = insert(:user)
+ user = attrs[:user] || insert(:user)
data = %{
"type" => "Note",
@@ -113,7 +113,8 @@ defmodule Pleroma.Factory do
end
def note_activity_factory(attrs \\ %{}) do
- note = attrs[:note] || insert(:note)
+ user = attrs[:user] || insert(:user)
+ note = attrs[:note] || insert(:note, user: user)
data = %{
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs
new file mode 100644
index 000000000..579130b05
--- /dev/null
+++ b/test/tasks/database_test.exs
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.DatabaseTest do
+ alias Pleroma.Repo
+ alias Pleroma.User
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ setup_all do
+ Mix.shell(Mix.Shell.Process)
+
+ on_exit(fn ->
+ Mix.shell(Mix.Shell.IO)
+ end)
+
+ :ok
+ end
+
+ describe "running update_users_following_followers_counts" do
+ test "following and followers count are updated" do
+ [user, user2] = insert_pair(:user)
+ {:ok, %User{following: following, info: info} = user} = User.follow(user, user2)
+
+ assert length(following) == 2
+ assert info.follower_count == 0
+
+ info_cng = Ecto.Changeset.change(info, %{follower_count: 3})
+
+ {:ok, user} =
+ user
+ |> Ecto.Changeset.change(%{following: following ++ following})
+ |> Ecto.Changeset.put_embed(:info, info_cng)
+ |> Repo.update()
+
+ assert length(user.following) == 4
+ assert user.info.follower_count == 3
+
+ assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
+
+ user = User.get_by_id(user.id)
+
+ assert length(user.following) == 2
+ assert user.info.follower_count == 0
+ end
+ end
+end
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index eaf4ecf84..260ce0d95 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.UserTest do
+ alias Pleroma.Repo
alias Pleroma.User
use Pleroma.DataCase
@@ -338,4 +339,31 @@ defmodule Mix.Tasks.Pleroma.UserTest do
assert message == "User #{nickname} statuses deleted."
end
end
+
+ describe "running toggle_confirmed" do
+ test "user is confirmed" do
+ %{id: id, nickname: nickname} = insert(:user, info: %{confirmation_pending: false})
+
+ assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
+ assert_received {:mix_shell, :info, [message]}
+ assert message == "#{nickname} needs confirmation."
+
+ user = Repo.get(User, id)
+ assert user.info.confirmation_pending
+ assert user.info.confirmation_token
+ end
+
+ test "user is not confirmed" do
+ %{id: id, nickname: nickname} =
+ insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
+
+ assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
+ assert_received {:mix_shell, :info, [message]}
+ assert message == "#{nickname} doesn't need confirmation."
+
+ user = Repo.get(User, id)
+ refute user.info.confirmation_pending
+ refute user.info.confirmation_token
+ end
+ end
end
diff --git a/test/user_test.exs b/test/user_test.exs
index 0b65e89e9..721b65693 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -626,6 +626,37 @@ defmodule Pleroma.UserTest do
end
end
+ describe "remove duplicates from following list" do
+ test "it removes duplicates" do
+ user = insert(:user)
+ follower = insert(:user)
+
+ {:ok, %User{following: following} = follower} = User.follow(follower, user)
+ assert length(following) == 2
+
+ {:ok, follower} =
+ follower
+ |> User.update_changeset(%{following: following ++ following})
+ |> Repo.update()
+
+ assert length(follower.following) == 4
+
+ {:ok, follower} = User.remove_duplicated_following(follower)
+ assert length(follower.following) == 2
+ end
+
+ test "it does nothing when following is uniq" do
+ user = insert(:user)
+ follower = insert(:user)
+
+ {:ok, follower} = User.follow(follower, user)
+ assert length(follower.following) == 2
+
+ {:ok, follower} = User.remove_duplicated_following(follower)
+ assert length(follower.following) == 2
+ end
+ end
+
describe "follow_import" do
test "it imports user followings from list" do
[user1, user2, user3] = insert_list(3, :user)
@@ -873,7 +904,6 @@ defmodule Pleroma.UserTest do
assert [activity] ==
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
- |> ActivityPub.contain_timeline(user2)
{:ok, _user} = User.deactivate(user)
@@ -882,7 +912,6 @@ defmodule Pleroma.UserTest do
assert [] ==
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
- |> ActivityPub.contain_timeline(user2)
end
end
@@ -1204,4 +1233,22 @@ defmodule Pleroma.UserTest do
assert Map.get(user_show, "followers_count") == 2
end
+
+ describe "toggle_confirmation/1" do
+ test "if user is confirmed" do
+ user = insert(:user, info: %{confirmation_pending: false})
+ {:ok, user} = User.toggle_confirmation(user)
+
+ assert user.info.confirmation_pending
+ assert user.info.confirmation_token
+ end
+
+ test "if user is unconfirmed" do
+ user = insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
+ {:ok, user} = User.toggle_confirmation(user)
+
+ refute user.info.confirmation_pending
+ refute user.info.confirmation_token
+ end
+ end
end
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 0f90aa1ac..c18e0ab5f 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -462,6 +462,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
refute Enum.member?(activities, activity_three.id)
end
+ test "doesn't return activities from blocked domains" do
+ domain = "dogwhistle.zone"
+ domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
+ note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
+ activity = insert(:note_activity, %{note: note})
+ user = insert(:user)
+ {:ok, user} = User.block_domain(user, domain)
+
+ activities =
+ ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+
+ refute activity in activities
+
+ followed_user = insert(:user)
+ ActivityPub.follow(user, followed_user)
+ {:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user)
+
+ activities =
+ ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
+
+ refute repeat_activity in activities
+ end
+
test "doesn't return muted activities" do
activity_one = insert(:note_activity)
activity_two = insert(:note_activity)
@@ -960,17 +983,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
"in_reply_to_status_id" => private_activity_2.id
})
- activities = ActivityPub.fetch_activities([user1.ap_id | user1.following])
+ activities =
+ ActivityPub.fetch_activities([user1.ap_id | user1.following])
+ |> Enum.map(fn a -> a.id end)
private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
- assert [public_activity, private_activity_1, private_activity_3] == activities
+ assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
assert length(activities) == 3
- activities = ActivityPub.contain_timeline(activities, user1)
+ activities =
+ ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
+ |> Enum.map(fn a -> a.id end)
- assert [public_activity, private_activity_1] == activities
+ assert [public_activity.id, private_activity_1.id] == activities
assert length(activities) == 2
end
end
diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs
index 9c03c8be2..e2584f635 100644
--- a/test/web/activity_pub/visibilty_test.exs
+++ b/test/web/activity_pub/visibilty_test.exs
@@ -96,6 +96,16 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
refute Visibility.visible_for_user?(direct, unrelated)
end
+ test "doesn't die when the user doesn't exist",
+ %{
+ direct: direct,
+ user: user
+ } do
+ Repo.delete(user)
+ Cachex.clear(:user_cache)
+ refute Visibility.is_private?(direct)
+ end
+
test "get_visibility", %{
public: public,
private: private,
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 6c1897b5a..ca12c7215 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -5,8 +5,10 @@
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
use Pleroma.Web.ConnCase
+ alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.UserInviteToken
+ alias Pleroma.Web.CommonAPI
import Pleroma.Factory
describe "/api/pleroma/admin/users" do
@@ -949,4 +951,329 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
}
end
end
+
+ describe "GET /api/pleroma/admin/reports/:id" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+
+ %{conn: assign(conn, :user, admin)}
+ end
+
+ test "returns report by its id", %{conn: conn} do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel offended",
+ "status_ids" => [activity.id]
+ })
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports/#{report_id}")
+ |> json_response(:ok)
+
+ assert response["id"] == report_id
+ end
+
+ test "returns 404 when report id is invalid", %{conn: conn} do
+ conn = get(conn, "/api/pleroma/admin/reports/test")
+
+ assert json_response(conn, :not_found) == "Not found"
+ end
+ end
+
+ describe "PUT /api/pleroma/admin/reports/:id" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel offended",
+ "status_ids" => [activity.id]
+ })
+
+ %{conn: assign(conn, :user, admin), id: report_id}
+ end
+
+ test "mark report as resolved", %{conn: conn, id: id} do
+ response =
+ conn
+ |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "resolved"})
+ |> json_response(:ok)
+
+ assert response["state"] == "resolved"
+ end
+
+ test "closes report", %{conn: conn, id: id} do
+ response =
+ conn
+ |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"})
+ |> json_response(:ok)
+
+ assert response["state"] == "closed"
+ end
+
+ test "returns 400 when state is unknown", %{conn: conn, id: id} do
+ conn =
+ conn
+ |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "test"})
+
+ assert json_response(conn, :bad_request) == "Unsupported state"
+ end
+
+ test "returns 404 when report is not exist", %{conn: conn} do
+ conn =
+ conn
+ |> put("/api/pleroma/admin/reports/test", %{"state" => "closed"})
+
+ assert json_response(conn, :not_found) == "Not found"
+ end
+ end
+
+ describe "GET /api/pleroma/admin/reports" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+
+ %{conn: assign(conn, :user, admin)}
+ end
+
+ test "returns empty response when no reports created", %{conn: conn} do
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports")
+ |> json_response(:ok)
+
+ assert Enum.empty?(response["reports"])
+ end
+
+ test "returns reports", %{conn: conn} do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel offended",
+ "status_ids" => [activity.id]
+ })
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports")
+ |> json_response(:ok)
+
+ [report] = response["reports"]
+
+ assert length(response["reports"]) == 1
+ assert report["id"] == report_id
+ end
+
+ test "returns reports with specified state", %{conn: conn} do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: first_report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel offended",
+ "status_ids" => [activity.id]
+ })
+
+ {:ok, %{id: second_report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I don't like this user"
+ })
+
+ CommonAPI.update_report_state(second_report_id, "closed")
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports", %{
+ "state" => "open"
+ })
+ |> json_response(:ok)
+
+ [open_report] = response["reports"]
+
+ assert length(response["reports"]) == 1
+ assert open_report["id"] == first_report_id
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports", %{
+ "state" => "closed"
+ })
+ |> json_response(:ok)
+
+ [closed_report] = response["reports"]
+
+ assert length(response["reports"]) == 1
+ assert closed_report["id"] == second_report_id
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports", %{
+ "state" => "resolved"
+ })
+ |> json_response(:ok)
+
+ assert Enum.empty?(response["reports"])
+ end
+
+ test "returns 403 when requested by a non-admin" do
+ user = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> get("/api/pleroma/admin/reports")
+
+ assert json_response(conn, :forbidden) == %{"error" => "User is not admin."}
+ end
+
+ test "returns 403 when requested by anonymous" do
+ conn =
+ build_conn()
+ |> get("/api/pleroma/admin/reports")
+
+ assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
+ end
+ end
+
+ describe "POST /api/pleroma/admin/reports/:id/respond" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+
+ %{conn: assign(conn, :user, admin)}
+ end
+
+ test "returns created dm", %{conn: conn} do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel offended",
+ "status_ids" => [activity.id]
+ })
+
+ response =
+ conn
+ |> post("/api/pleroma/admin/reports/#{report_id}/respond", %{
+ "status" => "I will check it out"
+ })
+ |> json_response(:ok)
+
+ recipients = Enum.map(response["mentions"], & &1["username"])
+
+ assert conn.assigns[:user].nickname in recipients
+ assert reporter.nickname in recipients
+ assert response["content"] == "I will check it out"
+ assert response["visibility"] == "direct"
+ end
+
+ test "returns 400 when status is missing", %{conn: conn} do
+ conn = post(conn, "/api/pleroma/admin/reports/test/respond")
+
+ assert json_response(conn, :bad_request) == "Invalid parameters"
+ end
+
+ test "returns 404 when report id is invalid", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/reports/test/respond", %{
+ "status" => "foo"
+ })
+
+ assert json_response(conn, :not_found) == "Not found"
+ end
+ end
+
+ describe "PUT /api/pleroma/admin/statuses/:id" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+ activity = insert(:note_activity)
+
+ %{conn: assign(conn, :user, admin), id: activity.id}
+ end
+
+ test "toggle sensitive flag", %{conn: conn, id: id} do
+ response =
+ conn
+ |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"})
+ |> json_response(:ok)
+
+ assert response["sensitive"]
+
+ response =
+ conn
+ |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"})
+ |> json_response(:ok)
+
+ refute response["sensitive"]
+ end
+
+ test "change visibility flag", %{conn: conn, id: id} do
+ response =
+ conn
+ |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "public"})
+ |> json_response(:ok)
+
+ assert response["visibility"] == "public"
+
+ response =
+ conn
+ |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "private"})
+ |> json_response(:ok)
+
+ assert response["visibility"] == "private"
+
+ response =
+ conn
+ |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "unlisted"})
+ |> json_response(:ok)
+
+ assert response["visibility"] == "unlisted"
+ end
+
+ test "returns 400 when visibility is unknown", %{conn: conn, id: id} do
+ conn =
+ conn
+ |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"})
+
+ assert json_response(conn, :bad_request) == "Unsupported visibility"
+ end
+ end
+
+ describe "DELETE /api/pleroma/admin/statuses/:id" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+ activity = insert(:note_activity)
+
+ %{conn: assign(conn, :user, admin), id: activity.id}
+ end
+
+ test "deletes status", %{conn: conn, id: id} do
+ conn
+ |> delete("/api/pleroma/admin/statuses/#{id}")
+ |> json_response(:ok)
+
+ refute Activity.get_by_id(id)
+ end
+
+ test "returns error when status is not exist", %{conn: conn} do
+ conn =
+ conn
+ |> delete("/api/pleroma/admin/statuses/test")
+
+ assert json_response(conn, :bad_request) == "Could not delete"
+ end
+ end
end
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index b0c441a37..696060fb1 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -261,10 +261,41 @@ defmodule Pleroma.Web.CommonAPITest do
data: %{
"type" => "Flag",
"content" => ^comment,
- "object" => [^target_ap_id, ^activity_ap_id]
+ "object" => [^target_ap_id, ^activity_ap_id],
+ "state" => "open"
}
} = flag_activity
end
+
+ test "updates report state" do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %Activity{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel offended",
+ "status_ids" => [activity.id]
+ })
+
+ {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
+
+ assert report.data["state"] == "resolved"
+ end
+
+ test "does not update report state when state is unsupported" do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %Activity{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ "account_id" => target_user.id,
+ "comment" => "I feel offended",
+ "status_ids" => [activity.id]
+ })
+
+ assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
+ end
end
describe "reblog muting" do
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index 40e7739e7..90d67a55f 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -2129,7 +2129,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
- assert length(anonymous_response) == 0
+ assert Enum.empty?(anonymous_response)
end
test "does not return others' favorited DM when user is not one of recipients", %{
@@ -2153,7 +2153,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
- assert length(response) == 0
+ assert Enum.empty?(response)
end
test "paginates favorites using since_id and max_id", %{