Merge remote-tracking branch 'remotes/upstream/develop' into 1427-oauth-admin-scopes
authorIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 10 Dec 2019 05:55:14 +0000 (08:55 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 10 Dec 2019 05:55:14 +0000 (08:55 +0300)
# Conflicts:
# CHANGELOG.md

87 files changed:
.formatter.exs
CHANGELOG.md
config/config.exs
docs/API/differences_in_mastoapi_responses.md
docs/API/pleroma_api.md
docs/administration/CLI_tasks/config.md
docs/administration/CLI_tasks/database.md
docs/administration/CLI_tasks/digest.md
docs/administration/CLI_tasks/emoji.md
docs/administration/CLI_tasks/general_cli_task_info.include [new file with mode: 0644]
docs/administration/CLI_tasks/instance.md
docs/administration/CLI_tasks/relay.md
docs/administration/CLI_tasks/uploads.md
docs/administration/CLI_tasks/user.md
docs/configuration/cheatsheet.md
lib/mix/tasks/pleroma/config.ex
lib/mix/tasks/pleroma/notification_settings.ex [new file with mode: 0644]
lib/mix/tasks/pleroma/user.ex
lib/pleroma/activity.ex
lib/pleroma/activity/queries.ex
lib/pleroma/activity/search.ex
lib/pleroma/application.ex
lib/pleroma/clippy.ex
lib/pleroma/ecto_enums.ex [new file with mode: 0644]
lib/pleroma/following_relationship.ex
lib/pleroma/html.ex
lib/pleroma/notification.ex
lib/pleroma/object.ex
lib/pleroma/plugs/parsers_plug.ex [new file with mode: 0644]
lib/pleroma/user.ex
lib/pleroma/user/notification_setting.ex [new file with mode: 0644]
lib/pleroma/user/search.ex
lib/pleroma/user_relationship.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/views/report_view.ex
lib/pleroma/web/chat_channel.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/endpoint.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/mastodon_api.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/oauth/token/clean_worker.ex
lib/pleroma/web/pleroma_api/controllers/account_controller.ex
lib/pleroma/web/push/impl.ex
lib/pleroma/web/streamer/worker.ex
lib/pleroma/workers/web_pusher_worker.ex
mix.exs
mix.lock
priv/repo/migrations/20191118084425_create_user_relationships.exs [new file with mode: 0644]
priv/repo/migrations/20191118084500_data_migration_populate_user_relationships.exs [new file with mode: 0644]
priv/scrubbers/default.ex [new file with mode: 0644]
priv/scrubbers/links_only.ex [new file with mode: 0644]
priv/scrubbers/media_proxy.ex [new file with mode: 0644]
priv/scrubbers/twitter_text.ex [new file with mode: 0644]
test/conversation/participation_test.exs
test/notification_test.exs
test/support/builders/user_builder.ex
test/support/channel_case.ex
test/support/factory.ex
test/support/helpers.ex
test/tasks/config_test.exs
test/user/notification_setting_test.exs [new file with mode: 0644]
test/user_relationship_test.exs [new file with mode: 0644]
test/user_search_test.exs
test/user_test.exs
test/web/activity_pub/activity_pub_controller_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/transmogrifier/follow_handling_test.exs
test/web/activity_pub/utils_test.exs
test/web/admin_api/admin_api_controller_test.exs
test/web/chat_channel_test.exs [new file with mode: 0644]
test/web/common_api/common_api_test.exs
test/web/mastodon_api/controllers/account_controller_test.exs
test/web/mastodon_api/controllers/notification_controller_test.exs
test/web/mastodon_api/controllers/search_controller_test.exs
test/web/mastodon_api/controllers/status_controller_test.exs
test/web/mastodon_api/controllers/timeline_controller_test.exs
test/web/mastodon_api/views/account_view_test.exs
test/web/mastodon_api/views/notification_view_test.exs
test/web/mastodon_api/views/status_view_test.exs
test/web/push/impl_test.exs
test/web/streamer/streamer_test.exs
test/web/twitter_api/util_controller_test.exs

index 7fa95a6190189ee1602c5dd5ba039c10910e00a4..5799ac127aa70ed8856089106c704ca68f8a1958 100644 (file)
@@ -1,3 +1,3 @@
 [
-  inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs"]
+  inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs", "priv/scrubbers/*.ex"]
 ]
index 007c6f114294ddaaac45f8b1372b47f73f85b070..bb32732866827965b6fe8ff1f646b500f25ceb85 100644 (file)
@@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - OStatus: Extract RSS functionality
 - Deprecated `User.Info` embedded schema (fields moved to `User`)
 - Store status data inside Flag activity
+- Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`).
 <details>
   <summary>API Changes</summary>
 
@@ -35,9 +36,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: `pleroma.thread_muted` to the Status entity
 - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
 - Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
+- Admin API: Render whole status in grouped reports
+- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
 </details>
 
 ### Added
+- `:chat_limit` option to limit chat characters.
 - Refreshing poll results for remote polls
 - Authentication: Added rate limit for password-authorized actions / login existence checks
 - Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app.
@@ -45,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mix task to list all users (`mix pleroma.user list`)
 - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
 - MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers.
+- User notification settings: Add `privacy_option` option.
 - OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
 <details>
   <summary>API Changes</summary>
@@ -79,11 +84,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Fixed
 - Report emails now include functional links to profiles of remote user accounts
 - Not being able to log in to some third-party apps when logged in to MastoFE
+- MRF: `Delete` activities being exempt from MRF policies
+- OTP releases: Not being able to configure OAuth expired token cleanup interval
+- OTP releases: Not being able to configure HTML sanitization policy
 <details>
   <summary>API Changes</summary>
 
 - Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
 - Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
+- AdminAPI: If some status received reports both in the "new" format and "old" format it was considered reports on two different statuses (in the context of grouped reports)
 - Admin API: Error when trying to update reports in the "old" format
 </details>
 
index a8534da5417ca042eefb815985bfa5cfd1c320c9..6ed80005632895dfc319772ced0d1dd9b53f7992 100644 (file)
@@ -225,6 +225,7 @@ config :pleroma, :instance,
   notify_email: "noreply@example.com",
   description: "A Pleroma instance, an alternative fediverse server",
   limit: 5_000,
+  chat_limit: 5_000,
   remote_limit: 100_000,
   upload_limit: 16_000_000,
   avatar_upload_limit: 2_000_000,
index 006d17c1bc28abd63ae6331f0cf9e9ab99dbac9a..566789ec74809801d98656734612bbac5256cf10 100644 (file)
@@ -103,6 +103,7 @@ The `type` value is `move`. Has an additional field:
 Accepts additional parameters:
 
 - `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
+- `with_move`: boolean, when set to `true` will include Move notifications. `false` by default.
 
 ## POST `/api/v1/statuses`
 
index ad16d027e6e8d311741f6be092197d2b79f63db8..7228d805b5a7e850628f9b9168febf3ce7f8c9c5 100644 (file)
@@ -302,6 +302,7 @@ See [Admin-API](admin_api.md)
     * `follows`: BOOLEAN field, receives notifications from people the user follows
     * `remote`: BOOLEAN field, receives notifications from people on remote instances
     * `local`: BOOLEAN field, receives notifications from people on the local instance
+    * `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
 * Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
 
 ## `/api/pleroma/healthcheck`
index ce19e2402ac0911d56ec06310c81b6612c8982ba..e9d44b9a426f0d55e157a36f1ba48a8b70a7c5b6 100644 (file)
@@ -3,17 +3,26 @@
 !!! danger
     This is a Work In Progress, not usable just yet.
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl config` and in case of source installs it's
-`mix pleroma.config`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Transfer config from file to DB.
 
-```sh
-$PREFIX migrate_to_db
+```sh tab="OTP"
+ ./bin/pleroma_ctl config migrate_to_db
 ```
 
+```sh tab="From Source"
+mix pleroma.config migrate_to_db
+```
+
+
 ## Transfer config from DB to `config/env.exported_from_db.secret.exs`
 
-```sh
-$PREFIX migrate_from_db <env>
+```sh tab="OTP"
+ ./bin/pleroma_ctl config migrate_from_db <env>
 ```
+
+```sh tab="From Source"
+mix pleroma.config migrate_from_db <env>
+```
+
index 3011646c8bf65ea9342f154f439e97490a8a99ea..51c7484ba21343153ac310408b59fd4755556c2d 100644 (file)
@@ -1,6 +1,6 @@
 # Database maintenance tasks
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl database` and in case of source installs it's `mix pleroma.database`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 !!! danger
     These mix tasks can take a long time to complete. Many of them were written to address specific database issues that happened because of bugs in migrations or other specific scenarios. Do not run these tasks "just in case" if everything is fine your instance.
@@ -9,8 +9,12 @@ Every command should be ran with a prefix, in case of OTP releases it is `./bin/
 
 Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once if the instance was created before Pleroma 1.0.5. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration.
 
-```sh
-$PREFIX remove_embedded_objects [<options>]
+```sh tab="OTP"
+./bin/pleroma_ctl database remove_embedded_objects [<options>]
+```
+
+```sh tab="From Source"
+mix pleroma.database remove_embedded_objects [<options>]
 ```
 
 ### Options
@@ -20,11 +24,15 @@ $PREFIX remove_embedded_objects [<options>]
 
 This will prune remote posts older than 90 days (configurable with [`config :pleroma, :instance, remote_post_retention_days`](../../configuration/cheatsheet.md#instance)) from the database, they will be refetched from source when accessed.
 
-!!! note
-    The disk space will only be reclaimed after `VACUUM FULL`
+!!! danger
+    The disk space will only be reclaimed after `VACUUM FULL`. You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free.
+
+```sh tab="OTP"
+./bin/pleroma_ctl database prune_objects [<options>]
+```
 
-```sh
-$PREFIX pleroma.database prune_objects [<options>]
+```sh tab="From Source"
+mix pleroma.database prune_objects [<options>]
 ```
 
 ### Options
@@ -34,18 +42,30 @@ $PREFIX pleroma.database prune_objects [<options>]
 
 Can be safely re-run
 
-```sh
-$PREFIX bump_all_conversations
+```sh tab="OTP"
+./bin/pleroma_ctl database bump_all_conversations
+```
+
+```sh tab="From Source"
+mix pleroma.database bump_all_conversations
 ```
 
 ## Remove duplicated items from following and update followers count for all users
 
-```sh
-$PREFIX update_users_following_followers_counts
+```sh tab="OTP"
+./bin/pleroma_ctl database update_users_following_followers_counts
+```
+
+```sh tab="From Source"
+mix pleroma.database update_users_following_followers_counts
 ```
 
 ## Fix the pre-existing "likes" collections for all objects
 
-```sh
-$PREFIX fix_likes_collections
+```sh tab="OTP"
+./bin/pleroma_ctl database fix_likes_collections
+```
+
+```sh tab="From Source"
+mix pleroma.database fix_likes_collections
 ```
index 5477020318bafb9987e489ba171d4931abf0cf4b..a70f24c061b17624c6fa9fbf536abe9b45467b15 100644 (file)
@@ -1,13 +1,24 @@
 # Managing digest emails
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl digest` and in case of source installs it's `mix pleroma.digest`.
+
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Send digest email since given date (user registration date by default) ignoring user activity status.
 
-```sh
-$PREFIX test <nickname> [<since_date>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl digest test <nickname> [<since_date>]
+```
+
+```sh tab="From Source"
+mix pleroma.digest test <nickname> [<since_date>]
 ```
 
+
 Example: 
-```sh
-$PREFIX test donaldtheduck 2019-05-20
+```sh tab="OTP"
+ ./bin/pleroma_ctl digest test donaldtheduck 2019-05-20
 ```
+
+```sh tab="From Source"
+mix pleroma.digest test donaldtheduck 2019-05-20
+```
+
index eee02f2ef30bc2057a34d02c5ec53a642fcb3edb..a3207bc6c290a3940c2703ba52265ce10732308d 100644 (file)
@@ -1,28 +1,44 @@
 # Managing emoji packs
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl emoji` and in case of source installs it's `mix pleroma.emoji`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Lists emoji packs and metadata specified in the manifest
 
-```sh
-$PREFIX ls-packs [<options>]
+```sh tab="OTP"
+./bin/pleroma_ctl emoji ls-packs [<options>]
 ```
 
+```sh tab="From Source"
+mix pleroma.emoji ls-packs [<options>]
+```
+
+
 ### Options
 - `-m, --manifest PATH/URL` - path to a custom manifest, it can either be an URL starting with `http`, in that case the manifest will be fetched from that address, or a local path
 
 ## Fetch, verify and install the specified packs from the manifest into `STATIC-DIR/emoji/PACK-NAME`
-```sh
-$PREFIX get-packs [<options>] <packs>
+
+```sh tab="OTP"
+./bin/pleroma_ctl emoji get-packs [<options>] <packs>
+```
+
+```sh tab="From Source"
+mix pleroma.emoji get-packs [<options>] <packs>
 ```
 
 ### Options
 - `-m, --manifest PATH/URL` - same as [`ls-packs`](#ls-packs)
 
 ## Create a new manifest entry and a file list from the specified remote pack file
-```sh
-$PREFIX gen-pack PACK-URL
+
+```sh tab="OTP"
+./bin/pleroma_ctl emoji gen-pack PACK-URL
 ```
+
+```sh tab="From Source"
+mix pleroma.emoji gen-pack PACK-URL
+```
+
 Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you. 
 
   The manifest entry will either be written to a newly created `index.json` file or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
diff --git a/docs/administration/CLI_tasks/general_cli_task_info.include b/docs/administration/CLI_tasks/general_cli_task_info.include
new file mode 100644 (file)
index 0000000..a1ff1da
--- /dev/null
@@ -0,0 +1,5 @@
+Every command should be ran as the `pleroma` user from it's home directory. For example if you are superuser, you would have to wrap the command in `su pleroma -s $SHELL -lc "$COMMAND"`.
+
+??? note "From source note about `MIX_ENV`"
+
+     The `mix` command should be prefixed with the name of environment your Pleroma server is running in, usually it's `MIX_ENV=prod`
index ab0b68ad0e487bb88dc75b1d4bb28eda4f820ea4..1a3b268be52f7e6aa2af8d5adb74ac317603b579 100644 (file)
@@ -1,12 +1,17 @@
 # Managing instance configuration
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl instance` and in case of source installs it's `mix pleroma.instance`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Generate a new configuration file
-```sh
-$PREFIX gen [<options>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl instance gen [<options>]
 ```
 
+```sh tab="From Source"
+mix pleroma.instance gen [<options>]
+```
+
+
 If any of the options are left unspecified, you will be prompted interactively.
 
 ### Options
index aa44617dfbba08822da6da6ed82df1533e9d14bc..c4f078f4d1a2f4a5b3f037d67bca3829c92610dc 100644 (file)
@@ -1,30 +1,33 @@
 # Managing relays
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl relay` and in case of source installs it's `mix pleroma.relay`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Follow a relay
-```sh
-$PREFIX follow <relay_url>
+
+```sh tab="OTP"
+./bin/pleroma_ctl relay follow <relay_url>
 ```
 
-Example:
-```sh
-$PREFIX follow https://example.org/relay
+```sh tab="From Source"
+mix pleroma.relay follow <relay_url>
 ```
 
 ## Unfollow a remote relay
 
-```sh
-$PREFIX unfollow <relay_url>
+```sh tab="OTP"
+./bin/pleroma_ctl relay unfollow <relay_url>
 ```
 
-Example:
-```sh
-$PREFIX unfollow https://example.org/relay
+```sh tab="From Source"
+mix pleroma.relay unfollow <relay_url>
 ```
 
 ## List relay subscriptions
 
-```sh
-$PREFIX list
+```sh tab="OTP"
+./bin/pleroma_ctl relay list
+```
+
+```sh tab="From Source"
+mix pleroma.relay list
 ```
index 71800e341944c269989a01d5dcc0e43a78425508..e36c94c389985ab09dc551146fb69d64433e847c 100644 (file)
@@ -1,11 +1,16 @@
 # Managing uploads
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl uploads` and in case of source installs it's `mix pleroma.uploads`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Migrate uploads from local to remote storage
-```sh
-$PREFIX migrate_local <target_uploader> [<options>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl uploads migrate_local <target_uploader> [<options>]
 ```
+
+```sh tab="From Source"
+mix pleroma.uploads migrate_local <target_uploader> [<options>]
+```
+
 ### Options
 - `--delete` - delete local uploads after migrating them to the target uploader
 
index 96b2d9e6a3326fafcd5bf54e0cceeaa88a288bf0..da8363131b0167408542b8618b4abe0f7a9171f5 100644 (file)
@@ -1,12 +1,18 @@
 # Managing users
 
-Every command should be ran with a prefix, in case of OTP releases it is `./bin/pleroma_ctl user` and in case of source installs it's `mix pleroma.user`.
+{! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
 ## Create a user
-```sh
-$PREFIX new <nickname> <email> [<options>]
+
+```sh tab="OTP"
+./bin/pleroma_ctl user new <email> [<options>]
+```
+
+```sh tab="From Source"
+mix pleroma.user new <email> [<options>]
 ```
 
+
 ### Options
 - `--name <name>` - the user's display name
 - `--bio <bio>` - the user's bio
@@ -16,84 +22,159 @@ $PREFIX new <nickname> <email> [<options>]
 - `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
 
 ## List local users
-```sh
-$PREFIX list
+```sh tab="OTP"
+ ./bin/pleroma_ctl user list
 ```
 
+```sh tab="From Source"
+mix pleroma.user list
+```
+
+
 ## Generate an invite link
-```sh
-$PREFIX invite [<options>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl user invite [<options>]
 ```
 
+```sh tab="From Source"
+mix pleroma.user invite [<options>]
+```
+
+
 ### Options
 - `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
 - `--max-use NUMBER` - maximum numbers of token uses
 
 ## List generated invites
-```sh
-$PREFIX invites
+```sh tab="OTP"
+ ./bin/pleroma_ctl user invites
 ```
 
+```sh tab="From Source"
+mix pleroma.user invites
+```
+
+
 ## Revoke invite
-```sh
-$PREFIX revoke_invite <token_or_id>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user revoke_invite <token_or_id>
 ```
 
+```sh tab="From Source"
+mix pleroma.user revoke_invite <token_or_id>
+```
+
+
 ## Delete a user
-```sh
-$PREFIX rm <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user rm <nickname>
 ```
 
+```sh tab="From Source"
+mix pleroma.user rm <nickname>
+```
+
+
 ## Delete user's posts and interactions
-```sh
-$PREFIX delete_activities <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user delete_activities <nickname>
 ```
 
+```sh tab="From Source"
+mix pleroma.user delete_activities <nickname>
+```
+
+
 ## Sign user out from all applications (delete user's OAuth tokens and authorizations)
-```sh
-$PREFIX sign_out <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user sign_out <nickname>
 ```
 
+```sh tab="From Source"
+mix pleroma.user sign_out <nickname>
+```
+
+
 ## Deactivate or activate a user 
-```sh
-$PREFIX toggle_activated <nickname> 
+```sh tab="OTP"
+ ./bin/pleroma_ctl user toggle_activated <nickname> 
 ```
 
+```sh tab="From Source"
+mix pleroma.user toggle_activated <nickname> 
+```
+
+
 ## Unsubscribe local users from a user and deactivate the user
-```sh
-$PREFIX unsubscribe NICKNAME
+```sh tab="OTP"
+ ./bin/pleroma_ctl user unsubscribe NICKNAME
 ```
 
+```sh tab="From Source"
+mix pleroma.user unsubscribe NICKNAME
+```
+
+
 ## Unsubscribe local users from an instance and deactivate all accounts on it
-```sh
-$PREFIX unsubscribe_all_from_instance <instance>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user unsubscribe_all_from_instance <instance>
 ```
 
+```sh tab="From Source"
+mix pleroma.user unsubscribe_all_from_instance <instance>
+```
+
+
 ## Create a password reset link for user
-```sh
-$PREFIX reset_password <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user reset_password <nickname>
 ```
 
+```sh tab="From Source"
+mix pleroma.user reset_password <nickname>
+```
+
+
 ## Set the value of the given user's settings
-```sh
-$PREFIX set <nickname> [<options>]
+```sh tab="OTP"
+ ./bin/pleroma_ctl user set <nickname> [<options>]
 ```
+
+```sh tab="From Source"
+mix pleroma.user set <nickname> [<options>]
+```
+
 ### Options
 - `--locked`/`--no-locked` - whether the user should be locked
 - `--moderator`/`--no-moderator` - whether the user should be a moderator
 - `--admin`/`--no-admin` - whether the user should be an admin
 
 ## Add tags to a user
-```sh
-$PREFIX tag <nickname> <tags>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user tag <nickname> <tags>
 ```
 
+```sh tab="From Source"
+mix pleroma.user tag <nickname> <tags>
+```
+
+
 ## Delete tags from a user
-```sh
-$PREFIX untag <nickname> <tags>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user untag <nickname> <tags>
 ```
 
+```sh tab="From Source"
+mix pleroma.user untag <nickname> <tags>
+```
+
+
 ## Toggle confirmation status of the user
-```sh
-$PREFIX toggle_confirmed <nickname>
+```sh tab="OTP"
+ ./bin/pleroma_ctl user toggle_confirmed <nickname>
 ```
+
+```sh tab="From Source"
+mix pleroma.user toggle_confirmed <nickname>
+```
+
index dc2f5522990d7a27dcab18d4f8d9ea1eaf6e52bf..ef2711e3c4f4a9ba614f6b46c60829d836ef3b35 100644 (file)
@@ -12,6 +12,7 @@ You shouldn't edit the base config directly to avoid breakages and merge conflic
 * `notify_email`: Email used for notifications.
 * `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``.
 * `limit`: Posts character limit (CW/Subject included in the counter).
+* `chat_limit`: Character limit of the instance chat messages.
 * `remote_limit`: Hard character limit beyond which remote posts will be dropped.
 * `upload_limit`: File size limit of uploads (except for avatar, background, banner).
 * `avatar_upload_limit`: File size limit of user’s profile avatars.
index 0e21408b2b8b315cde8248fff16aeb9540c077b1..590c7a91431ee271daa10007997b17e97b7a7201 100644 (file)
@@ -52,7 +52,9 @@ defmodule Mix.Tasks.Pleroma.Config do
       |> Enum.each(fn config ->
         IO.write(
           file,
-          "config :#{config.group}, #{config.key}, #{inspect(Config.from_binary(config.value))}\r\n\r\n"
+          "config :#{config.group}, #{config.key}, #{
+            inspect(Config.from_binary(config.value), limit: :infinity)
+          }\r\n\r\n"
         )
 
         if delete? do
diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex
new file mode 100644 (file)
index 0000000..7d65f05
--- /dev/null
@@ -0,0 +1,83 @@
+defmodule Mix.Tasks.Pleroma.NotificationSettings do
+  @shortdoc "Enable&Disable privacy option for push notifications"
+  @moduledoc """
+  Example:
+
+  > mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588"  # set false only for parallel588 user
+  > mix pleroma.notification_settings --privacy-option=true # set true for all users
+
+  """
+
+  use Mix.Task
+  import Mix.Pleroma
+  import Ecto.Query
+
+  def run(args) do
+    start_pleroma()
+
+    {options, _, _} =
+      OptionParser.parse(
+        args,
+        strict: [
+          privacy_option: :boolean,
+          email_users: :string,
+          nickname_users: :string
+        ]
+      )
+
+    privacy_option = Keyword.get(options, :privacy_option)
+
+    if not is_nil(privacy_option) do
+      privacy_option
+      |> build_query(options)
+      |> Pleroma.Repo.update_all([])
+    end
+
+    shell_info("Done")
+  end
+
+  defp build_query(privacy_option, options) do
+    query =
+      from(u in Pleroma.User,
+        update: [
+          set: [
+            notification_settings:
+              fragment(
+                "jsonb_set(notification_settings, '{privacy_option}', ?)",
+                ^privacy_option
+              )
+          ]
+        ]
+      )
+
+    user_emails =
+      options
+      |> Keyword.get(:email_users, "")
+      |> String.split(",")
+      |> Enum.map(&String.trim(&1))
+      |> Enum.reject(&(&1 == ""))
+
+    query =
+      if length(user_emails) > 0 do
+        where(query, [u], u.email in ^user_emails)
+      else
+        query
+      end
+
+    user_nicknames =
+      options
+      |> Keyword.get(:nickname_users, "")
+      |> String.split(",")
+      |> Enum.map(&String.trim(&1))
+      |> Enum.reject(&(&1 == ""))
+
+    query =
+      if length(user_nicknames) > 0 do
+        where(query, [u], u.nickname in ^user_nicknames)
+      else
+        query
+      end
+
+    query
+  end
+end
index eff5191f5397d98f3b99ec6f58749c1368549d73..85c9e4954afdc344d695c7f132882cf292cb1b30 100644 (file)
@@ -371,9 +371,9 @@ defmodule Mix.Tasks.Pleroma.User do
       users
       |> Enum.each(fn user ->
         shell_info(
-          "#{user.nickname} moderator: #{user.info.is_moderator}, admin: #{user.info.is_admin}, locked: #{
-            user.info.locked
-          }, deactivated: #{user.info.deactivated}"
+          "#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{
+            user.locked
+          }, deactivated: #{user.deactivated}"
         )
       end)
     end)
index f180c1e3311021a92ea4e2bd40dd53e877d2fa6c..480b261cfdae0e2615e612b675d3022aaef43d7e 100644 (file)
@@ -241,9 +241,10 @@ defmodule Pleroma.Activity do
   def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
   def normalize(_), do: nil
 
-  def delete_by_ap_id(id) when is_binary(id) do
+  def delete_all_by_object_ap_id(id) when is_binary(id) do
     id
     |> Queries.by_object_id()
+    |> Queries.exclude_type("Delete")
     |> select([u], u)
     |> Repo.delete_all()
     |> elem(1)
@@ -255,7 +256,7 @@ defmodule Pleroma.Activity do
     |> purge_web_resp_cache()
   end
 
-  def delete_by_ap_id(_), do: nil
+  def delete_all_by_object_ap_id(_), do: nil
 
   defp purge_web_resp_cache(%Activity{} = activity) do
     %{path: path} = URI.parse(activity.data["id"])
index 949f010a81f840368fa8cf41a1f39297b8132104..26bc1099daea092b78ae7b6e5ad9c0c561608183 100644 (file)
@@ -64,4 +64,12 @@ defmodule Pleroma.Activity.Queries do
       where: fragment("(?)->>'type' = ?", activity.data, ^activity_type)
     )
   end
+
+  @spec exclude_type(query, String.t()) :: query
+  def exclude_type(query \\ Activity, activity_type) do
+    from(
+      activity in query,
+      where: fragment("(?)->>'type' != ?", activity.data, ^activity_type)
+    )
+  end
 end
index f847ac2381faa0d25678aa79503a8cc1752ba24c..d30a5a6a57ac9a1e80a2644a15fe73bdcfb3af4c 100644 (file)
@@ -86,7 +86,7 @@ defmodule Pleroma.Activity.Search do
          {:ok, object} <- Fetcher.fetch_object_from_id(search_query),
          %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
          true <- Visibility.visible_for_user?(activity, user) do
-      activities ++ [activity]
+      [activity | activities]
     else
       _ -> activities
     end
index 9dbd1e26b699130c8b507d794de8191bcef530f7..5b844aa413d03ba124bdd3bb591a406459164aef 100644 (file)
@@ -30,6 +30,7 @@ defmodule Pleroma.Application do
   # See http://elixir-lang.org/docs/stable/elixir/Application.html
   # for more information on OTP Applications
   def start(_type, _args) do
+    Pleroma.HTML.compile_scrubbers()
     Pleroma.Config.DeprecationWarnings.warn()
     setup_instrumenters()
 
@@ -147,8 +148,6 @@ defmodule Pleroma.Application do
 
   defp oauth_cleanup_child(_), do: []
 
-  defp chat_child(:test, _), do: []
-
   defp chat_child(_env, true) do
     [Pleroma.Web.ChatChannel.ChatChannelState]
   end
index bd20952a6952a4c6ee130b620097bd23bb66ae99..6e6121d4e5f958f5d117dfb3b94451bbb7c28ae6 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Clippy do
   @moduledoc false
+
   # No software is complete until they have a Clippy implementation.
   # A ballmer peak _may_ be required to change this module.
 
diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex
new file mode 100644 (file)
index 0000000..b862293
--- /dev/null
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+import EctoEnum
+
+defenum(UserRelationshipTypeEnum,
+  block: 1,
+  mute: 2,
+  reblog_mute: 3,
+  notification_mute: 4,
+  inverse_subscription: 5
+)
index a03c9bd304c595bb6e16832866001328c4951a2f..0b0219b82c3b240aa77a486e748eb943a5ba7df4 100644 (file)
@@ -121,8 +121,12 @@ defmodule Pleroma.FollowingRelationship do
       Pleroma.Web.CommonAPI.follow(following_relationship.follower, target)
     end)
     |> case do
-      [] -> :ok
-      _ -> move_following(origin, target)
+      [] ->
+        User.update_follower_count(origin)
+        :ok
+
+      _ ->
+        move_following(origin, target)
     end
   end
 end
index 997e965f0829afdf4e475a1ecdc16e5f8a9e80b9..2cae29f35170fe5b952551b405db677c8baba9f2 100644 (file)
@@ -3,6 +3,25 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.HTML do
+  # Scrubbers are compiled on boot so they can be configured in OTP releases
+  #  @on_load :compile_scrubbers
+
+  def compile_scrubbers do
+    dir = Path.join(:code.priv_dir(:pleroma), "scrubbers")
+
+    dir
+    |> File.ls!()
+    |> Enum.map(&Path.join(dir, &1))
+    |> Kernel.ParallelCompiler.compile()
+    |> case do
+      {:error, _errors, _warnings} ->
+        raise "Compiling scrubbers failed"
+
+      {:ok, _modules, _warnings} ->
+        :ok
+    end
+  end
+
   defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber]
   defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
   defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
@@ -99,215 +118,3 @@ defmodule Pleroma.HTML do
     end)
   end
 end
-
-defmodule Pleroma.HTML.Scrubber.TwitterText do
-  @moduledoc """
-  An HTML scrubbing policy which limits to twitter-style text.  Only
-  paragraphs, breaks and links are allowed through the filter.
-  """
-
-  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
-
-  require FastSanitize.Sanitizer.Meta
-  alias FastSanitize.Sanitizer.Meta
-
-  Meta.strip_comments()
-
-  # links
-  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
-
-  Meta.allow_tag_with_this_attribute_values(:a, "class", [
-    "hashtag",
-    "u-url",
-    "mention",
-    "u-url mention",
-    "mention u-url"
-  ])
-
-  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
-    "tag",
-    "nofollow",
-    "noopener",
-    "noreferrer"
-  ])
-
-  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
-
-  # paragraphs and linebreaks
-  Meta.allow_tag_with_these_attributes(:br, [])
-  Meta.allow_tag_with_these_attributes(:p, [])
-
-  # microformats
-  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
-  Meta.allow_tag_with_these_attributes(:span, [])
-
-  # allow inline images for custom emoji
-  if Pleroma.Config.get([:markup, :allow_inline_images]) do
-    # restrict img tags to http/https only, because of MediaProxy.
-    Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
-
-    Meta.allow_tag_with_these_attributes(:img, [
-      "width",
-      "height",
-      "class",
-      "title",
-      "alt"
-    ])
-  end
-
-  Meta.strip_everything_not_covered()
-end
-
-defmodule Pleroma.HTML.Scrubber.Default do
-  @doc "The default HTML scrubbing policy: no "
-
-  require FastSanitize.Sanitizer.Meta
-  alias FastSanitize.Sanitizer.Meta
-  # credo:disable-for-previous-line
-  # No idea how to fix this one…
-
-  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
-
-  Meta.strip_comments()
-
-  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
-
-  Meta.allow_tag_with_this_attribute_values(:a, "class", [
-    "hashtag",
-    "u-url",
-    "mention",
-    "u-url mention",
-    "mention u-url"
-  ])
-
-  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
-    "tag",
-    "nofollow",
-    "noopener",
-    "noreferrer",
-    "ugc"
-  ])
-
-  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
-
-  Meta.allow_tag_with_these_attributes(:abbr, ["title"])
-
-  Meta.allow_tag_with_these_attributes(:b, [])
-  Meta.allow_tag_with_these_attributes(:blockquote, [])
-  Meta.allow_tag_with_these_attributes(:br, [])
-  Meta.allow_tag_with_these_attributes(:code, [])
-  Meta.allow_tag_with_these_attributes(:del, [])
-  Meta.allow_tag_with_these_attributes(:em, [])
-  Meta.allow_tag_with_these_attributes(:i, [])
-  Meta.allow_tag_with_these_attributes(:li, [])
-  Meta.allow_tag_with_these_attributes(:ol, [])
-  Meta.allow_tag_with_these_attributes(:p, [])
-  Meta.allow_tag_with_these_attributes(:pre, [])
-  Meta.allow_tag_with_these_attributes(:strong, [])
-  Meta.allow_tag_with_these_attributes(:sub, [])
-  Meta.allow_tag_with_these_attributes(:sup, [])
-  Meta.allow_tag_with_these_attributes(:u, [])
-  Meta.allow_tag_with_these_attributes(:ul, [])
-
-  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
-  Meta.allow_tag_with_these_attributes(:span, [])
-
-  @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
-
-  if @allow_inline_images do
-    # restrict img tags to http/https only, because of MediaProxy.
-    Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
-
-    Meta.allow_tag_with_these_attributes(:img, [
-      "width",
-      "height",
-      "class",
-      "title",
-      "alt"
-    ])
-  end
-
-  if Pleroma.Config.get([:markup, :allow_tables]) do
-    Meta.allow_tag_with_these_attributes(:table, [])
-    Meta.allow_tag_with_these_attributes(:tbody, [])
-    Meta.allow_tag_with_these_attributes(:td, [])
-    Meta.allow_tag_with_these_attributes(:th, [])
-    Meta.allow_tag_with_these_attributes(:thead, [])
-    Meta.allow_tag_with_these_attributes(:tr, [])
-  end
-
-  if Pleroma.Config.get([:markup, :allow_headings]) do
-    Meta.allow_tag_with_these_attributes(:h1, [])
-    Meta.allow_tag_with_these_attributes(:h2, [])
-    Meta.allow_tag_with_these_attributes(:h3, [])
-    Meta.allow_tag_with_these_attributes(:h4, [])
-    Meta.allow_tag_with_these_attributes(:h5, [])
-  end
-
-  if Pleroma.Config.get([:markup, :allow_fonts]) do
-    Meta.allow_tag_with_these_attributes(:font, ["face"])
-  end
-
-  Meta.strip_everything_not_covered()
-end
-
-defmodule Pleroma.HTML.Transform.MediaProxy do
-  @moduledoc "Transforms inline image URIs to use MediaProxy."
-
-  alias Pleroma.Web.MediaProxy
-
-  def before_scrub(html), do: html
-
-  def scrub_attribute(:img, {"src", "http" <> target}) do
-    media_url =
-      ("http" <> target)
-      |> MediaProxy.url()
-
-    {"src", media_url}
-  end
-
-  def scrub_attribute(_tag, attribute), do: attribute
-
-  def scrub({:img, attributes, children}) do
-    attributes =
-      attributes
-      |> Enum.map(fn attr -> scrub_attribute(:img, attr) end)
-      |> Enum.reject(&is_nil(&1))
-
-    {:img, attributes, children}
-  end
-
-  def scrub({:comment, _text, _children}), do: ""
-
-  def scrub({tag, attributes, children}), do: {tag, attributes, children}
-  def scrub({_tag, children}), do: children
-  def scrub(text), do: text
-end
-
-defmodule Pleroma.HTML.Scrubber.LinksOnly do
-  @moduledoc """
-  An HTML scrubbing policy which limits to links only.
-  """
-
-  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
-
-  require FastSanitize.Sanitizer.Meta
-  alias FastSanitize.Sanitizer.Meta
-
-  Meta.strip_comments()
-
-  # links
-  Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
-
-  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
-    "tag",
-    "nofollow",
-    "noopener",
-    "noreferrer",
-    "me",
-    "ugc"
-  ])
-
-  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
-  Meta.strip_everything_not_covered()
-end
index f37e7ec671ee88c39848cc92a7f715621766999e..8f3e46af98d187e52949b4f9bdf4eb3dcda49fc1 100644 (file)
@@ -21,6 +21,8 @@ defmodule Pleroma.Notification do
 
   @type t :: %__MODULE__{}
 
+  @include_muted_option :with_muted
+
   schema "notifications" do
     field(:seen, :boolean, default: false)
     belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
@@ -34,7 +36,25 @@ defmodule Pleroma.Notification do
     |> cast(attrs, [:seen])
   end
 
-  def for_user_query(user, opts \\ []) do
+  defp for_user_query_ap_id_opts(user, opts) do
+    ap_id_relations =
+      [:block] ++
+        if opts[@include_muted_option], do: [], else: [:notification_mute]
+
+    preloaded_ap_ids = User.outgoing_relations_ap_ids(user, ap_id_relations)
+
+    exclude_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts)
+
+    exclude_notification_muted_opts =
+      Map.merge(%{notification_muted_users_ap_ids: preloaded_ap_ids[:notification_mute]}, opts)
+
+    {exclude_blocked_opts, exclude_notification_muted_opts}
+  end
+
+  def for_user_query(user, opts \\ %{}) do
+    {exclude_blocked_opts, exclude_notification_muted_opts} =
+      for_user_query_ap_id_opts(user, opts)
+
     Notification
     |> where(user_id: ^user.id)
     |> where(
@@ -54,43 +74,75 @@ defmodule Pleroma.Notification do
         )
     )
     |> preload([n, a, o], activity: {a, object: o})
-    |> exclude_muted(user, opts)
-    |> exclude_blocked(user)
+    |> exclude_notification_muted(user, exclude_notification_muted_opts)
+    |> exclude_blocked(user, exclude_blocked_opts)
     |> exclude_visibility(opts)
+    |> exclude_move(opts)
   end
 
-  defp exclude_blocked(query, user) do
+  defp exclude_blocked(query, user, opts) do
+    blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
+
     query
-    |> where([n, a], a.actor not in ^user.blocks)
+    |> where([n, a], a.actor not in ^blocked_ap_ids)
     |> where(
       [n, a],
       fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
     )
   end
 
-  defp exclude_muted(query, _, %{with_muted: true}) do
+  defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
     query
   end
 
-  defp exclude_muted(query, user, _opts) do
+  defp exclude_notification_muted(query, user, opts) do
+    notification_muted_ap_ids =
+      opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user)
+
     query
-    |> where([n, a], a.actor not in ^user.muted_notifications)
+    |> where([n, a], a.actor not in ^notification_muted_ap_ids)
     |> join(:left, [n, a], tm in Pleroma.ThreadMute,
       on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
     )
     |> where([n, a, o, tm], is_nil(tm.user_id))
   end
 
+  defp exclude_move(query, %{with_move: true}) do
+    query
+  end
+
+  defp exclude_move(query, _opts) do
+    where(query, [n, a], fragment("?->>'type' != 'Move'", a.data))
+  end
+
   @valid_visibilities ~w[direct unlisted public private]
 
   defp exclude_visibility(query, %{exclude_visibilities: visibility})
        when is_list(visibility) do
     if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
       query
+      |> join(:left, [n, a], mutated_activity in Pleroma.Activity,
+        on:
+          fragment("?->>'context'", a.data) ==
+            fragment("?->>'context'", mutated_activity.data) and
+            fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
+            fragment("?->>'type'", mutated_activity.data) == "Create",
+        as: :mutated_activity
+      )
       |> where(
-        [n, a],
+        [n, a, mutated_activity: mutated_activity],
         not fragment(
-          "activity_visibility(?, ?, ?) = ANY (?)",
+          """
+          CASE WHEN (?->>'type') = 'Like' or (?->>'type') = 'Announce'
+            THEN (activity_visibility(?, ?, ?) = ANY (?))
+            ELSE (activity_visibility(?, ?, ?) = ANY (?)) END
+          """,
+          a.data,
+          a.data,
+          mutated_activity.actor,
+          mutated_activity.recipients,
+          mutated_activity.data,
+          ^visibility,
           a.actor,
           a.recipients,
           a.data,
@@ -105,17 +157,7 @@ defmodule Pleroma.Notification do
 
   defp exclude_visibility(query, %{exclude_visibilities: visibility})
        when visibility in @valid_visibilities do
-    query
-    |> where(
-      [n, a],
-      not fragment(
-        "activity_visibility(?, ?, ?) = (?)",
-        a.actor,
-        a.recipients,
-        a.data,
-        ^visibility
-      )
-    )
+    exclude_visibility(query, [visibility])
   end
 
   defp exclude_visibility(query, %{exclude_visibilities: visibility})
@@ -313,7 +355,7 @@ defmodule Pleroma.Notification do
   def skip?(
         :followers,
         activity,
-        %{notification_settings: %{"followers" => false}} = user
+        %{notification_settings: %{followers: false}} = user
       ) do
     actor = activity.data["actor"]
     follower = User.get_cached_by_ap_id(actor)
@@ -323,14 +365,14 @@ defmodule Pleroma.Notification do
   def skip?(
         :non_followers,
         activity,
-        %{notification_settings: %{"non_followers" => false}} = user
+        %{notification_settings: %{non_followers: false}} = user
       ) do
     actor = activity.data["actor"]
     follower = User.get_cached_by_ap_id(actor)
     !User.following?(follower, user)
   end
 
-  def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} = user) do
+  def skip?(:follows, activity, %{notification_settings: %{follows: false}} = user) do
     actor = activity.data["actor"]
     followed = User.get_cached_by_ap_id(actor)
     User.following?(user, followed)
@@ -339,7 +381,7 @@ defmodule Pleroma.Notification do
   def skip?(
         :non_follows,
         activity,
-        %{notification_settings: %{"non_follows" => false}} = user
+        %{notification_settings: %{non_follows: false}} = user
       ) do
     actor = activity.data["actor"]
     followed = User.get_cached_by_ap_id(actor)
index b4ed3a9b2d1ba7b0004e213f0924009cb081bc8c..ff0e592418a4c9b9d1a3e5f5a3f1cabeeaa8a718 100644 (file)
@@ -147,7 +147,7 @@ defmodule Pleroma.Object do
 
   def delete(%Object{data: %{"id" => id}} = object) do
     with {:ok, _obj} = swap_object_with_tombstone(object),
-         deleted_activity = Activity.delete_by_ap_id(id),
+         deleted_activity = Activity.delete_all_by_object_ap_id(id),
          {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
          {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
       {:ok, object, deleted_activity}
diff --git a/lib/pleroma/plugs/parsers_plug.ex b/lib/pleroma/plugs/parsers_plug.ex
new file mode 100644 (file)
index 0000000..2e493ce
--- /dev/null
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.Parsers do
+  @moduledoc "Initializes Plug.Parsers with upload limit set at boot time"
+
+  @behaviour Plug
+
+  def init(_opts) do
+    Plug.Parsers.init(
+      parsers: [:urlencoded, :multipart, :json],
+      pass: ["*/*"],
+      json_decoder: Jason,
+      length: Pleroma.Config.get([:instance, :upload_limit]),
+      body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
+    )
+  end
+
+  defdelegate call(conn, opts), to: Plug.Parsers
+end
index 1006b5bf9bb41e4211a0a479f2c5ee37eefef195..22dd30d97fa55fc4dfb4270b6d909a11682b652b 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.User do
 
   import Ecto.Changeset
   import Ecto.Query
+  import Ecto, only: [assoc: 2]
 
   alias Comeonin.Pbkdf2
   alias Ecto.Multi
@@ -21,6 +22,7 @@ defmodule Pleroma.User do
   alias Pleroma.Repo
   alias Pleroma.RepoStreamer
   alias Pleroma.User
+  alias Pleroma.UserRelationship
   alias Pleroma.Web
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
@@ -42,6 +44,32 @@ defmodule Pleroma.User do
   @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
   @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
 
+  # AP ID user relationships (blocks, mutes etc.)
+  # Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
+  @user_relationships_config [
+    block: [
+      blocker_blocks: :blocked_users,
+      blockee_blocks: :blocker_users
+    ],
+    mute: [
+      muter_mutes: :muted_users,
+      mutee_mutes: :muter_users
+    ],
+    reblog_mute: [
+      reblog_muter_mutes: :reblog_muted_users,
+      reblog_mutee_mutes: :reblog_muter_users
+    ],
+    notification_mute: [
+      notification_muter_mutes: :notification_muted_users,
+      notification_mutee_mutes: :notification_muter_users
+    ],
+    # Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
+    inverse_subscription: [
+      subscribee_subscriptions: :subscriber_users,
+      subscriber_subscriptions: :subscribee_users
+    ]
+  ]
+
   schema "users" do
     field(:bio, :string)
     field(:email, :string)
@@ -61,7 +89,6 @@ defmodule Pleroma.User do
     field(:tags, {:array, :string}, default: [])
     field(:last_refreshed_at, :naive_datetime_usec)
     field(:last_digest_emailed_at, :naive_datetime)
-
     field(:banner, :map, default: %{})
     field(:background, :map, default: %{})
     field(:source_data, :map, default: %{})
@@ -73,12 +100,7 @@ defmodule Pleroma.User do
     field(:password_reset_pending, :boolean, default: false)
     field(:confirmation_token, :string, default: nil)
     field(:default_scope, :string, default: "public")
-    field(:blocks, {:array, :string}, default: [])
     field(:domain_blocks, {:array, :string}, default: [])
-    field(:mutes, {:array, :string}, default: [])
-    field(:muted_reblogs, {:array, :string}, default: [])
-    field(:muted_notifications, {:array, :string}, default: [])
-    field(:subscribers, {:array, :string}, default: [])
     field(:deactivated, :boolean, default: false)
     field(:no_rich_text, :boolean, default: false)
     field(:ap_enabled, :boolean, default: false)
@@ -107,22 +129,92 @@ defmodule Pleroma.User do
     field(:skip_thread_containment, :boolean, default: false)
     field(:also_known_as, {:array, :string}, default: [])
 
-    field(:notification_settings, :map,
-      default: %{
-        "followers" => true,
-        "follows" => true,
-        "non_follows" => true,
-        "non_followers" => true
-      }
+    embeds_one(
+      :notification_settings,
+      Pleroma.User.NotificationSetting,
+      on_replace: :update
     )
 
     has_many(:notifications, Notification)
     has_many(:registrations, Registration)
     has_many(:deliveries, Delivery)
 
+    has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
+    has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
+
+    for {relationship_type,
+         [
+           {outgoing_relation, outgoing_relation_target},
+           {incoming_relation, incoming_relation_source}
+         ]} <- @user_relationships_config do
+      # Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
+      has_many(outgoing_relation, UserRelationship,
+        foreign_key: :source_id,
+        where: [relationship_type: relationship_type]
+      )
+
+      # Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
+      has_many(incoming_relation, UserRelationship,
+        foreign_key: :target_id,
+        where: [relationship_type: relationship_type]
+      )
+
+      # Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
+      has_many(outgoing_relation_target, through: [outgoing_relation, :target])
+
+      # Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
+      has_many(incoming_relation_source, through: [incoming_relation, :source])
+    end
+
+    # `:blocks` is deprecated (replaced with `blocked_users` relation)
+    field(:blocks, {:array, :string}, default: [])
+    # `:mutes` is deprecated (replaced with `muted_users` relation)
+    field(:mutes, {:array, :string}, default: [])
+    # `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
+    field(:muted_reblogs, {:array, :string}, default: [])
+    # `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
+    field(:muted_notifications, {:array, :string}, default: [])
+    # `:subscribers` is deprecated (replaced with `subscriber_users` relation)
+    field(:subscribers, {:array, :string}, default: [])
+
     timestamps()
   end
 
+  for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
+        @user_relationships_config do
+    # Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
+    def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
+      target_users_query = assoc(user, unquote(outgoing_relation_target))
+
+      if restrict_deactivated? do
+        restrict_deactivated(target_users_query)
+      else
+        target_users_query
+      end
+    end
+
+    # Definitions of `blocked_users/1`, `muted_users/1`, etc.
+    def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
+      __MODULE__
+      |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
+        user,
+        restrict_deactivated?
+      ])
+      |> Repo.all()
+    end
+
+    # Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
+    def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
+      __MODULE__
+      |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
+        user,
+        restrict_deactivated?
+      ])
+      |> select([u], u.ap_id)
+      |> Repo.all()
+    end
+  end
+
   @doc "Returns if the user should be allowed to authenticate"
   def auth_active?(%User{deactivated: true}), do: false
 
@@ -946,34 +1038,45 @@ defmodule Pleroma.User do
     |> Repo.all()
   end
 
-  @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
-  def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
-    add_to_mutes(muter, ap_id, notifications?)
+  @spec mute(User.t(), User.t(), boolean()) ::
+          {:ok, list(UserRelationship.t())} | {:error, String.t()}
+  def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
+    add_to_mutes(muter, mutee, notifications?)
   end
 
-  def unmute(muter, %{ap_id: ap_id}) do
-    remove_from_mutes(muter, ap_id)
+  def unmute(%User{} = muter, %User{} = mutee) do
+    remove_from_mutes(muter, mutee)
   end
 
-  def subscribe(subscriber, %{ap_id: ap_id}) do
-    with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
-      deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
+  def subscribe(%User{} = subscriber, %User{} = target) do
+    deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
 
-      if blocks?(subscribed, subscriber) and deny_follow_blocked do
-        {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
-      else
-        User.add_to_subscribers(subscribed, subscriber.ap_id)
-      end
+    if blocks?(target, subscriber) and deny_follow_blocked do
+      {:error, "Could not subscribe: #{target.nickname} is blocking you"}
+    else
+      # Note: the relationship is inverse: subscriber acts as relationship target
+      UserRelationship.create_inverse_subscription(target, subscriber)
     end
   end
 
-  def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
+  def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
+    with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
+      subscribe(subscriber, subscribee)
+    end
+  end
+
+  def unsubscribe(%User{} = unsubscriber, %User{} = target) do
+    # Note: the relationship is inverse: subscriber acts as relationship target
+    UserRelationship.delete_inverse_subscription(target, unsubscriber)
+  end
+
+  def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
     with %User{} = user <- get_cached_by_ap_id(ap_id) do
-      User.remove_from_subscribers(user, unsubscriber.ap_id)
+      unsubscribe(unsubscriber, user)
     end
   end
 
-  def block(blocker, %User{ap_id: ap_id} = blocked) do
+  def block(%User{} = blocker, %User{} = blocked) do
     # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
     blocker =
       if following?(blocker, blocked) do
@@ -990,50 +1093,53 @@ defmodule Pleroma.User do
         nil -> blocked
       end
 
-    blocker =
-      if subscribed_to?(blocked, blocker) do
-        {:ok, blocker} = unsubscribe(blocked, blocker)
-        blocker
-      else
-        blocker
-      end
+    unsubscribe(blocked, blocker)
 
     if following?(blocked, blocker), do: unfollow(blocked, blocker)
 
     {:ok, blocker} = update_follower_count(blocker)
     {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
-    add_to_block(blocker, ap_id)
+    add_to_block(blocker, blocked)
   end
 
   # helper to handle the block given only an actor's AP id
-  def block(blocker, %{ap_id: ap_id}) do
+  def block(%User{} = blocker, %{ap_id: ap_id}) do
     block(blocker, get_cached_by_ap_id(ap_id))
   end
 
-  def unblock(blocker, %{ap_id: ap_id}) do
-    remove_from_block(blocker, ap_id)
+  def unblock(%User{} = blocker, %User{} = blocked) do
+    remove_from_block(blocker, blocked)
+  end
+
+  # helper to handle the block given only an actor's AP id
+  def unblock(%User{} = blocker, %{ap_id: ap_id}) do
+    unblock(blocker, get_cached_by_ap_id(ap_id))
   end
 
   def mutes?(nil, _), do: false
-  def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
+  def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
+
+  def mutes_user?(%User{} = user, %User{} = target) do
+    UserRelationship.mute_exists?(user, target)
+  end
 
   @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
   def muted_notifications?(nil, _), do: false
 
-  def muted_notifications?(user, %{ap_id: ap_id}),
-    do: Enum.member?(user.muted_notifications, ap_id)
+  def muted_notifications?(%User{} = user, %User{} = target),
+    do: UserRelationship.notification_mute_exists?(user, target)
+
+  def blocks?(nil, _), do: false
 
   def blocks?(%User{} = user, %User{} = target) do
-    blocks_ap_id?(user, target) || blocks_domain?(user, target)
+    blocks_user?(user, target) || blocks_domain?(user, target)
   end
 
-  def blocks?(nil, _), do: false
-
-  def blocks_ap_id?(%User{} = user, %User{} = target) do
-    Enum.member?(user.blocks, target.ap_id)
+  def blocks_user?(%User{} = user, %User{} = target) do
+    UserRelationship.block_exists?(user, target)
   end
 
-  def blocks_ap_id?(_, _), do: false
+  def blocks_user?(_, _), do: false
 
   def blocks_domain?(%User{} = user, %User{} = target) do
     domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
@@ -1043,28 +1149,41 @@ defmodule Pleroma.User do
 
   def blocks_domain?(_, _), do: false
 
-  def subscribed_to?(user, %{ap_id: ap_id}) do
+  def subscribed_to?(%User{} = user, %User{} = target) do
+    # Note: the relationship is inverse: subscriber acts as relationship target
+    UserRelationship.inverse_subscription_exists?(target, user)
+  end
+
+  def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
     with %User{} = target <- get_cached_by_ap_id(ap_id) do
-      Enum.member?(target.subscribers, user.ap_id)
+      subscribed_to?(user, target)
     end
   end
 
-  @spec muted_users(User.t()) :: [User.t()]
-  def muted_users(user) do
-    User.Query.build(%{ap_id: user.mutes, deactivated: false})
-    |> Repo.all()
-  end
+  @doc """
+  Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
+  E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
+  """
+  @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
+  def outgoing_relations_ap_ids(_, []), do: %{}
 
-  @spec blocked_users(User.t()) :: [User.t()]
-  def blocked_users(user) do
-    User.Query.build(%{ap_id: user.blocks, deactivated: false})
-    |> Repo.all()
-  end
+  def outgoing_relations_ap_ids(%User{} = user, relationship_types)
+      when is_list(relationship_types) do
+    db_result =
+      user
+      |> assoc(:outgoing_relationships)
+      |> join(:inner, [user_rel], u in assoc(user_rel, :target))
+      |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
+      |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
+      |> group_by([user_rel, u], user_rel.relationship_type)
+      |> Repo.all()
+      |> Enum.into(%{}, fn [k, v] -> {k, v} end)
 
-  @spec subscribers(User.t()) :: [User.t()]
-  def subscribers(user) do
-    User.Query.build(%{ap_id: user.subscribers, deactivated: false})
-    |> Repo.all()
+    Enum.into(
+      relationship_types,
+      %{},
+      fn rel_type -> {rel_type, db_result[rel_type] || []} end
+    )
   end
 
   def deactivate_async(user, status \\ true) do
@@ -1099,20 +1218,9 @@ defmodule Pleroma.User do
   end
 
   def update_notification_settings(%User{} = user, settings) do
-    settings =
-      settings
-      |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
-      |> Map.new()
-
-    notification_settings =
-      user.notification_settings
-      |> Map.merge(settings)
-      |> Map.take(["followers", "follows", "non_follows", "non_followers"])
-
-    params = %{notification_settings: notification_settings}
-
     user
-    |> cast(params, [:notification_settings])
+    |> cast(%{notification_settings: settings}, [])
+    |> cast_embed(:notification_settings)
     |> validate_required([:notification_settings])
     |> update_and_set_cache()
   end
@@ -1171,7 +1279,7 @@ defmodule Pleroma.User do
       blocked_identifiers,
       fn blocked_identifier ->
         with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
-             {:ok, blocker} <- block(blocker, blocked),
+             {:ok, _user_block} <- block(blocker, blocked),
              {:ok, _} <- ActivityPub.block(blocker, blocked) do
           blocked
         else
@@ -1485,7 +1593,7 @@ defmodule Pleroma.User do
   end
 
   def showing_reblogs?(%User{} = user, %User{} = target) do
-    target.ap_id not in user.muted_reblogs
+    not UserRelationship.reblog_mute_exists?(user, target)
   end
 
   @doc """
@@ -1823,23 +1931,6 @@ defmodule Pleroma.User do
     |> update_and_set_cache()
   end
 
-  defp set_subscribers(user, subscribers) do
-    params = %{subscribers: subscribers}
-
-    user
-    |> cast(params, [:subscribers])
-    |> validate_required([:subscribers])
-    |> update_and_set_cache()
-  end
-
-  def add_to_subscribers(user, subscribed) do
-    set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
-  end
-
-  def remove_from_subscribers(user, subscribed) do
-    set_subscribers(user, List.delete(user.subscribers, subscribed))
-  end
-
   defp set_domain_blocks(user, domain_blocks) do
     params = %{domain_blocks: domain_blocks}
 
@@ -1857,81 +1948,35 @@ defmodule Pleroma.User do
     set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
   end
 
-  defp set_blocks(user, blocks) do
-    params = %{blocks: blocks}
-
-    user
-    |> cast(params, [:blocks])
-    |> validate_required([:blocks])
-    |> update_and_set_cache()
-  end
-
-  def add_to_block(user, blocked) do
-    set_blocks(user, Enum.uniq([blocked | user.blocks]))
-  end
-
-  def remove_from_block(user, blocked) do
-    set_blocks(user, List.delete(user.blocks, blocked))
+  @spec add_to_block(User.t(), User.t()) ::
+          {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
+  defp add_to_block(%User{} = user, %User{} = blocked) do
+    UserRelationship.create_block(user, blocked)
   end
 
-  defp set_mutes(user, mutes) do
-    params = %{mutes: mutes}
-
-    user
-    |> cast(params, [:mutes])
-    |> validate_required([:mutes])
-    |> update_and_set_cache()
+  @spec add_to_block(User.t(), User.t()) ::
+          {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
+  defp remove_from_block(%User{} = user, %User{} = blocked) do
+    UserRelationship.delete_block(user, blocked)
   end
 
-  def add_to_mutes(user, muted, notifications?) do
-    with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
-      set_notification_mutes(
-        user,
-        Enum.uniq([muted | user.muted_notifications]),
-        notifications?
-      )
+  defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
+    with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
+         {:ok, user_notification_mute} <-
+           (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
+             {:ok, nil} do
+      {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
     end
   end
 
-  def remove_from_mutes(user, muted) do
-    with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
-      set_notification_mutes(
-        user,
-        List.delete(user.muted_notifications, muted),
-        true
-      )
+  defp remove_from_mutes(user, %User{} = muted_user) do
+    with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
+         {:ok, user_notification_mute} <-
+           UserRelationship.delete_notification_mute(user, muted_user) do
+      {:ok, [user_mute, user_notification_mute]}
     end
   end
 
-  defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
-    {:ok, user}
-  end
-
-  defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
-    params = %{muted_notifications: muted_notifications}
-
-    user
-    |> cast(params, [:muted_notifications])
-    |> validate_required([:muted_notifications])
-    |> update_and_set_cache()
-  end
-
-  def add_reblog_mute(user, ap_id) do
-    params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
-
-    user
-    |> cast(params, [:muted_reblogs])
-    |> update_and_set_cache()
-  end
-
-  def remove_reblog_mute(user, ap_id) do
-    params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
-
-    user
-    |> cast(params, [:muted_reblogs])
-    |> update_and_set_cache()
-  end
-
   def set_invisible(user, invisible) do
     params = %{invisible: invisible}
 
diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex
new file mode 100644 (file)
index 0000000..f089961
--- /dev/null
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.NotificationSetting do
+  use Ecto.Schema
+  import Ecto.Changeset
+
+  @derive Jason.Encoder
+  @primary_key false
+
+  embedded_schema do
+    field(:followers, :boolean, default: true)
+    field(:follows, :boolean, default: true)
+    field(:non_follows, :boolean, default: true)
+    field(:non_followers, :boolean, default: true)
+    field(:privacy_option, :boolean, default: false)
+  end
+
+  def changeset(schema, params) do
+    schema
+    |> cast(prepare_attrs(params), [
+      :followers,
+      :follows,
+      :non_follows,
+      :non_followers,
+      :privacy_option
+    ])
+  end
+
+  defp prepare_attrs(params) do
+    Enum.reduce(params, %{}, fn
+      {k, v}, acc when is_binary(v) ->
+        Map.put(acc, k, String.downcase(v))
+
+      {k, v}, acc ->
+        Map.put(acc, k, v)
+    end)
+  end
+end
index b1bb9d4da0aa65ad14c14a1fe131f88380ecdc19..6b55df483b76944ba6276f25033ea30f76841628 100644 (file)
@@ -103,9 +103,13 @@ defmodule Pleroma.User.Search do
     from(q in query, where: q.invisible == false)
   end
 
-  defp filter_blocked_user(query, %User{blocks: blocks})
-       when length(blocks) > 0 do
-    from(q in query, where: not (q.ap_id in ^blocks))
+  defp filter_blocked_user(query, %User{} = blocker) do
+    query
+    |> join(:left, [u], b in Pleroma.UserRelationship,
+      as: :blocks,
+      on: b.relationship_type == ^:block and b.source_id == ^blocker.id and u.id == b.target_id
+    )
+    |> where([blocks: b], is_nil(b.target_id))
   end
 
   defp filter_blocked_user(query, _), do: query
diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex
new file mode 100644 (file)
index 0000000..24c7245
--- /dev/null
@@ -0,0 +1,92 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.UserRelationship do
+  use Ecto.Schema
+
+  import Ecto.Changeset
+  import Ecto.Query
+
+  alias Pleroma.Repo
+  alias Pleroma.User
+  alias Pleroma.UserRelationship
+
+  schema "user_relationships" do
+    belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
+    belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
+    field(:relationship_type, UserRelationshipTypeEnum)
+
+    timestamps(updated_at: false)
+  end
+
+  for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
+    # Definitions of `create_block/2`, `create_mute/2` etc.
+    def unquote(:"create_#{relationship_type}")(source, target),
+      do: create(unquote(relationship_type), source, target)
+
+    # Definitions of `delete_block/2`, `delete_mute/2` etc.
+    def unquote(:"delete_#{relationship_type}")(source, target),
+      do: delete(unquote(relationship_type), source, target)
+
+    # Definitions of `block_exists?/2`, `mute_exists?/2` etc.
+    def unquote(:"#{relationship_type}_exists?")(source, target),
+      do: exists?(unquote(relationship_type), source, target)
+  end
+
+  def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
+    user_relationship
+    |> cast(params, [:relationship_type, :source_id, :target_id])
+    |> validate_required([:relationship_type, :source_id, :target_id])
+    |> unique_constraint(:relationship_type,
+      name: :user_relationships_source_id_relationship_type_target_id_index
+    )
+    |> validate_not_self_relationship()
+  end
+
+  def exists?(relationship_type, %User{} = source, %User{} = target) do
+    UserRelationship
+    |> where(relationship_type: ^relationship_type, source_id: ^source.id, target_id: ^target.id)
+    |> Repo.exists?()
+  end
+
+  def create(relationship_type, %User{} = source, %User{} = target) do
+    %UserRelationship{}
+    |> changeset(%{
+      relationship_type: relationship_type,
+      source_id: source.id,
+      target_id: target.id
+    })
+    |> Repo.insert(
+      on_conflict: :replace_all_except_primary_key,
+      conflict_target: [:source_id, :relationship_type, :target_id]
+    )
+  end
+
+  def delete(relationship_type, %User{} = source, %User{} = target) do
+    attrs = %{relationship_type: relationship_type, source_id: source.id, target_id: target.id}
+
+    case Repo.get_by(UserRelationship, attrs) do
+      %UserRelationship{} = existing_record -> Repo.delete(existing_record)
+      nil -> {:ok, nil}
+    end
+  end
+
+  defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
+    changeset
+    |> validate_change(:target_id, fn _, target_id ->
+      if target_id == get_field(changeset, :source_id) do
+        [target_id: "can't be equal to source_id"]
+      else
+        []
+      end
+    end)
+    |> validate_change(:source_id, fn _, source_id ->
+      if source_id == get_field(changeset, :target_id) do
+        [source_id: "can't be equal to target_id"]
+      else
+        []
+      end
+    end)
+  end
+end
index d6a425d8bd0f6628e4a1ec606402806c4fea89d3..1e2cc2e2b9ee6219c38ad8fe8a44b47cbae175e5 100644 (file)
@@ -456,17 +456,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     user = User.get_cached_by_ap_id(actor)
     to = (object.data["to"] || []) ++ (object.data["cc"] || [])
 
-    with {:ok, object, activity} <- Object.delete(object),
+    with create_activity <- Activity.get_create_by_object_ap_id(id),
          data <-
            %{
              "type" => "Delete",
              "actor" => actor,
              "object" => id,
              "to" => to,
-             "deleted_activity_id" => activity && activity.id
+             "deleted_activity_id" => create_activity && create_activity.id
            }
            |> maybe_put("id", activity_id),
          {:ok, activity} <- insert(data, local, false),
+         {:ok, object, _create_activity} <- Object.delete(object),
          stream_out_participations(object, user),
          _ <- decrease_replies_count_if_reply(object),
          {:ok, _actor} <- decrease_note_count_if_public(user, object),
@@ -748,6 +749,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
       |> Map.put("whole_db", true)
       |> Map.put("pinned_activity_ids", user.pinned_activities)
 
+    params =
+      if User.blocks?(reading_user, user) do
+        params
+      else
+        params
+        |> Map.put("blocking_user", reading_user)
+        |> Map.put("muting_user", reading_user)
+      end
+
     recipients =
       user_activities_recipients(%{
         "godmode" => params["godmode"],
@@ -919,7 +929,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
 
   defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
-    mutes = user.mutes
+    mutes = opts["muted_users_ap_ids"] || User.muted_users_ap_ids(user)
 
     query =
       from([activity] in query,
@@ -936,8 +946,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_muted(query, _), do: query
 
-  defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do
-    blocks = user.blocks || []
+  defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do
+    blocked_ap_ids = opts["blocked_users_ap_ids"] || User.blocked_users_ap_ids(user)
     domain_blocks = user.domain_blocks || []
 
     query =
@@ -945,14 +955,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
     from(
       [activity, object: o] in query,
-      where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
-      where: fragment("not (? && ?)", activity.recipients, ^blocks),
+      where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
+      where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids),
       where:
         fragment(
           "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
           activity.data,
           activity.data,
-          ^blocks
+          ^blocked_ap_ids
         ),
       where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
       where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
@@ -979,8 +989,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_pinned(query, _), do: query
 
-  defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user}) do
-    muted_reblogs = user.muted_reblogs || []
+  defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user} = opts) do
+    muted_reblogs = opts["reblog_muted_users_ap_ids"] || User.reblog_muted_users_ap_ids(user)
 
     from(
       activity in query,
@@ -1061,7 +1071,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp maybe_order(query, _), do: query
 
+  defp fetch_activities_query_ap_ids_ops(opts) do
+    source_user = opts["muting_user"]
+    ap_id_relations = if source_user, do: [:mute, :reblog_mute], else: []
+
+    ap_id_relations =
+      ap_id_relations ++
+        if opts["blocking_user"] && opts["blocking_user"] == source_user do
+          [:block]
+        else
+          []
+        end
+
+    preloaded_ap_ids = User.outgoing_relations_ap_ids(source_user, ap_id_relations)
+
+    restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts)
+    restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts)
+
+    restrict_muted_reblogs_opts =
+      Map.merge(%{"reblog_muted_users_ap_ids" => preloaded_ap_ids[:reblog_mute]}, opts)
+
+    {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts}
+  end
+
   def fetch_activities_query(recipients, opts \\ %{}) do
+    {restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
+      fetch_activities_query_ap_ids_ops(opts)
+
     config = %{
       skip_thread_containment: Config.get([:instance, :skip_thread_containment])
     }
@@ -1081,15 +1117,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_type(opts)
     |> restrict_state(opts)
     |> restrict_favorited_by(opts)
-    |> restrict_blocked(opts)
-    |> restrict_muted(opts)
+    |> restrict_blocked(restrict_blocked_opts)
+    |> restrict_muted(restrict_muted_opts)
     |> restrict_media(opts)
     |> restrict_visibility(opts)
     |> restrict_thread_visibility(opts, config)
     |> restrict_replies(opts)
     |> restrict_reblogs(opts)
     |> restrict_pinned(opts)
-    |> restrict_muted_reblogs(opts)
+    |> restrict_muted_reblogs(restrict_muted_reblogs_opts)
     |> restrict_instance(opts)
     |> Activity.restrict_deactivated_users()
     |> exclude_poll_votes(opts)
index ce95fb6babf84a0c7ac38359c7bae335f2dea5aa..ecba27bef439060af70ed417d4f080b1af9a56cf 100644 (file)
@@ -387,7 +387,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def handle_incoming(%{"id" => nil}, _options), do: :error
   def handle_incoming(%{"id" => ""}, _options), do: :error
   # length of https:// = 8, should validate better, but good enough for now.
-  def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8),
+  def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
     do: :error
 
   # TODO: validate those with a Ecto scheme
index 01aacbde3fbac90d68f08d12ed0497383d4ceb22..2ca805c091ab25c9340e05782750844b1ff6d711 100644 (file)
@@ -722,16 +722,22 @@ defmodule Pleroma.Web.ActivityPub.Utils do
         act when is_binary(act) -> act
       end
 
-    activity = Activity.get_by_ap_id_with_object(id)
-    actor = User.get_by_ap_id(activity.object.data["actor"])
+    case Activity.get_by_ap_id_with_object(id) do
+      %Activity{} = activity ->
+        %{
+          "type" => "Note",
+          "id" => activity.data["id"],
+          "content" => activity.object.data["content"],
+          "published" => activity.object.data["published"],
+          "actor" =>
+            AccountView.render("show.json", %{
+              user: User.get_by_ap_id(activity.object.data["actor"])
+            })
+        }
 
-    %{
-      "type" => "Note",
-      "id" => activity.data["id"],
-      "content" => activity.object.data["content"],
-      "published" => activity.object.data["published"],
-      "actor" => AccountView.render("show.json", %{user: actor})
-    }
+      _ ->
+        %{"id" => id, "deleted" => true}
+    end
   end
 
   defp build_flag_object(_), do: []
@@ -788,63 +794,76 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     ActivityPub.fetch_activities([], params, :offset)
   end
 
-  @spec get_reports_grouped_by_status(%{required(:activity) => String.t()}) :: %{
-          required(:groups) => [
-            %{
-              required(:date) => String.t(),
-              required(:account) => %{},
-              required(:status) => %{},
-              required(:actors) => [%User{}],
-              required(:reports) => [%Activity{}]
-            }
-          ],
-          required(:total) => integer
-        }
-  def get_reports_grouped_by_status(groups) do
-    parsed_groups =
-      groups
-      |> Enum.map(fn entry ->
-        activity =
-          case Jason.decode(entry.activity) do
-            {:ok, activity} -> activity
-            _ -> build_flag_object(entry.activity)
-          end
-
-        parse_report_group(activity)
-      end)
-
-    %{
-      groups: parsed_groups
-    }
-  end
-
   def parse_report_group(activity) do
     reports = get_reports_by_status_id(activity["id"])
     max_date = Enum.max_by(reports, &NaiveDateTime.from_iso8601!(&1.data["published"]))
     actors = Enum.map(reports, & &1.user_actor)
+    [%{data: %{"object" => [account_id | _]}} | _] = reports
+
+    account =
+      AccountView.render("show.json", %{
+        user: User.get_by_ap_id(account_id)
+      })
+
+    status = get_status_data(activity)
 
     %{
       date: max_date.data["published"],
-      account: activity["actor"],
-      status: %{
-        id: activity["id"],
-        content: activity["content"],
-        published: activity["published"]
-      },
+      account: account,
+      status: status,
       actors: Enum.uniq(actors),
       reports: reports
     }
   end
 
+  defp get_status_data(status) do
+    case status["deleted"] do
+      true ->
+        %{
+          "id" => status["id"],
+          "deleted" => true
+        }
+
+      _ ->
+        Activity.get_by_ap_id(status["id"])
+    end
+  end
+
   def get_reports_by_status_id(ap_id) do
     from(a in Activity,
       where: fragment("(?)->>'type' = 'Flag'", a.data),
-      where: fragment("(?)->'object' @> ?", a.data, ^[%{id: ap_id}])
+      where: fragment("(?)->'object' @> ?", a.data, ^[%{id: ap_id}]),
+      or_where: fragment("(?)->'object' @> ?", a.data, ^[ap_id])
     )
     |> Activity.with_preloaded_user_actor()
     |> Repo.all()
   end
 
+  @spec get_reports_grouped_by_status([String.t()]) :: %{
+          required(:groups) => [
+            %{
+              required(:date) => String.t(),
+              required(:account) => %{},
+              required(:status) => %{},
+              required(:actors) => [%User{}],
+              required(:reports) => [%Activity{}]
+            }
+          ]
+        }
+  def get_reports_grouped_by_status(activity_ids) do
+    parsed_groups =
+      activity_ids
+      |> Enum.map(fn id ->
+        id
+        |> build_flag_object()
+        |> parse_report_group()
+      end)
+
+    %{
+      groups: parsed_groups
+    }
+  end
+
   @spec get_reported_activities() :: [
           %{
             required(:activity) => String.t(),
@@ -852,17 +871,23 @@ defmodule Pleroma.Web.ActivityPub.Utils do
           }
         ]
   def get_reported_activities do
-    from(a in Activity,
-      where: fragment("(?)->>'type' = 'Flag'", a.data),
+    reported_activities_query =
+      from(a in Activity,
+        where: fragment("(?)->>'type' = 'Flag'", a.data),
+        select: %{
+          activity: fragment("jsonb_array_elements((? #- '{object,0}')->'object')", a.data)
+        },
+        group_by: fragment("activity")
+      )
+
+    from(a in subquery(reported_activities_query),
+      distinct: true,
       select: %{
-        date: fragment("max(?->>'published') date", a.data),
-        activity:
-          fragment("jsonb_array_elements_text((? #- '{object,0}')->'object') activity", a.data)
-      },
-      group_by: fragment("activity"),
-      order_by: fragment("date DESC")
+        id: fragment("COALESCE(?->>'id'::text, ? #>> '{}')", a.activity, a.activity)
+      }
     )
     |> Repo.all()
+    |> Enum.map(& &1.id)
   end
 
   def update_report_state(%Activity{} = activity, state)
index 0a63f3fe6c2761d1845e366a12cf8e0f3fa2b2d1..0a8a56cd8950b80faff6e88ba4ba54eaf93df99e 100644 (file)
@@ -649,11 +649,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   end
 
   def list_grouped_reports(conn, _params) do
-    reports = Utils.get_reported_activities()
+    statuses = Utils.get_reported_activities()
 
     conn
     |> put_view(ReportView)
-    |> render("index_grouped.json", Utils.get_reports_grouped_by_status(reports))
+    |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses))
   end
 
   def report_show(conn, %{"id" => id}) do
index ca88595c722054c4cb1155aca19aea52c5bb1584..13602efd9f4d9612b6f6787f3226e8f77c63a5a1 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.AdminAPI.ReportView do
   use Pleroma.Web, :view
+  alias Pleroma.Activity
   alias Pleroma.HTML
   alias Pleroma.User
   alias Pleroma.Web.AdminAPI.Report
@@ -45,10 +46,16 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
   def render("index_grouped.json", %{groups: groups}) do
     reports =
       Enum.map(groups, fn group ->
+        status =
+          case group.status do
+            %Activity{} = activity -> StatusView.render("show.json", %{activity: activity})
+            _ -> group.status
+          end
+
         %{
           date: group[:date],
           account: group[:account],
-          status: group[:status],
+          status: Map.put_new(status, "deleted", false),
           actors: Enum.map(group[:actors], &merge_account_views/1),
           reports:
             group[:reports]
index 08841a3e8ca52faf749a51f4cc08761807bfbe4f..840414933463d2d74482ec48b29b62de0402cded 100644 (file)
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.ChatChannel do
   def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} = socket) do
     text = String.trim(text)
 
-    if String.length(text) > 0 do
+    if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do
       author = User.get_cached_by_nickname(user_name)
       author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author)
       message = ChatChannelState.add_message(%{text: text, author: author})
index fe6e26a90ab6dee48c35df65acfe1f16670638fa..2f3bcfc3c979cd24af1070a7138230c009e0d3b5 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.CommonAPI do
   alias Pleroma.Object
   alias Pleroma.ThreadMute
   alias Pleroma.User
+  alias Pleroma.UserRelationship
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
@@ -32,7 +33,7 @@ defmodule Pleroma.Web.CommonAPI do
   def unfollow(follower, unfollowed) do
     with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
          {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
-         {:ok, _unfollowed} <- User.unsubscribe(follower, unfollowed) do
+         {:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
       {:ok, follower}
     end
   end
@@ -420,15 +421,11 @@ defmodule Pleroma.Web.CommonAPI do
 
   defp set_visibility(activity, _), do: {:ok, activity}
 
-  def hide_reblogs(user, %{ap_id: ap_id} = _muted) do
-    if ap_id not in user.muted_reblogs do
-      User.add_reblog_mute(user, ap_id)
-    end
+  def hide_reblogs(%User{} = user, %User{} = target) do
+    UserRelationship.create_reblog_mute(user, target)
   end
 
-  def show_reblogs(user, %{ap_id: ap_id} = _muted) do
-    if ap_id in user.muted_reblogs do
-      User.remove_reblog_mute(user, ap_id)
-    end
+  def show_reblogs(%User{} = user, %User{} = target) do
+    UserRelationship.delete_reblog_mute(user, target)
   end
 end
index cbb64f8d24f1c3cd2b2d4e4d8e96791504247a57..a9b164d9ac88a177d0e513aa31f1bb64ee427629 100644 (file)
@@ -494,7 +494,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
     with %User{} = user <- User.get_cached_by_ap_id(actor) do
       subscriber_ids =
         user
-        |> User.subscribers()
+        |> User.subscriber_users()
         |> Enum.filter(&Visibility.visible_for_user?(activity, &1))
         |> Enum.map(& &1.ap_id)
 
index 49735b5c2f858fd38e0cb8aa13baa2b7a7e9de1e..bbea31682c1f6533eb95f53011774df6f6c8f0cc 100644 (file)
@@ -61,14 +61,7 @@ defmodule Pleroma.Web.Endpoint do
   plug(Plug.RequestId)
   plug(Plug.Logger)
 
-  plug(
-    Plug.Parsers,
-    parsers: [:urlencoded, :multipart, :json],
-    pass: ["*/*"],
-    json_decoder: Jason,
-    length: Pleroma.Config.get([:instance, :upload_limit]),
-    body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
-  )
+  plug(Pleroma.Plugs.Parsers)
 
   plug(Plug.MethodOverride)
   plug(Plug.Head)
index a69423f60eda71e54005b70c058136cc7ad0b925..d19029cb54ef827fb25e83b1c4abc5c196b0a24c 100644 (file)
@@ -249,7 +249,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   @doc "GET /api/v1/accounts/:id/statuses"
   def statuses(%{assigns: %{user: reading_user}} = conn, params) do
     with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
-      params = Map.put(params, "tag", params["tagged"])
+      params =
+        params
+        |> Map.put("tag", params["tagged"])
+        |> Map.delete("godmode")
+
       activities = ActivityPub.fetch_user_activities(user, reading_user, params)
 
       conn
@@ -324,7 +328,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
     notifications? = params |> Map.get("notifications", true) |> truthy_param?()
 
-    with {:ok, muter} <- User.mute(muter, muted, notifications?) do
+    with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do
       render(conn, "relationship.json", user: muter, target: muted)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
@@ -333,7 +337,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "POST /api/v1/accounts/:id/unmute"
   def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
-    with {:ok, muter} <- User.unmute(muter, muted) do
+    with {:ok, _user_relationships} <- User.unmute(muter, muted) do
       render(conn, "relationship.json", user: muter, target: muted)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
@@ -342,7 +346,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "POST /api/v1/accounts/:id/block"
   def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
-    with {:ok, blocker} <- User.block(blocker, blocked),
+    with {:ok, _user_block} <- User.block(blocker, blocked),
          {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
       render(conn, "relationship.json", user: blocker, target: blocked)
     else
@@ -352,7 +356,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "POST /api/v1/accounts/:id/unblock"
   def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
-    with {:ok, blocker} <- User.unblock(blocker, blocked),
+    with {:ok, _user_block} <- User.unblock(blocker, blocked),
          {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
       render(conn, "relationship.json", user: blocker, target: blocked)
     else
@@ -374,12 +378,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "GET /api/v1/mutes"
   def mutes(%{assigns: %{user: user}} = conn, _) do
-    render(conn, "index.json", users: User.muted_users(user), for: user, as: :user)
+    users = User.muted_users(user, _restrict_deactivated = true)
+    render(conn, "index.json", users: users, for: user, as: :user)
   end
 
   @doc "GET /api/v1/blocks"
   def blocks(%{assigns: %{user: user}} = conn, _) do
-    render(conn, "index.json", users: User.blocked_users(user), for: user, as: :user)
+    users = User.blocked_users(user, _restrict_deactivated = true)
+    render(conn, "index.json", users: users, for: user, as: :user)
   end
 
   @doc "GET /api/v1/endorsements"
index d875a578840b4bfa6ec3af391c537aea05f7378c..b1816370ef3c996eee077681d5aeec6afdafb114 100644 (file)
@@ -24,19 +24,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
 
     with {:ok, follower, _followed, _} <- result do
       options = cast_params(params)
-
-      case reblogs_visibility(options[:reblogs], result) do
-        {:ok, follower} -> {:ok, follower}
-        _ -> {:ok, follower}
-      end
+      set_reblogs_visibility(options[:reblogs], result)
+      {:ok, follower}
     end
   end
 
-  defp reblogs_visibility(false, {:ok, follower, followed, _}) do
+  defp set_reblogs_visibility(false, {:ok, follower, followed, _}) do
     CommonAPI.hide_reblogs(follower, followed)
   end
 
-  defp reblogs_visibility(_, {:ok, follower, followed, _}) do
+  defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do
     CommonAPI.show_reblogs(follower, followed)
   end
 
@@ -73,7 +70,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
       exclude_types: {:array, :string},
       exclude_visibilities: {:array, :string},
       reblogs: :boolean,
-      with_muted: :boolean
+      with_muted: :boolean,
+      with_move: :boolean
     }
 
     changeset = cast({%{}, param_types}, params, Map.keys(param_types))
index ec720e472e20bd368c80cf79fa82922239bf5a50..546cc0ed5daf362076dbdae731f31d1d424cb36e 100644 (file)
@@ -50,8 +50,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       id: to_string(target.id),
       following: User.following?(user, target),
       followed_by: User.following?(target, user),
-      blocking: User.blocks_ap_id?(user, target),
-      blocked_by: User.blocks_ap_id?(target, user),
+      blocking: User.blocks_user?(user, target),
+      blocked_by: User.blocks_user?(target, user),
       muting: User.mutes?(user, target),
       muting_notifications: User.muted_notifications?(user, target),
       subscribing: User.subscribed_to?(user, target),
index f639f9c6fd202c38ea753c9336192700df93ddc2..3c9c580d51f1870ed5810709fe477f7f0726faea 100644 (file)
@@ -11,11 +11,6 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do
   @ten_seconds 10_000
   @one_day 86_400_000
 
-  @interval Pleroma.Config.get(
-              [:oauth2, :clean_expired_tokens_interval],
-              @one_day
-            )
-
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Workers.BackgroundWorker
 
@@ -29,8 +24,9 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do
   @doc false
   def handle_info(:perform, state) do
     BackgroundWorker.enqueue("clean_expired_tokens", %{})
+    interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day)
 
-    Process.send_after(self(), :perform, @interval)
+    Process.send_after(self(), :perform, interval)
     {:noreply, state}
   end
 
index bc2f1017c83fb52f9a3b5e1e025f835d9c2fcc99..773cd9a9743ac81585dfc5461ca9e2006a95a25a 100644 (file)
@@ -144,7 +144,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
 
   @doc "POST /api/v1/pleroma/accounts/:id/subscribe"
   def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
-    with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do
+    with {:ok, _subscription} <- User.subscribe(user, subscription_target) do
       render(conn, "relationship.json", user: user, target: subscription_target)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
@@ -153,7 +153,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
 
   @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe"
   def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
-    with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do
+    with {:ok, _subscription} <- User.unsubscribe(user, subscription_target) do
       render(conn, "relationship.json", user: user, target: subscription_target)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
index a6a924d02f067191ddda7e157ffbb40209fd2f12..34ec1d8d967493140032966f90b80602cb334222 100644 (file)
@@ -22,8 +22,8 @@ defmodule Pleroma.Web.Push.Impl do
   @spec perform(Notification.t()) :: list(any) | :error
   def perform(
         %{
-          activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
-          user_id: user_id
+          activity: %{data: %{"type" => activity_type}} = activity,
+          user: %User{id: user_id}
         } = notif
       )
       when activity_type in @types do
@@ -39,18 +39,17 @@ defmodule Pleroma.Web.Push.Impl do
     for subscription <- fetch_subsriptions(user_id),
         get_in(subscription.data, ["alerts", type]) do
       %{
-        title: format_title(notif),
         access_token: subscription.token.token,
-        body: format_body(notif, actor, object),
         notification_id: notif.id,
         notification_type: type,
         icon: avatar_url,
         preferred_locale: "en",
         pleroma: %{
-          activity_id: activity_id,
+          activity_id: notif.activity.id,
           direct_conversation_id: direct_conversation_id
         }
       }
+      |> Map.merge(build_content(notif, actor, object))
       |> Jason.encode!()
       |> push_message(build_sub(subscription), gcm_api_key, subscription)
     end
@@ -100,6 +99,24 @@ defmodule Pleroma.Web.Push.Impl do
     }
   end
 
+  def build_content(
+        %{
+          activity: %{data: %{"directMessage" => true}},
+          user: %{notification_settings: %{privacy_option: true}}
+        },
+        actor,
+        _
+      ) do
+    %{title: "New Direct Message", body: "@#{actor.nickname}"}
+  end
+
+  def build_content(notif, actor, object) do
+    %{
+      title: format_title(notif),
+      body: format_body(notif, actor, object)
+    }
+  end
+
   def format_body(
         %{activity: %{data: %{"type" => "Create"}}},
         actor,
index 33b24840d1794c17625c03507f6667c68f17fb35..a1b445f2f131dfad9f9973aadfb40d9c6d15307e 100644 (file)
@@ -129,16 +129,17 @@ defmodule Pleroma.Web.Streamer.Worker do
   end
 
   defp should_send?(%User{} = user, %Activity{} = item) do
-    blocks = user.blocks || []
-    mutes = user.mutes || []
-    reblog_mutes = user.muted_reblogs || []
-    recipient_blocks = MapSet.new(blocks ++ mutes)
+    %{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
+      User.outgoing_relations_ap_ids(user, [:block, :mute, :reblog_mute])
+
+    recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids)
     recipients = MapSet.new(item.recipients)
     domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
 
     with parent <- Object.normalize(item) || item,
-         true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
-         true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
+         true <-
+           Enum.all?([blocked_ap_ids, muted_ap_ids, reblog_muted_ap_ids], &(item.actor not in &1)),
+         true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
          true <- MapSet.disjoint?(recipients, recipient_blocks),
          %{host: item_host} <- URI.parse(item.actor),
          %{host: parent_host} <- URI.parse(parent.data["actor"]),
index 61b451e3ed99aecd8fb7bdee08c084b0138bf74f..a978c4013d03837dca7fc24cec2166e1f6ff0ec7 100644 (file)
@@ -13,7 +13,7 @@ defmodule Pleroma.Workers.WebPusherWorker do
     notification =
       Notification
       |> Repo.get(notification_id)
-      |> Repo.preload([:activity])
+      |> Repo.preload([:activity, :user])
 
     Pleroma.Web.Push.Impl.perform(notification)
   end
diff --git a/mix.exs b/mix.exs
index eb2e54e4ab0575afff184726cfbf55e8b017766a..7c8e52a676f35b77f1deaf71749f32b396fea8d3 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -100,6 +100,7 @@ defmodule Pleroma.Mixfile do
       {:plug_cowboy, "~> 2.0"},
       {:phoenix_pubsub, "~> 1.1"},
       {:phoenix_ecto, "~> 4.0"},
+      {:ecto_enum, "~> 1.4"},
       {:ecto_sql, "~> 3.2"},
       {:postgrex, ">= 0.13.5"},
       {:oban, "~> 0.12.0"},
index 49bdad128b9d75f46e63b56df6020f1208ccb196..b008f2b55dd9821eebba3ca0d1dadf172f2b27f4 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -24,6 +24,7 @@
   "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
   "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
   "ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
+  "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
   "ecto_sql": {:hex, :ecto_sql, "3.2.2", "d10845bc147b9f61ef485cbf0973c0a337237199bd9bd30dd9542db00aadc26b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0 or ~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
   "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
   "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"},
diff --git a/priv/repo/migrations/20191118084425_create_user_relationships.exs b/priv/repo/migrations/20191118084425_create_user_relationships.exs
new file mode 100644 (file)
index 0000000..c281f88
--- /dev/null
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.CreateUserRelationships do
+  use Ecto.Migration
+
+  def change do
+    create_if_not_exists table(:user_relationships) do
+      add(:source_id, references(:users, type: :uuid, on_delete: :delete_all))
+      add(:target_id, references(:users, type: :uuid, on_delete: :delete_all))
+      add(:relationship_type, :integer, null: false)
+
+      timestamps(updated_at: false)
+    end
+
+    create_if_not_exists(
+      unique_index(:user_relationships, [:source_id, :relationship_type, :target_id])
+    )
+  end
+end
diff --git a/priv/repo/migrations/20191118084500_data_migration_populate_user_relationships.exs b/priv/repo/migrations/20191118084500_data_migration_populate_user_relationships.exs
new file mode 100644 (file)
index 0000000..990e9f3
--- /dev/null
@@ -0,0 +1,68 @@
+defmodule Pleroma.Repo.Migrations.DataMigrationPopulateUserRelationships do
+  use Ecto.Migration
+
+  alias Ecto.Adapters.SQL
+  alias Pleroma.Repo
+
+  require Logger
+
+  def up do
+    Enum.each(
+      [blocks: 1, mutes: 2, muted_reblogs: 3, muted_notifications: 4, subscribers: 5],
+      fn {field, relationship_type_code} ->
+        migrate(field, relationship_type_code)
+
+        if field == :subscribers do
+          drop_if_exists(index(:users, [:subscribers]))
+        end
+      end
+    )
+  end
+
+  def down, do: :noop
+
+  defp migrate(field, relationship_type_code) do
+    Logger.info("Processing users.#{field}...")
+
+    {:ok, %{rows: field_rows}} =
+      SQL.query(Repo, "SELECT id, #{field} FROM users WHERE #{field} != '{}'")
+
+    target_ap_ids =
+      Enum.flat_map(
+        field_rows,
+        fn [_, ap_ids] -> ap_ids end
+      )
+      |> Enum.uniq()
+
+    # Selecting ids of all targets at once in order to reduce the number of SELECT queries
+    {:ok, %{rows: target_ap_id_id}} =
+      SQL.query(Repo, "SELECT ap_id, id FROM users WHERE ap_id = ANY($1)", [target_ap_ids])
+
+    target_id_by_ap_id = Enum.into(target_ap_id_id, %{}, fn [k, v] -> {k, v} end)
+
+    Enum.each(
+      field_rows,
+      fn [source_id, target_ap_ids] ->
+        source_uuid = Ecto.UUID.cast!(source_id)
+
+        for target_ap_id <- target_ap_ids do
+          target_id = target_id_by_ap_id[target_ap_id]
+
+          with {:ok, target_uuid} <- target_id && Ecto.UUID.cast(target_id) do
+            execute("""
+            INSERT INTO user_relationships(
+              source_id, target_id, relationship_type, inserted_at
+            )
+            VALUES(
+              '#{source_uuid}'::uuid, '#{target_uuid}'::uuid, #{relationship_type_code}, now()
+            )
+            ON CONFLICT (source_id, relationship_type, target_id) DO NOTHING
+            """)
+          else
+            _ -> Logger.warn("Unresolved #{field} reference: (#{source_uuid}, #{target_id})")
+          end
+        end
+      end
+    )
+  end
+end
diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex
new file mode 100644 (file)
index 0000000..ea0480d
--- /dev/null
@@ -0,0 +1,93 @@
+defmodule Pleroma.HTML.Scrubber.Default do
+  @doc "The default HTML scrubbing policy: no "
+
+  require FastSanitize.Sanitizer.Meta
+  alias FastSanitize.Sanitizer.Meta
+
+  # credo:disable-for-previous-line
+  # No idea how to fix this one…
+
+  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
+
+  Meta.strip_comments()
+
+  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
+
+  Meta.allow_tag_with_this_attribute_values(:a, "class", [
+    "hashtag",
+    "u-url",
+    "mention",
+    "u-url mention",
+    "mention u-url"
+  ])
+
+  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
+    "tag",
+    "nofollow",
+    "noopener",
+    "noreferrer",
+    "ugc"
+  ])
+
+  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
+
+  Meta.allow_tag_with_these_attributes(:abbr, ["title"])
+
+  Meta.allow_tag_with_these_attributes(:b, [])
+  Meta.allow_tag_with_these_attributes(:blockquote, [])
+  Meta.allow_tag_with_these_attributes(:br, [])
+  Meta.allow_tag_with_these_attributes(:code, [])
+  Meta.allow_tag_with_these_attributes(:del, [])
+  Meta.allow_tag_with_these_attributes(:em, [])
+  Meta.allow_tag_with_these_attributes(:i, [])
+  Meta.allow_tag_with_these_attributes(:li, [])
+  Meta.allow_tag_with_these_attributes(:ol, [])
+  Meta.allow_tag_with_these_attributes(:p, [])
+  Meta.allow_tag_with_these_attributes(:pre, [])
+  Meta.allow_tag_with_these_attributes(:strong, [])
+  Meta.allow_tag_with_these_attributes(:sub, [])
+  Meta.allow_tag_with_these_attributes(:sup, [])
+  Meta.allow_tag_with_these_attributes(:u, [])
+  Meta.allow_tag_with_these_attributes(:ul, [])
+
+  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
+  Meta.allow_tag_with_these_attributes(:span, [])
+
+  @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])
+
+  if @allow_inline_images do
+    # restrict img tags to http/https only, because of MediaProxy.
+    Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
+
+    Meta.allow_tag_with_these_attributes(:img, [
+      "width",
+      "height",
+      "class",
+      "title",
+      "alt"
+    ])
+  end
+
+  if Pleroma.Config.get([:markup, :allow_tables]) do
+    Meta.allow_tag_with_these_attributes(:table, [])
+    Meta.allow_tag_with_these_attributes(:tbody, [])
+    Meta.allow_tag_with_these_attributes(:td, [])
+    Meta.allow_tag_with_these_attributes(:th, [])
+    Meta.allow_tag_with_these_attributes(:thead, [])
+    Meta.allow_tag_with_these_attributes(:tr, [])
+  end
+
+  if Pleroma.Config.get([:markup, :allow_headings]) do
+    Meta.allow_tag_with_these_attributes(:h1, [])
+    Meta.allow_tag_with_these_attributes(:h2, [])
+    Meta.allow_tag_with_these_attributes(:h3, [])
+    Meta.allow_tag_with_these_attributes(:h4, [])
+    Meta.allow_tag_with_these_attributes(:h5, [])
+  end
+
+  if Pleroma.Config.get([:markup, :allow_fonts]) do
+    Meta.allow_tag_with_these_attributes(:font, ["face"])
+  end
+
+  Meta.strip_everything_not_covered()
+end
diff --git a/priv/scrubbers/links_only.ex b/priv/scrubbers/links_only.ex
new file mode 100644 (file)
index 0000000..b30a005
--- /dev/null
@@ -0,0 +1,27 @@
+defmodule Pleroma.HTML.Scrubber.LinksOnly do
+  @moduledoc """
+  An HTML scrubbing policy which limits to links only.
+  """
+
+  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
+
+  require FastSanitize.Sanitizer.Meta
+  alias FastSanitize.Sanitizer.Meta
+
+  Meta.strip_comments()
+
+  # links
+  Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
+
+  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
+    "tag",
+    "nofollow",
+    "noopener",
+    "noreferrer",
+    "me",
+    "ugc"
+  ])
+
+  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
+  Meta.strip_everything_not_covered()
+end
diff --git a/priv/scrubbers/media_proxy.ex b/priv/scrubbers/media_proxy.ex
new file mode 100644 (file)
index 0000000..5dbe576
--- /dev/null
@@ -0,0 +1,32 @@
+defmodule Pleroma.HTML.Transform.MediaProxy do
+  @moduledoc "Transforms inline image URIs to use MediaProxy."
+
+  alias Pleroma.Web.MediaProxy
+
+  def before_scrub(html), do: html
+
+  def scrub_attribute(:img, {"src", "http" <> target}) do
+    media_url =
+      ("http" <> target)
+      |> MediaProxy.url()
+
+    {"src", media_url}
+  end
+
+  def scrub_attribute(_tag, attribute), do: attribute
+
+  def scrub({:img, attributes, children}) do
+    attributes =
+      attributes
+      |> Enum.map(fn attr -> scrub_attribute(:img, attr) end)
+      |> Enum.reject(&is_nil(&1))
+
+    {:img, attributes, children}
+  end
+
+  def scrub({:comment, _text, _children}), do: ""
+
+  def scrub({tag, attributes, children}), do: {tag, attributes, children}
+  def scrub({_tag, children}), do: children
+  def scrub(text), do: text
+end
diff --git a/priv/scrubbers/twitter_text.ex b/priv/scrubbers/twitter_text.ex
new file mode 100644 (file)
index 0000000..c4e796c
--- /dev/null
@@ -0,0 +1,57 @@
+defmodule Pleroma.HTML.Scrubber.TwitterText do
+  @moduledoc """
+  An HTML scrubbing policy which limits to twitter-style text.  Only
+  paragraphs, breaks and links are allowed through the filter.
+  """
+
+  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
+
+  require FastSanitize.Sanitizer.Meta
+  alias FastSanitize.Sanitizer.Meta
+
+  Meta.strip_comments()
+
+  # links
+  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
+
+  Meta.allow_tag_with_this_attribute_values(:a, "class", [
+    "hashtag",
+    "u-url",
+    "mention",
+    "u-url mention",
+    "mention u-url"
+  ])
+
+  Meta.allow_tag_with_this_attribute_values(:a, "rel", [
+    "tag",
+    "nofollow",
+    "noopener",
+    "noreferrer"
+  ])
+
+  Meta.allow_tag_with_these_attributes(:a, ["name", "title"])
+
+  # paragraphs and linebreaks
+  Meta.allow_tag_with_these_attributes(:br, [])
+  Meta.allow_tag_with_these_attributes(:p, [])
+
+  # microformats
+  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"])
+  Meta.allow_tag_with_these_attributes(:span, [])
+
+  # allow inline images for custom emoji
+  if Pleroma.Config.get([:markup, :allow_inline_images]) do
+    # restrict img tags to http/https only, because of MediaProxy.
+    Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
+
+    Meta.allow_tag_with_these_attributes(:img, [
+      "width",
+      "height",
+      "class",
+      "title",
+      "alt"
+    ])
+  end
+
+  Meta.strip_everything_not_covered()
+end
index 863270022e8c0f8ac8675277114642cc46498598..9b2c97963a8c56b1f9d7d677a6f9dee6ae2e7c7c 100644 (file)
@@ -252,7 +252,7 @@ defmodule Pleroma.Conversation.ParticipationTest do
 
       assert User.get_cached_by_id(blocker.id).unread_conversation_count == 4
 
-      {:ok, blocker} = User.block(blocker, blocked)
+      {:ok, _user_relationship} = User.block(blocker, blocked)
 
       # The conversations with the blocked user are marked as read
       assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] =
@@ -274,7 +274,7 @@ defmodule Pleroma.Conversation.ParticipationTest do
       blocked = insert(:user)
       third_user = insert(:user)
 
-      {:ok, blocker} = User.block(blocker, blocked)
+      {:ok, _user_relationship} = User.block(blocker, blocked)
 
       # When the blocked user is the author
       {:ok, _direct1} =
@@ -311,7 +311,7 @@ defmodule Pleroma.Conversation.ParticipationTest do
           "visibility" => "direct"
         })
 
-      {:ok, blocker} = User.block(blocker, blocked)
+      {:ok, _user_relationship} = User.block(blocker, blocked)
       assert [%{read: true}] = Participation.for_user(blocker)
       assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
 
index dcbffeafe39b16c1afdf7505fc4744cf2119ce25..ffa3d4b8c52604f0c638928de65d49b9b1de0c24 100644 (file)
@@ -93,7 +93,7 @@ defmodule Pleroma.NotificationTest do
       activity = insert(:note_activity)
       author = User.get_cached_by_ap_id(activity.data["actor"])
       user = insert(:user)
-      {:ok, user} = User.block(user, author)
+      {:ok, _user_relationship} = User.block(user, author)
 
       assert Notification.create_notification(activity, user)
     end
@@ -112,7 +112,7 @@ defmodule Pleroma.NotificationTest do
       muter = insert(:user)
       muted = insert(:user)
 
-      {:ok, muter} = User.mute(muter, muted, false)
+      {:ok, _user_relationships} = User.mute(muter, muted, false)
 
       {:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
 
@@ -136,7 +136,10 @@ defmodule Pleroma.NotificationTest do
 
     test "it disables notifications from followers" do
       follower = insert(:user)
-      followed = insert(:user, notification_settings: %{"followers" => false})
+
+      followed =
+        insert(:user, notification_settings: %Pleroma.User.NotificationSetting{followers: false})
+
       User.follow(follower, followed)
       {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
       refute Notification.create_notification(activity, followed)
@@ -144,13 +147,20 @@ defmodule Pleroma.NotificationTest do
 
     test "it disables notifications from non-followers" do
       follower = insert(:user)
-      followed = insert(:user, notification_settings: %{"non_followers" => false})
+
+      followed =
+        insert(:user,
+          notification_settings: %Pleroma.User.NotificationSetting{non_followers: false}
+        )
+
       {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
       refute Notification.create_notification(activity, followed)
     end
 
     test "it disables notifications from people the user follows" do
-      follower = insert(:user, notification_settings: %{"follows" => false})
+      follower =
+        insert(:user, notification_settings: %Pleroma.User.NotificationSetting{follows: false})
+
       followed = insert(:user)
       User.follow(follower, followed)
       follower = Repo.get(User, follower.id)
@@ -159,7 +169,9 @@ defmodule Pleroma.NotificationTest do
     end
 
     test "it disables notifications from people the user does not follow" do
-      follower = insert(:user, notification_settings: %{"non_follows" => false})
+      follower =
+        insert(:user, notification_settings: %Pleroma.User.NotificationSetting{non_follows: false})
+
       followed = insert(:user)
       {:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
       refute Notification.create_notification(activity, follower)
@@ -643,13 +655,17 @@ defmodule Pleroma.NotificationTest do
       Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
       ObanHelpers.perform_all()
 
+      assert [] = Notification.for_user(follower)
+
       assert [
                %{
                  activity: %{
                    data: %{"type" => "Move", "actor" => ^old_ap_id, "target" => ^new_ap_id}
                  }
                }
-             ] = Notification.for_user(follower)
+             ] = Notification.for_user(follower, %{with_move: true})
+
+      assert [] = Notification.for_user(other_follower)
 
       assert [
                %{
@@ -657,7 +673,7 @@ defmodule Pleroma.NotificationTest do
                    data: %{"type" => "Move", "actor" => ^old_ap_id, "target" => ^new_ap_id}
                  }
                }
-             ] = Notification.for_user(other_follower)
+             ] = Notification.for_user(other_follower, %{with_move: true})
     end
   end
 
@@ -665,7 +681,7 @@ defmodule Pleroma.NotificationTest do
     test "it returns notifications for muted user without notifications" do
       user = insert(:user)
       muted = insert(:user)
-      {:ok, user} = User.mute(user, muted, false)
+      {:ok, _user_relationships} = User.mute(user, muted, false)
 
       {:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
 
@@ -675,7 +691,7 @@ defmodule Pleroma.NotificationTest do
     test "it doesn't return notifications for muted user with notifications" do
       user = insert(:user)
       muted = insert(:user)
-      {:ok, user} = User.mute(user, muted)
+      {:ok, _user_relationships} = User.mute(user, muted)
 
       {:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
 
@@ -685,7 +701,7 @@ defmodule Pleroma.NotificationTest do
     test "it doesn't return notifications for blocked user" do
       user = insert(:user)
       blocked = insert(:user)
-      {:ok, user} = User.block(user, blocked)
+      {:ok, _user_relationship} = User.block(user, blocked)
 
       {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
 
@@ -715,7 +731,7 @@ defmodule Pleroma.NotificationTest do
     test "it returns notifications from a muted user when with_muted is set" do
       user = insert(:user)
       muted = insert(:user)
-      {:ok, user} = User.mute(user, muted)
+      {:ok, _user_relationships} = User.mute(user, muted)
 
       {:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
 
@@ -725,7 +741,7 @@ defmodule Pleroma.NotificationTest do
     test "it doesn't return notifications from a blocked user when with_muted is set" do
       user = insert(:user)
       blocked = insert(:user)
-      {:ok, user} = User.block(user, blocked)
+      {:ok, _user_relationship} = User.block(user, blocked)
 
       {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
 
index 6da16f71a90df664f9467633cfef851502f4c132..fcfea666f1a2f517f2b8a698e632cc0f1c1a9728 100644 (file)
@@ -10,7 +10,8 @@ defmodule Pleroma.Builders.UserBuilder do
       password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
       bio: "A tester.",
       ap_id: "some id",
-      last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+      last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
+      notification_settings: %Pleroma.User.NotificationSetting{}
     }
 
     Map.merge(user, data)
index 466d8986f9b10bb7554466454aad05af1dd90528..4a458584489fa63d1e66be55a0608e37bbb69acc 100644 (file)
@@ -23,6 +23,7 @@ defmodule Pleroma.Web.ChannelCase do
     quote do
       # Import conveniences for testing with channels
       use Phoenix.ChannelTest
+      use Pleroma.Tests.Helpers
 
       # The default endpoint for testing
       @endpoint Pleroma.Web.Endpoint
index bb8a64e7243816e5b9a036e25ec458311eda8c72..314f26ec99d1de43b7d7f07311b50ab39753a867 100644 (file)
@@ -31,7 +31,8 @@ defmodule Pleroma.Factory do
       nickname: sequence(:nickname, &"nick#{&1}"),
       password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
       bio: sequence(:bio, &"Tester Number #{&1}"),
-      last_digest_emailed_at: NaiveDateTime.utc_now()
+      last_digest_emailed_at: NaiveDateTime.utc_now(),
+      notification_settings: %Pleroma.User.NotificationSetting{}
     }
 
     %{
@@ -42,6 +43,18 @@ defmodule Pleroma.Factory do
     }
   end
 
+  def user_relationship_factory(attrs \\ %{}) do
+    source = attrs[:source] || insert(:user)
+    target = attrs[:target] || insert(:user)
+    relationship_type = attrs[:relationship_type] || :block
+
+    %Pleroma.UserRelationship{
+      source_id: source.id,
+      target_id: target.id,
+      relationship_type: relationship_type
+    }
+  end
+
   def note_factory(attrs \\ %{}) do
     text = sequence(:text, &"This is :moominmamma: note #{&1}")
 
index ce39dd9d8fad52ccaa405d36a10388068e3a8a45..af2b2eddf7bea5815e6dd05960eef6a839a5e213 100644 (file)
@@ -75,6 +75,23 @@ defmodule Pleroma.Tests.Helpers do
         |> Poison.decode!()
       end
 
+      def stringify_keys(nil), do: nil
+
+      def stringify_keys(key) when key in [true, false], do: key
+      def stringify_keys(key) when is_atom(key), do: Atom.to_string(key)
+
+      def stringify_keys(map) when is_map(map) do
+        map
+        |> Enum.map(fn {k, v} -> {stringify_keys(k), stringify_keys(v)} end)
+        |> Enum.into(%{})
+      end
+
+      def stringify_keys([head | rest] = list) when is_list(list) do
+        [stringify_keys(head) | stringify_keys(rest)]
+      end
+
+      def stringify_keys(key), do: key
+
       defmacro guards_config(config_path) do
         quote do
           initial_setting = Pleroma.Config.get(config_path)
index 9cd47380ca9a4e20b6472c8a1f1d28e76fa67d76..fab9d6e9acfb2df6254196870b43afb9260fb26e 100644 (file)
@@ -63,4 +63,84 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
     assert file =~ "config :pleroma, :setting_first,"
     assert file =~ "config :pleroma, :setting_second,"
   end
+
+  test "load a settings with large values and pass to file", %{temp_file: temp_file} do
+    Config.create(%{
+      group: "pleroma",
+      key: ":instance",
+      value: [
+        name: "Pleroma",
+        email: "example@example.com",
+        notify_email: "noreply@example.com",
+        description: "A Pleroma instance, an alternative fediverse server",
+        limit: 5_000,
+        chat_limit: 5_000,
+        remote_limit: 100_000,
+        upload_limit: 16_000_000,
+        avatar_upload_limit: 2_000_000,
+        background_upload_limit: 4_000_000,
+        banner_upload_limit: 4_000_000,
+        poll_limits: %{
+          max_options: 20,
+          max_option_chars: 200,
+          min_expiration: 0,
+          max_expiration: 365 * 24 * 60 * 60
+        },
+        registrations_open: true,
+        federating: true,
+        federation_incoming_replies_max_depth: 100,
+        federation_reachability_timeout_days: 7,
+        federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],
+        allow_relay: true,
+        rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
+        public: true,
+        quarantined_instances: [],
+        managed_config: true,
+        static_dir: "instance/static/",
+        allowed_post_formats: ["text/plain", "text/html", "text/markdown", "text/bbcode"],
+        mrf_transparency: true,
+        mrf_transparency_exclusions: [],
+        autofollowed_nicknames: [],
+        max_pinned_statuses: 1,
+        no_attachment_links: true,
+        welcome_user_nickname: nil,
+        welcome_message: nil,
+        max_report_comment_size: 1000,
+        safe_dm_mentions: false,
+        healthcheck: false,
+        remote_post_retention_days: 90,
+        skip_thread_containment: true,
+        limit_to_local_content: :unauthenticated,
+        dynamic_configuration: false,
+        user_bio_length: 5000,
+        user_name_length: 100,
+        max_account_fields: 10,
+        max_remote_account_fields: 20,
+        account_field_name_length: 512,
+        account_field_value_length: 2048,
+        external_user_synchronization: true,
+        extended_nickname_format: true,
+        multi_factor_authentication: [
+          totp: [
+            # digits 6 or 8
+            digits: 6,
+            period: 30
+          ],
+          backup_codes: [
+            number: 2,
+            length: 6
+          ]
+        ]
+      ]
+    })
+
+    Mix.Tasks.Pleroma.Config.run(["migrate_from_db", "temp", "true"])
+
+    assert Repo.all(Config) == []
+    assert File.exists?(temp_file)
+    {:ok, file} = File.read(temp_file)
+
+    assert file ==
+             "use Mix.Config\n\nconfig :pleroma, :instance,\n  name: \"Pleroma\",\n  email: \"example@example.com\",\n  notify_email: \"noreply@example.com\",\n  description: \"A Pleroma instance, an alternative fediverse server\",\n  limit: 5000,\n  chat_limit: 5000,\n  remote_limit: 100_000,\n  upload_limit: 16_000_000,\n  avatar_upload_limit: 2_000_000,\n  background_upload_limit: 4_000_000,\n  banner_upload_limit: 4_000_000,\n  poll_limits: %{\n    max_expiration: 31_536_000,\n    max_option_chars: 200,\n    max_options: 20,\n    min_expiration: 0\n  },\n  registrations_open: true,\n  federating: true,\n  federation_incoming_replies_max_depth: 100,\n  federation_reachability_timeout_days: 7,\n  federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n  allow_relay: true,\n  rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,\n  public: true,\n  quarantined_instances: [],\n  managed_config: true,\n  static_dir: \"instance/static/\",\n  allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n  mrf_transparency: true,\n  mrf_transparency_exclusions: [],\n  autofollowed_nicknames: [],\n  max_pinned_statuses: 1,\n  no_attachment_links: true,\n  welcome_user_nickname: nil,\n  welcome_message: nil,\n  max_report_comment_size: 1000,\n  safe_dm_mentions: false,\n  healthcheck: false,\n  remote_post_retention_days: 90,\n  skip_thread_containment: true,\n  limit_to_local_content: :unauthenticated,\n  dynamic_configuration: false,\n  user_bio_length: 5000,\n  user_name_length: 100,\n  max_account_fields: 10,\n  max_remote_account_fields: 20,\n  account_field_name_length: 512,\n  account_field_value_length: 2048,\n  external_user_synchronization: true,\n  extended_nickname_format: true,\n  multi_factor_authentication: [\n    totp: [digits: 6, period: 30],\n    backup_codes: [number: 2, length: 6]\n  ]\n"
+  end
 end
diff --git a/test/user/notification_setting_test.exs b/test/user/notification_setting_test.exs
new file mode 100644 (file)
index 0000000..4744d7b
--- /dev/null
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.NotificationSettingTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.User.NotificationSetting
+
+  describe "changeset/2" do
+    test "sets valid privacy option" do
+      changeset =
+        NotificationSetting.changeset(
+          %NotificationSetting{},
+          %{"privacy_option" => true}
+        )
+
+      assert %Ecto.Changeset{valid?: true} = changeset
+    end
+  end
+end
diff --git a/test/user_relationship_test.exs b/test/user_relationship_test.exs
new file mode 100644 (file)
index 0000000..437450b
--- /dev/null
@@ -0,0 +1,130 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.UserRelationshipTest do
+  alias Pleroma.UserRelationship
+
+  use Pleroma.DataCase
+
+  import Pleroma.Factory
+
+  describe "*_exists?/2" do
+    setup do
+      {:ok, users: insert_list(2, :user)}
+    end
+
+    test "returns false if record doesn't exist", %{users: [user1, user2]} do
+      refute UserRelationship.block_exists?(user1, user2)
+      refute UserRelationship.mute_exists?(user1, user2)
+      refute UserRelationship.notification_mute_exists?(user1, user2)
+      refute UserRelationship.reblog_mute_exists?(user1, user2)
+      refute UserRelationship.inverse_subscription_exists?(user1, user2)
+    end
+
+    test "returns true if record exists", %{users: [user1, user2]} do
+      for relationship_type <- [
+            :block,
+            :mute,
+            :notification_mute,
+            :reblog_mute,
+            :inverse_subscription
+          ] do
+        insert(:user_relationship,
+          source: user1,
+          target: user2,
+          relationship_type: relationship_type
+        )
+      end
+
+      assert UserRelationship.block_exists?(user1, user2)
+      assert UserRelationship.mute_exists?(user1, user2)
+      assert UserRelationship.notification_mute_exists?(user1, user2)
+      assert UserRelationship.reblog_mute_exists?(user1, user2)
+      assert UserRelationship.inverse_subscription_exists?(user1, user2)
+    end
+  end
+
+  describe "create_*/2" do
+    setup do
+      {:ok, users: insert_list(2, :user)}
+    end
+
+    test "creates user relationship record if it doesn't exist", %{users: [user1, user2]} do
+      for relationship_type <- [
+            :block,
+            :mute,
+            :notification_mute,
+            :reblog_mute,
+            :inverse_subscription
+          ] do
+        insert(:user_relationship,
+          source: user1,
+          target: user2,
+          relationship_type: relationship_type
+        )
+      end
+
+      UserRelationship.create_block(user1, user2)
+      UserRelationship.create_mute(user1, user2)
+      UserRelationship.create_notification_mute(user1, user2)
+      UserRelationship.create_reblog_mute(user1, user2)
+      UserRelationship.create_inverse_subscription(user1, user2)
+
+      assert UserRelationship.block_exists?(user1, user2)
+      assert UserRelationship.mute_exists?(user1, user2)
+      assert UserRelationship.notification_mute_exists?(user1, user2)
+      assert UserRelationship.reblog_mute_exists?(user1, user2)
+      assert UserRelationship.inverse_subscription_exists?(user1, user2)
+    end
+
+    test "if record already exists, returns it", %{users: [user1, user2]} do
+      user_block = UserRelationship.create_block(user1, user2)
+      assert user_block == UserRelationship.create_block(user1, user2)
+    end
+  end
+
+  describe "delete_*/2" do
+    setup do
+      {:ok, users: insert_list(2, :user)}
+    end
+
+    test "deletes user relationship record if it exists", %{users: [user1, user2]} do
+      for relationship_type <- [
+            :block,
+            :mute,
+            :notification_mute,
+            :reblog_mute,
+            :inverse_subscription
+          ] do
+        insert(:user_relationship,
+          source: user1,
+          target: user2,
+          relationship_type: relationship_type
+        )
+      end
+
+      assert {:ok, %UserRelationship{}} = UserRelationship.delete_block(user1, user2)
+      assert {:ok, %UserRelationship{}} = UserRelationship.delete_mute(user1, user2)
+      assert {:ok, %UserRelationship{}} = UserRelationship.delete_notification_mute(user1, user2)
+      assert {:ok, %UserRelationship{}} = UserRelationship.delete_reblog_mute(user1, user2)
+
+      assert {:ok, %UserRelationship{}} =
+               UserRelationship.delete_inverse_subscription(user1, user2)
+
+      refute UserRelationship.block_exists?(user1, user2)
+      refute UserRelationship.mute_exists?(user1, user2)
+      refute UserRelationship.notification_mute_exists?(user1, user2)
+      refute UserRelationship.reblog_mute_exists?(user1, user2)
+      refute UserRelationship.inverse_subscription_exists?(user1, user2)
+    end
+
+    test "if record does not exist, returns {:ok, nil}", %{users: [user1, user2]} do
+      assert {:ok, nil} = UserRelationship.delete_block(user1, user2)
+      assert {:ok, nil} = UserRelationship.delete_mute(user1, user2)
+      assert {:ok, nil} = UserRelationship.delete_notification_mute(user1, user2)
+      assert {:ok, nil} = UserRelationship.delete_reblog_mute(user1, user2)
+      assert {:ok, nil} = UserRelationship.delete_inverse_subscription(user1, user2)
+    end
+  end
+end
index 98841dbbda2c231f586a3b17896ca08815f2b737..82185847672a6a2e5affa1be18339721fd78f0f6 100644 (file)
@@ -174,6 +174,7 @@ defmodule Pleroma.UserSearchTest do
         |> Map.put(:search_rank, nil)
         |> Map.put(:search_type, nil)
         |> Map.put(:last_digest_emailed_at, nil)
+        |> Map.put(:notification_settings, nil)
 
       assert user == expected
     end
index c345e43e9168a475bb291013208a16b48bf3f1b1..bfa8faafa3e292d67639d37ee8b3976bcbbccdfb 100644 (file)
@@ -44,6 +44,56 @@ defmodule Pleroma.UserTest do
     end
   end
 
+  describe "AP ID user relationships" do
+    setup do
+      {:ok, user: insert(:user)}
+    end
+
+    test "outgoing_relations_ap_ids/1", %{user: user} do
+      rel_types = [:block, :mute, :notification_mute, :reblog_mute, :inverse_subscription]
+
+      ap_ids_by_rel =
+        Enum.into(
+          rel_types,
+          %{},
+          fn rel_type ->
+            rel_records =
+              insert_list(2, :user_relationship, %{source: user, relationship_type: rel_type})
+
+            ap_ids = Enum.map(rel_records, fn rr -> Repo.preload(rr, :target).target.ap_id end)
+            {rel_type, Enum.sort(ap_ids)}
+          end
+        )
+
+      assert ap_ids_by_rel[:block] == Enum.sort(User.blocked_users_ap_ids(user))
+      assert ap_ids_by_rel[:block] == Enum.sort(Enum.map(User.blocked_users(user), & &1.ap_id))
+
+      assert ap_ids_by_rel[:mute] == Enum.sort(User.muted_users_ap_ids(user))
+      assert ap_ids_by_rel[:mute] == Enum.sort(Enum.map(User.muted_users(user), & &1.ap_id))
+
+      assert ap_ids_by_rel[:notification_mute] ==
+               Enum.sort(User.notification_muted_users_ap_ids(user))
+
+      assert ap_ids_by_rel[:notification_mute] ==
+               Enum.sort(Enum.map(User.notification_muted_users(user), & &1.ap_id))
+
+      assert ap_ids_by_rel[:reblog_mute] == Enum.sort(User.reblog_muted_users_ap_ids(user))
+
+      assert ap_ids_by_rel[:reblog_mute] ==
+               Enum.sort(Enum.map(User.reblog_muted_users(user), & &1.ap_id))
+
+      assert ap_ids_by_rel[:inverse_subscription] == Enum.sort(User.subscriber_users_ap_ids(user))
+
+      assert ap_ids_by_rel[:inverse_subscription] ==
+               Enum.sort(Enum.map(User.subscriber_users(user), & &1.ap_id))
+
+      outgoing_relations_ap_ids = User.outgoing_relations_ap_ids(user, rel_types)
+
+      assert ap_ids_by_rel ==
+               Enum.into(outgoing_relations_ap_ids, %{}, fn {k, v} -> {k, Enum.sort(v)} end)
+    end
+  end
+
   describe "when tags are nil" do
     test "tagging a user" do
       user = insert(:user, %{tags: nil})
@@ -119,7 +169,7 @@ defmodule Pleroma.UserTest do
     CommonAPI.follow(follower, followed)
     assert [_activity] = User.get_follow_requests(followed)
 
-    {:ok, _follower} = User.block(followed, follower)
+    {:ok, _user_relationship} = User.block(followed, follower)
     assert [] = User.get_follow_requests(followed)
   end
 
@@ -132,8 +182,8 @@ defmodule Pleroma.UserTest do
     not_followed = insert(:user)
     reverse_blocked = insert(:user)
 
-    {:ok, user} = User.block(user, blocked)
-    {:ok, reverse_blocked} = User.block(reverse_blocked, user)
+    {:ok, _user_relationship} = User.block(user, blocked)
+    {:ok, _user_relationship} = User.block(reverse_blocked, user)
 
     {:ok, user} = User.follow(user, followed_zero)
 
@@ -186,7 +236,7 @@ defmodule Pleroma.UserTest do
     blocker = insert(:user)
     blockee = insert(:user)
 
-    {:ok, blocker} = User.block(blocker, blockee)
+    {:ok, _user_relationship} = User.block(blocker, blockee)
 
     {:error, _} = User.follow(blockee, blocker)
   end
@@ -195,7 +245,7 @@ defmodule Pleroma.UserTest do
     blocker = insert(:user)
     blocked = insert(:user)
 
-    {:ok, blocker} = User.block(blocker, blocked)
+    {:ok, _user_relationship} = User.block(blocker, blocked)
 
     {:error, _} = User.subscribe(blocked, blocker)
   end
@@ -678,7 +728,7 @@ defmodule Pleroma.UserTest do
       refute User.mutes?(user, muted_user)
       refute User.muted_notifications?(user, muted_user)
 
-      {:ok, user} = User.mute(user, muted_user)
+      {:ok, _user_relationships} = User.mute(user, muted_user)
 
       assert User.mutes?(user, muted_user)
       assert User.muted_notifications?(user, muted_user)
@@ -688,8 +738,8 @@ defmodule Pleroma.UserTest do
       user = insert(:user)
       muted_user = insert(:user)
 
-      {:ok, user} = User.mute(user, muted_user)
-      {:ok, user} = User.unmute(user, muted_user)
+      {:ok, _user_relationships} = User.mute(user, muted_user)
+      {:ok, _user_mute} = User.unmute(user, muted_user)
 
       refute User.mutes?(user, muted_user)
       refute User.muted_notifications?(user, muted_user)
@@ -702,7 +752,7 @@ defmodule Pleroma.UserTest do
       refute User.mutes?(user, muted_user)
       refute User.muted_notifications?(user, muted_user)
 
-      {:ok, user} = User.mute(user, muted_user, false)
+      {:ok, _user_relationships} = User.mute(user, muted_user, false)
 
       assert User.mutes?(user, muted_user)
       refute User.muted_notifications?(user, muted_user)
@@ -716,7 +766,7 @@ defmodule Pleroma.UserTest do
 
       refute User.blocks?(user, blocked_user)
 
-      {:ok, user} = User.block(user, blocked_user)
+      {:ok, _user_relationship} = User.block(user, blocked_user)
 
       assert User.blocks?(user, blocked_user)
     end
@@ -725,8 +775,8 @@ defmodule Pleroma.UserTest do
       user = insert(:user)
       blocked_user = insert(:user)
 
-      {:ok, user} = User.block(user, blocked_user)
-      {:ok, user} = User.unblock(user, blocked_user)
+      {:ok, _user_relationship} = User.block(user, blocked_user)
+      {:ok, _user_block} = User.unblock(user, blocked_user)
 
       refute User.blocks?(user, blocked_user)
     end
@@ -741,7 +791,7 @@ defmodule Pleroma.UserTest do
       assert User.following?(blocker, blocked)
       assert User.following?(blocked, blocker)
 
-      {:ok, blocker} = User.block(blocker, blocked)
+      {:ok, _user_relationship} = User.block(blocker, blocked)
       blocked = User.get_cached_by_id(blocked.id)
 
       assert User.blocks?(blocker, blocked)
@@ -759,7 +809,7 @@ defmodule Pleroma.UserTest do
       assert User.following?(blocker, blocked)
       refute User.following?(blocked, blocker)
 
-      {:ok, blocker} = User.block(blocker, blocked)
+      {:ok, _user_relationship} = User.block(blocker, blocked)
       blocked = User.get_cached_by_id(blocked.id)
 
       assert User.blocks?(blocker, blocked)
@@ -777,7 +827,7 @@ defmodule Pleroma.UserTest do
       refute User.following?(blocker, blocked)
       assert User.following?(blocked, blocker)
 
-      {:ok, blocker} = User.block(blocker, blocked)
+      {:ok, _user_relationship} = User.block(blocker, blocked)
       blocked = User.get_cached_by_id(blocked.id)
 
       assert User.blocks?(blocker, blocked)
@@ -790,12 +840,12 @@ defmodule Pleroma.UserTest do
       blocker = insert(:user)
       blocked = insert(:user)
 
-      {:ok, blocker} = User.subscribe(blocked, blocker)
+      {:ok, _subscription} = User.subscribe(blocked, blocker)
 
       assert User.subscribed_to?(blocked, blocker)
       refute User.subscribed_to?(blocker, blocked)
 
-      {:ok, blocker} = User.block(blocker, blocked)
+      {:ok, _user_relationship} = User.block(blocker, blocked)
 
       assert User.blocks?(blocker, blocked)
       refute User.subscribed_to?(blocker, blocked)
@@ -1324,7 +1374,8 @@ defmodule Pleroma.UserTest do
     {:ok, _follower2} = User.follow(follower2, user)
     {:ok, _follower3} = User.follow(follower3, user)
 
-    {:ok, user} = User.block(user, follower)
+    {:ok, _user_relationship} = User.block(user, follower)
+    user = refresh_record(user)
 
     assert user.follower_count == 2
   end
index 1aa73d75cf675efee5dfd1df83028840b92324b5..ba2ce1dd9157e5ea3ac854398a11208968969a1a 100644 (file)
@@ -298,7 +298,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       assert json_response(conn1, :ok)
       assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
 
-      Activity.delete_by_ap_id(activity.object.data["id"])
+      Activity.delete_all_by_object_ap_id(activity.object.data["id"])
 
       conn2 =
         conn
index 2677b9e36bbd48b1a700a2dc9f5811f997dc237a..97844a4075965b1f893265c5b9ca636bc2d2d375 100644 (file)
@@ -487,7 +487,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       activity_five = insert(:note_activity)
       user = insert(:user)
 
-      {:ok, user} = User.block(user, %{ap_id: activity_five.data["actor"]})
+      {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
 
       activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
       assert activities == [activity_two, activity]
@@ -500,7 +500,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     activity_three = insert(:note_activity)
     user = insert(:user)
     booster = insert(:user)
-    {:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]})
+    {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
 
     activities =
       ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
@@ -509,7 +509,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     assert Enum.member?(activities, activity_three)
     refute Enum.member?(activities, activity_one)
 
-    {:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
+    {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
 
     activities =
       ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
@@ -518,7 +518,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     assert Enum.member?(activities, activity_three)
     assert Enum.member?(activities, activity_one)
 
-    {:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
+    {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
     {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
     %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
     activity_three = Activity.get_by_id(activity_three.id)
@@ -545,7 +545,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     blockee = insert(:user)
     friend = insert(:user)
 
-    {:ok, blocker} = User.block(blocker, blockee)
+    {:ok, _user_relationship} = User.block(blocker, blockee)
 
     {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
 
@@ -568,7 +568,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     blockee = insert(:user)
     friend = insert(:user)
 
-    {:ok, blocker} = User.block(blocker, blockee)
+    {:ok, _user_relationship} = User.block(blocker, blockee)
 
     {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
 
@@ -614,7 +614,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     activity_three = insert(:note_activity)
     user = insert(:user)
     booster = insert(:user)
-    {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]})
+
+    activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
+    {:ok, _user_relationships} = User.mute(user, activity_one_actor)
 
     activities =
       ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
@@ -635,7 +637,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     assert Enum.member?(activities, activity_three)
     assert Enum.member?(activities, activity_one)
 
-    {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})
+    {:ok, _user_mute} = User.unmute(user, activity_one_actor)
 
     activities =
       ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
@@ -644,7 +646,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     assert Enum.member?(activities, activity_three)
     assert Enum.member?(activities, activity_one)
 
-    {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
+    activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
+    {:ok, _user_relationships} = User.mute(user, activity_three_actor)
     {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
     %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
     activity_three = Activity.get_by_id(activity_three.id)
@@ -791,7 +794,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       activity = insert(:note_activity)
       user = insert(:user)
       booster = insert(:user)
-      {:ok, user} = CommonAPI.hide_reblogs(user, booster)
+      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
 
       {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
 
@@ -804,8 +807,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       activity = insert(:note_activity)
       user = insert(:user)
       booster = insert(:user)
-      {:ok, user} = CommonAPI.hide_reblogs(user, booster)
-      {:ok, user} = CommonAPI.show_reblogs(user, booster)
+      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
+      {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
 
       {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
 
@@ -1256,6 +1259,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
       assert object.data["repliesCount"] == 0
     end
+
+    test "it passes delete activity through MRF before deleting the object" do
+      rewrite_policy = Pleroma.Config.get([:instance, :rewrite_policy])
+      Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy)
+
+      on_exit(fn -> Pleroma.Config.put([:instance, :rewrite_policy], rewrite_policy) end)
+
+      note = insert(:note_activity)
+      object = Object.normalize(note)
+
+      {:error, {:reject, _}} = ActivityPub.delete(object)
+
+      assert Activity.get_by_id(note.id)
+      assert Repo.get(Object, object.id).data["type"] == object.data["type"]
+    end
   end
 
   describe "timeline post-processing" do
@@ -1619,10 +1637,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       activity = %Activity{activity | object: nil}
 
       assert [%Notification{activity: ^activity}] =
-               Notification.for_user_since(follower, ~N[2019-04-13 11:22:33])
+               Notification.for_user(follower, %{with_move: true})
 
       assert [%Notification{activity: ^activity}] =
-               Notification.for_user_since(follower_move_opted_out, ~N[2019-04-13 11:22:33])
+               Notification.for_user(follower_move_opted_out, %{with_move: true})
     end
 
     test "old user must be in the new user's `also_known_as` list" do
index 75cfbea2e284195811b4e18eba5a9bb6bf89ea29..7d6d0814d7eb6e21d035d5f7fb4c8592f0f50c8d 100644 (file)
@@ -128,7 +128,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
       user = insert(:user)
       {:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin")
 
-      {:ok, user} = User.block(user, target)
+      {:ok, _user_relationship} = User.block(user, target)
 
       data =
         File.read!("test/fixtures/mastodon-follow-activity.json")
index 1feb076ba953d9693aa03f38ee5ae0cf13d667f1..586eb1d2f902bf11b83e9be7d2d2af942f68aa5d 100644 (file)
@@ -636,47 +636,4 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
       assert updated_object.data["announcement_count"] == 1
     end
   end
-
-  describe "get_reports_grouped_by_status/1" do
-    setup do
-      [reporter, target_user] = insert_pair(:user)
-      first_status = insert(:note_activity, user: target_user)
-      second_status = insert(:note_activity, user: target_user)
-
-      CommonAPI.report(reporter, %{
-        "account_id" => target_user.id,
-        "comment" => "I feel offended",
-        "status_ids" => [first_status.id]
-      })
-
-      CommonAPI.report(reporter, %{
-        "account_id" => target_user.id,
-        "comment" => "I feel offended2",
-        "status_ids" => [second_status.id]
-      })
-
-      data = [%{activity: first_status.data["id"]}, %{activity: second_status.data["id"]}]
-
-      {:ok,
-       %{
-         first_status: first_status,
-         second_status: second_status,
-         data: data
-       }}
-    end
-
-    test "works for deprecated reports format", %{
-      first_status: first_status,
-      second_status: second_status,
-      data: data
-    } do
-      groups = Utils.get_reports_grouped_by_status(data).groups
-
-      first_group = Enum.find(groups, &(&1.status.id == first_status.data["id"]))
-      second_group = Enum.find(groups, &(&1.status.id == second_status.data["id"]))
-
-      assert first_group.status.id == first_status.data["id"]
-      assert second_group.status.id == second_status.data["id"]
-    end
-  end
 end
index bcab63cf05193b34f015b1d15344cc8dd83be34f..23ca7f110d1d41c400e13d0708e55407ca6dea26 100644 (file)
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   alias Pleroma.UserInviteToken
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.MediaProxy
   import Pleroma.Factory
 
@@ -1667,6 +1668,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         first_status: Activity.get_by_ap_id_with_object(first_status.data["id"]),
         second_status: Activity.get_by_ap_id_with_object(second_status.data["id"]),
         third_status: Activity.get_by_ap_id_with_object(third_status.data["id"]),
+        first_report: first_report,
         first_status_reports: [first_report, second_report, third_report],
         second_status_reports: [first_report, second_report],
         third_status_reports: [first_report],
@@ -1693,14 +1695,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       assert length(response["reports"]) == 3
 
-      first_group =
-        Enum.find(response["reports"], &(&1["status"]["id"] == first_status.data["id"]))
+      first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id))
 
-      second_group =
-        Enum.find(response["reports"], &(&1["status"]["id"] == second_status.data["id"]))
+      second_group = Enum.find(response["reports"], &(&1["status"]["id"] == second_status.id))
 
-      third_group =
-        Enum.find(response["reports"], &(&1["status"]["id"] == third_status.data["id"]))
+      third_group = Enum.find(response["reports"], &(&1["status"]["id"] == third_status.id))
 
       assert length(first_group["reports"]) == 3
       assert length(second_group["reports"]) == 2
@@ -1711,13 +1710,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                  NaiveDateTime.from_iso8601!(act.data["published"])
                end).data["published"]
 
-      assert first_group["status"] == %{
-               "id" => first_status.data["id"],
-               "content" => first_status.object.data["content"],
-               "published" => first_status.object.data["published"]
-             }
+      assert first_group["status"] ==
+               Map.put(
+                 stringify_keys(StatusView.render("show.json", %{activity: first_status})),
+                 "deleted",
+                 false
+               )
 
-      assert first_group["account"]["id"] == target_user.id
+      assert(first_group["account"]["id"] == target_user.id)
 
       assert length(first_group["actors"]) == 1
       assert hd(first_group["actors"])["id"] == reporter.id
@@ -1730,11 +1730,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                  NaiveDateTime.from_iso8601!(act.data["published"])
                end).data["published"]
 
-      assert second_group["status"] == %{
-               "id" => second_status.data["id"],
-               "content" => second_status.object.data["content"],
-               "published" => second_status.object.data["published"]
-             }
+      assert second_group["status"] ==
+               Map.put(
+                 stringify_keys(StatusView.render("show.json", %{activity: second_status})),
+                 "deleted",
+                 false
+               )
 
       assert second_group["account"]["id"] == target_user.id
 
@@ -1749,11 +1750,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                  NaiveDateTime.from_iso8601!(act.data["published"])
                end).data["published"]
 
-      assert third_group["status"] == %{
-               "id" => third_status.data["id"],
-               "content" => third_status.object.data["content"],
-               "published" => third_status.object.data["published"]
-             }
+      assert third_group["status"] ==
+               Map.put(
+                 stringify_keys(StatusView.render("show.json", %{activity: third_status})),
+                 "deleted",
+                 false
+               )
 
       assert third_group["account"]["id"] == target_user.id
 
@@ -1763,6 +1765,70 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert Enum.map(third_group["reports"], & &1["id"]) --
                Enum.map(third_status_reports, & &1.id) == []
     end
+
+    test "reopened report renders status data", %{
+      conn: conn,
+      first_report: first_report,
+      first_status: first_status
+    } do
+      {:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved")
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/grouped_reports")
+        |> json_response(:ok)
+
+      first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id))
+
+      assert first_group["status"] ==
+               Map.put(
+                 stringify_keys(StatusView.render("show.json", %{activity: first_status})),
+                 "deleted",
+                 false
+               )
+    end
+
+    test "reopened report does not render status data if status has been deleted", %{
+      conn: conn,
+      first_report: first_report,
+      first_status: first_status,
+      target_user: target_user
+    } do
+      {:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved")
+      {:ok, _} = CommonAPI.delete(first_status.id, target_user)
+
+      refute Activity.get_by_ap_id(first_status.id)
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/grouped_reports")
+        |> json_response(:ok)
+
+      assert Enum.find(response["reports"], &(&1["status"]["deleted"] == true))["status"][
+               "deleted"
+             ] == true
+
+      assert length(Enum.filter(response["reports"], &(&1["status"]["deleted"] == false))) == 2
+    end
+
+    test "account not empty if status was deleted", %{
+      conn: conn,
+      first_report: first_report,
+      first_status: first_status,
+      target_user: target_user
+    } do
+      {:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved")
+      {:ok, _} = CommonAPI.delete(first_status.id, target_user)
+
+      refute Activity.get_by_ap_id(first_status.id)
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/grouped_reports")
+        |> json_response(:ok)
+
+      assert Enum.find(response["reports"], &(&1["status"]["deleted"] == true))["account"]
+    end
   end
 
   describe "POST /api/pleroma/admin/reports/:id/respond" do
diff --git a/test/web/chat_channel_test.exs b/test/web/chat_channel_test.exs
new file mode 100644 (file)
index 0000000..68c24a9
--- /dev/null
@@ -0,0 +1,37 @@
+defmodule Pleroma.Web.ChatChannelTest do
+  use Pleroma.Web.ChannelCase
+  alias Pleroma.Web.ChatChannel
+  alias Pleroma.Web.UserSocket
+
+  import Pleroma.Factory
+
+  setup do
+    user = insert(:user)
+
+    {:ok, _, socket} =
+      socket(UserSocket, "", %{user_name: user.nickname})
+      |> subscribe_and_join(ChatChannel, "chat:public")
+
+    {:ok, socket: socket}
+  end
+
+  test "it broadcasts a message", %{socket: socket} do
+    push(socket, "new_msg", %{"text" => "why is tenshi eating a corndog so cute?"})
+    assert_broadcast("new_msg", %{text: "why is tenshi eating a corndog so cute?"})
+  end
+
+  describe "message lengths" do
+    clear_config([:instance, :chat_limit])
+
+    test "it ignores messages of length zero", %{socket: socket} do
+      push(socket, "new_msg", %{"text" => ""})
+      refute_broadcast("new_msg", %{text: ""})
+    end
+
+    test "it ignores messages above a certain length", %{socket: socket} do
+      Pleroma.Config.put([:instance, :chat_limit], 2)
+      push(socket, "new_msg", %{"text" => "123"})
+      refute_broadcast("new_msg", %{text: "123"})
+    end
+  end
+end
index 138488d44b1626f8ea7fe5ec5ce140794a5d7c71..b5d6d40558441c250331f81b3bcfed0c131fb43d 100644 (file)
@@ -509,14 +509,14 @@ defmodule Pleroma.Web.CommonAPITest do
     end
 
     test "add a reblog mute", %{muter: muter, muted: muted} do
-      {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
+      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
 
       assert User.showing_reblogs?(muter, muted) == false
     end
 
     test "remove a reblog mute", %{muter: muter, muted: muted} do
-      {:ok, muter} = CommonAPI.hide_reblogs(muter, muted)
-      {:ok, muter} = CommonAPI.show_reblogs(muter, muted)
+      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
+      {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
 
       assert User.showing_reblogs?(muter, muted) == true
     end
@@ -526,7 +526,7 @@ defmodule Pleroma.Web.CommonAPITest do
     test "also unsubscribes a user" do
       [follower, followed] = insert_pair(:user)
       {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
-      {:ok, followed} = User.subscribe(follower, followed)
+      {:ok, _subscription} = User.subscribe(follower, followed)
 
       assert User.subscribed_to?(follower, followed)
 
index 585cb8a9e95d54b00572a949530d61e8416b9b25..fa08ae4df5ba4764a202358423275d64aed2ff3f 100644 (file)
@@ -144,6 +144,50 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
   end
 
   describe "user timelines" do
+    test "respects blocks", %{conn: conn} do
+      user_one = insert(:user)
+      user_two = insert(:user)
+      user_three = insert(:user)
+
+      User.block(user_one, user_two)
+
+      {:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"})
+      {:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three)
+
+      resp =
+        conn
+        |> get("/api/v1/accounts/#{user_two.id}/statuses")
+
+      assert [%{"id" => id}] = json_response(resp, 200)
+      assert id == activity.id
+
+      # Even a blocked user will deliver the full user timeline, there would be
+      # no point in looking at a blocked users timeline otherwise
+      resp =
+        conn
+        |> assign(:user, user_one)
+        |> get("/api/v1/accounts/#{user_two.id}/statuses")
+
+      assert [%{"id" => id}] = json_response(resp, 200)
+      assert id == activity.id
+
+      resp =
+        conn
+        |> get("/api/v1/accounts/#{user_three.id}/statuses")
+
+      assert [%{"id" => id}] = json_response(resp, 200)
+      assert id == repeat.id
+
+      # When viewing a third user's timeline, the blocked users will NOT be
+      # shown.
+      resp =
+        conn
+        |> assign(:user, user_one)
+        |> get("/api/v1/accounts/#{user_three.id}/statuses")
+
+      assert [] = json_response(resp, 200)
+    end
+
     test "gets a users statuses", %{conn: conn} do
       user_one = insert(:user)
       user_two = insert(:user)
@@ -891,7 +935,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     user = insert(:user)
     other_user = insert(:user)
 
-    {:ok, user} = User.mute(user, other_user)
+    {:ok, _user_relationships} = User.mute(user, other_user)
 
     conn =
       conn
@@ -906,7 +950,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     user = insert(:user)
     other_user = insert(:user)
 
-    {:ok, user} = User.block(user, other_user)
+    {:ok, _user_relationship} = User.block(user, other_user)
 
     conn =
       conn
index fa55a7cf927e33313dd00b074bcf607f1fb8f04b..6635ea7a2a027f562b60be06aa66ec1d6def10cf 100644 (file)
@@ -137,55 +137,151 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
     assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
   end
 
-  test "filters notifications using exclude_visibilities", %{conn: conn} do
-    user = insert(:user)
-    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"})
-
-    conn = assign(conn, :user, user)
-
-    conn_res =
-      get(conn, "/api/v1/notifications", %{
-        exclude_visibilities: ["public", "unlisted", "private"]
-      })
-
-    assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
-    assert id == direct_activity.id
-
-    conn_res =
-      get(conn, "/api/v1/notifications", %{
-        exclude_visibilities: ["public", "unlisted", "direct"]
-      })
-
-    assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
-    assert id == private_activity.id
-
-    conn_res =
-      get(conn, "/api/v1/notifications", %{
-        exclude_visibilities: ["public", "private", "direct"]
-      })
-
-    assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
-    assert id == unlisted_activity.id
-
-    conn_res =
-      get(conn, "/api/v1/notifications", %{
-        exclude_visibilities: ["unlisted", "private", "direct"]
-      })
-
-    assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
-    assert id == public_activity.id
+  describe "exclude_visibilities" do
+    test "filters notifications for mentions", %{conn: conn} do
+      user = insert(:user)
+      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"})
+
+      conn = assign(conn, :user, user)
+
+      conn_res =
+        get(conn, "/api/v1/notifications", %{
+          exclude_visibilities: ["public", "unlisted", "private"]
+        })
+
+      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
+      assert id == direct_activity.id
+
+      conn_res =
+        get(conn, "/api/v1/notifications", %{
+          exclude_visibilities: ["public", "unlisted", "direct"]
+        })
+
+      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
+      assert id == private_activity.id
+
+      conn_res =
+        get(conn, "/api/v1/notifications", %{
+          exclude_visibilities: ["public", "private", "direct"]
+        })
+
+      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
+      assert id == unlisted_activity.id
+
+      conn_res =
+        get(conn, "/api/v1/notifications", %{
+          exclude_visibilities: ["unlisted", "private", "direct"]
+        })
+
+      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
+      assert id == public_activity.id
+    end
+
+    test "filters notifications for Like activities", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(: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
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["direct"]})
+        |> json_response(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
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]})
+        |> json_response(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
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["private"]})
+        |> json_response(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
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["public"]})
+        |> json_response(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", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {: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
+        |> assign(:user, other_user)
+        |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]})
+        |> json_response(200)
+        |> Enum.map(& &1["status"]["id"])
+
+      assert public_activity.id in activity_ids
+      refute unlisted_activity.id in activity_ids
+    end
   end
 
   test "filters notifications using exclude_types", %{conn: conn} do
@@ -289,7 +385,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
 
     assert length(json_response(conn, 200)) == 1
 
-    {:ok, user} = User.mute(user, user2)
+    {:ok, _user_relationships} = User.mute(user, user2)
 
     conn = assign(build_conn(), :user, user)
     conn = get(conn, "/api/v1/notifications")
@@ -310,7 +406,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
 
     assert length(json_response(conn, 200)) == 1
 
-    {:ok, user} = User.mute(user, user2, false)
+    {:ok, _user_relationships} = User.mute(user, user2, false)
 
     conn = assign(build_conn(), :user, user)
     conn = get(conn, "/api/v1/notifications")
@@ -333,7 +429,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
 
     assert length(json_response(conn, 200)) == 1
 
-    {:ok, user} = User.mute(user, user2)
+    {:ok, _user_relationships} = User.mute(user, user2)
 
     conn = assign(build_conn(), :user, user)
     conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
@@ -341,6 +437,32 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
     assert length(json_response(conn, 200)) == 1
   end
 
+  test "see move notifications with `with_move` parameter", %{
+    conn: conn
+  } do
+    old_user = insert(:user)
+    new_user = insert(:user, also_known_as: [old_user.ap_id])
+    follower = insert(:user)
+
+    User.follow(follower, old_user)
+    Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
+    Pleroma.Tests.ObanHelpers.perform_all()
+
+    conn =
+      conn
+      |> assign(:user, follower)
+      |> get("/api/v1/notifications")
+
+    assert json_response(conn, 200) == []
+
+    conn =
+      build_conn()
+      |> assign(:user, follower)
+      |> get("/api/v1/notifications", %{"with_move" => "true"})
+
+    assert length(json_response(conn, 200)) == 1
+  end
+
   defp get_notification_id_by_activity(%{id: id}) do
     Notification
     |> Repo.get_by(activity_id: id)
index 7953fad62c197ada25def63fd8821988c7a19184..34deeba47c5cd4f5a4ad82d1d54cc92cabf09997 100644 (file)
@@ -165,15 +165,20 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
       assert status["id"] == to_string(activity.id)
     end
 
-    test "search fetches remote statuses", %{conn: conn} do
+    test "search fetches remote statuses and prefers them over other results", %{conn: conn} do
       capture_log(fn ->
+        {:ok, %{id: activity_id}} =
+          CommonAPI.post(insert(:user), %{
+            "status" => "check out https://shitposter.club/notice/2827873"
+          })
+
         conn =
           conn
           |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
 
         assert results = json_response(conn, 200)
 
-        [status] = results["statuses"]
+        [status, %{"id" => ^activity_id}] = results["statuses"]
 
         assert status["uri"] ==
                  "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
index a96fd860b50fe2edb08996e0cf7af3d5bc93137b..5fbe947ba3df9943be64b375f0c8a48591c22bb2 100644 (file)
@@ -1108,7 +1108,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
       activity: activity
     } do
       other_user = insert(:user)
-      {:ok, user} = User.block(user, other_user)
+      {:ok, _user_relationship} = User.block(user, other_user)
 
       {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
 
@@ -1205,7 +1205,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
       activity: activity
     } do
       other_user = insert(:user)
-      {:ok, user} = User.block(user, other_user)
+      {:ok, _user_relationship} = User.block(user, other_user)
 
       {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
 
index 61b6cea7547c49ec2d90a115b8cf2e4abd248e3f..dc17cc963d62db5fd5ec461a8f485d4454bb4c17 100644 (file)
@@ -194,7 +194,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       blocker = insert(:user)
       blocked = insert(:user)
       user = insert(:user)
-      {:ok, blocker} = User.block(blocker, blocked)
+      {:ok, _user_relationship} = User.block(blocker, blocked)
 
       {:ok, _blocked_direct} =
         CommonAPI.post(blocked, %{
index 35aefb7dc08f4efbd0cadb0f75cc767c60e7166a..5e297d1298015fe8a2637512c921d95ba6ede6f6 100644 (file)
@@ -92,13 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
   test "Represent the user account for the account owner" do
     user = insert(:user)
 
-    notification_settings = %{
-      "followers" => true,
-      "follows" => true,
-      "non_follows" => true,
-      "non_followers" => true
-    }
-
+    notification_settings = %Pleroma.User.NotificationSetting{}
     privacy = user.default_scope
 
     assert %{
@@ -190,9 +184,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
 
       {:ok, user} = User.follow(user, other_user)
       {:ok, other_user} = User.follow(other_user, user)
-      {:ok, other_user} = User.subscribe(user, other_user)
-      {:ok, user} = User.mute(user, other_user, true)
-      {:ok, user} = CommonAPI.hide_reblogs(user, other_user)
+      {:ok, _subscription} = User.subscribe(user, other_user)
+      {:ok, _user_relationships} = User.mute(user, other_user, true)
+      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
 
       expected = %{
         id: to_string(other_user.id),
@@ -218,9 +212,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       other_user = insert(:user)
 
       {:ok, user} = User.follow(user, other_user)
-      {:ok, other_user} = User.subscribe(user, other_user)
-      {:ok, user} = User.block(user, other_user)
-      {:ok, other_user} = User.block(other_user, user)
+      {:ok, _subscription} = User.subscribe(user, other_user)
+      {:ok, _user_relationship} = User.block(user, other_user)
+      {:ok, _user_relationship} = User.block(other_user, user)
 
       expected = %{
         id: to_string(other_user.id),
@@ -291,7 +285,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
 
     other_user = insert(:user)
     {:ok, other_user} = User.follow(other_user, user)
-    {:ok, other_user} = User.block(other_user, user)
+    {:ok, _user_relationship} = User.block(other_user, user)
     {:ok, _} = User.follow(insert(:user), user)
 
     expected = %{
index 26e1afc857d5fc8f2ae5f68f65ba3f9cdc4f5920..ba1721e06dd5b9ebf5ec884e5df9d0e9ffffc53e 100644 (file)
@@ -109,8 +109,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
   end
 
   test "Move notification" do
-    %{ap_id: old_ap_id} = old_user = insert(:user)
-    %{ap_id: _new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
+    old_user = insert(:user)
+    new_user = insert(:user, also_known_as: [old_user.ap_id])
     follower = insert(:user)
 
     User.follow(follower, old_user)
@@ -120,7 +120,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
     old_user = refresh_record(old_user)
     new_user = refresh_record(new_user)
 
-    [notification] = Notification.for_user(follower)
+    [notification] = Notification.for_user(follower, %{with_move: true})
 
     expected = %{
       id: to_string(notification.id),
index d46ecc646b624a8297cec9903317f29ade3d74c8..bdd87a79e6296339530f3a73b62430f10c3a03a2 100644 (file)
@@ -183,7 +183,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
     user = insert(:user)
     other_user = insert(:user)
 
-    {:ok, user} = User.mute(user, other_user)
+    {:ok, _user_relationships} = User.mute(user, other_user)
 
     {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
     status = StatusView.render("show.json", %{activity: activity})
@@ -199,7 +199,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
     user = insert(:user)
     other_user = insert(:user)
 
-    {:ok, user} = User.mute(user, other_user)
+    {:ok, _user_relationships} = User.mute(user, other_user)
 
     {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
     status = StatusView.render("show.json", %{activity: activity, for: user})
index 9b554601d9c146079ccb1809a49ba37aef522bbd..acae7a734deca67cb8d147f0c58e8d26feebb349 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.Push.ImplTest do
   use Pleroma.DataCase
 
   alias Pleroma.Object
+  alias Pleroma.User
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Push.Impl
   alias Pleroma.Web.Push.Subscription
@@ -182,4 +183,50 @@ defmodule Pleroma.Web.Push.ImplTest do
     assert Impl.format_title(%{activity: activity}) ==
              "New Direct Message"
   end
+
+  describe "build_content/3" do
+    test "returns info content for direct message with enabled privacy option" do
+      user = insert(:user, nickname: "Bob")
+      user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true})
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "visibility" => "direct",
+          "status" => "<Lorem ipsum dolor sit amet."
+        })
+
+      notif = insert(:notification, user: user2, activity: activity)
+
+      actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
+      object = Object.normalize(activity)
+
+      assert Impl.build_content(notif, actor, object) == %{
+               body: "@Bob",
+               title: "New Direct Message"
+             }
+    end
+
+    test "returns regular content for direct message with disabled privacy option" do
+      user = insert(:user, nickname: "Bob")
+      user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: false})
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "visibility" => "direct",
+          "status" =>
+            "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
+        })
+
+      notif = insert(:notification, user: user2, activity: activity)
+
+      actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
+      object = Object.normalize(activity)
+
+      assert Impl.build_content(notif, actor, object) == %{
+               body:
+                 "@Bob: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini...",
+               title: "New Direct Message"
+             }
+    end
+  end
 end
index 8265f18dd28e1939d186f5264204aae65d2deac7..8911c46b190bdd62e2ccc328697ea2c5d2cf3e40 100644 (file)
@@ -59,7 +59,7 @@ defmodule Pleroma.Web.StreamerTest do
       user: user
     } do
       blocked = insert(:user)
-      {:ok, user} = User.block(user, blocked)
+      {:ok, _user_relationship} = User.block(user, blocked)
 
       task = Task.async(fn -> refute_receive {:text, _}, 4_000 end)
 
@@ -259,7 +259,7 @@ defmodule Pleroma.Web.StreamerTest do
     test "it doesn't send messages involving blocked users" do
       user = insert(:user)
       blocked_user = insert(:user)
-      {:ok, user} = User.block(user, blocked_user)
+      {:ok, _user_relationship} = User.block(user, blocked_user)
 
       task =
         Task.async(fn ->
@@ -301,7 +301,7 @@ defmodule Pleroma.Web.StreamerTest do
         "public" => [fake_socket]
       }
 
-      {:ok, blocker} = User.block(blocker, blockee)
+      {:ok, _user_relationship} = User.block(blocker, blockee)
 
       {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"})
 
index f0211f59c64e4f5681ac7c4ed1cb3462b7386a9a..734cd221199554898ef40bbec79c0b658a4b139a 100644 (file)
@@ -159,11 +159,31 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
 
       user = Repo.get(User, user.id)
 
-      assert %{
-               "followers" => false,
-               "follows" => true,
-               "non_follows" => true,
-               "non_followers" => true
+      assert %Pleroma.User.NotificationSetting{
+               followers: false,
+               follows: true,
+               non_follows: true,
+               non_followers: true,
+               privacy_option: false
+             } == user.notification_settings
+    end
+
+    test "it update notificatin privacy option", %{conn: conn} do
+      user = insert(:user)
+
+      conn
+      |> assign(:user, user)
+      |> put("/api/pleroma/notification_settings", %{"privacy_option" => "1"})
+      |> json_response(:ok)
+
+      user = refresh_record(user)
+
+      assert %Pleroma.User.NotificationSetting{
+               followers: true,
+               follows: true,
+               non_follows: true,
+               non_followers: true,
+               privacy_option: true
              } == user.notification_settings
     end
   end
@@ -387,7 +407,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       user = insert(:user)
       user2 = insert(:user)
 
-      {:ok, _user} = Pleroma.User.block(user2, user)
+      {:ok, _user_block} = Pleroma.User.block(user2, user)
 
       response =
         conn
@@ -485,7 +505,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       Pleroma.Config.put([:user, :deny_follow_blocked], true)
       user = insert(:user)
       user2 = insert(:user)
-      {:ok, _user} = Pleroma.User.block(user2, user)
+      {:ok, _user_block} = Pleroma.User.block(user2, user)
 
       response =
         conn