aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml19
-rw-r--r--CHANGELOG.md15
-rw-r--r--README.md10
-rw-r--r--config/config.exs19
-rw-r--r--config/description.exs151
-rw-r--r--docs/API/admin_api.md120
-rw-r--r--docs/API/chats.md248
-rw-r--r--docs/API/differences_in_mastoapi_responses.md4
-rw-r--r--docs/API/pleroma_api.md34
-rw-r--r--docs/administration/CLI_tasks/emoji.md8
-rw-r--r--docs/administration/CLI_tasks/user.md10
-rw-r--r--docs/ap_extensions.md35
-rw-r--r--docs/configuration/cheatsheet.md63
-rw-r--r--docs/configuration/howto_theming_your_instance.md2
-rw-r--r--docs/configuration/mrf.md14
-rw-r--r--docs/configuration/storing_remote_media.md4
-rw-r--r--docs/dev.md2
-rw-r--r--docs/index.md26
-rw-r--r--docs/installation/alpine_linux_en.md5
-rw-r--r--docs/installation/arch_linux_en.md5
-rw-r--r--docs/installation/debian_based_en.md5
-rw-r--r--docs/installation/debian_based_jp.md5
-rw-r--r--docs/installation/further_reading.include5
-rw-r--r--docs/installation/gentoo_en.md5
-rw-r--r--docs/installation/netbsd_en.md8
-rw-r--r--docs/installation/openbsd_en.md8
-rw-r--r--docs/installation/otp_en.md5
-rw-r--r--docs/introduction.md65
-rw-r--r--elixir_buildpack.config4
-rwxr-xr-xinstallation/nginx-cache-purge.sh.example4
-rw-r--r--installation/pleroma.nginx11
-rw-r--r--lib/mix/tasks/pleroma/config.ex11
-rw-r--r--lib/mix/tasks/pleroma/emoji.ex6
-rw-r--r--lib/mix/tasks/pleroma/user.ex12
-rw-r--r--lib/pleroma/activity.ex40
-rw-r--r--lib/pleroma/application.ex5
-rw-r--r--lib/pleroma/application_requirements.ex107
-rw-r--r--lib/pleroma/chat.ex72
-rw-r--r--lib/pleroma/chat/message_reference.ex117
-rw-r--r--lib/pleroma/config/config_db.ex285
-rw-r--r--lib/pleroma/config/deprecation_warnings.ex69
-rw-r--r--lib/pleroma/config/transfer_task.ex12
-rw-r--r--lib/pleroma/conversation/participation.ex11
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/date_time.ex (renamed from lib/pleroma/web/activity_pub/object_validators/types/date_time.ex)6
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/object_id.ex (renamed from lib/pleroma/web/activity_pub/object_validators/types/object_id.ex)6
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex40
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex25
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/uri.ex (renamed from lib/pleroma/web/activity_pub/object_validators/types/uri.ex)6
-rw-r--r--lib/pleroma/ecto_type/config/atom.ex26
-rw-r--r--lib/pleroma/ecto_type/config/binary_value.ex27
-rw-r--r--lib/pleroma/emoji/pack.ex54
-rw-r--r--lib/pleroma/migration_helper/notification_backfill.ex85
-rw-r--r--lib/pleroma/notification.ex129
-rw-r--r--lib/pleroma/pagination.ex6
-rw-r--r--lib/pleroma/plugs/http_security_plug.ex5
-rw-r--r--lib/pleroma/plugs/uploaded_media.ex16
-rw-r--r--lib/pleroma/repo.ex59
-rw-r--r--lib/pleroma/signature.ex4
-rw-r--r--lib/pleroma/upload.ex1
-rw-r--r--lib/pleroma/user.ex117
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex101
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex3
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex37
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex11
-rw-r--r--lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex43
-rw-r--r--lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex7
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex28
-rw-r--r--lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex51
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/announce_validator.ex14
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex80
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex123
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex91
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex (renamed from lib/pleroma/web/activity_pub/object_validators/create_validator.ex)6
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/delete_validator.ex17
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex8
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/like_validator.ex14
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/note_validator.ex13
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/types/recipients.ex34
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/undo_validator.ex8
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex24
-rw-r--r--lib/pleroma/web/activity_pub/pipeline.ex4
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex97
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex36
-rw-r--r--lib/pleroma/web/admin_api/controllers/admin_api_controller.ex26
-rw-r--r--lib/pleroma/web/admin_api/controllers/config_controller.ex34
-rw-r--r--lib/pleroma/web/admin_api/controllers/fallback_controller.ex6
-rw-r--r--lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex63
-rw-r--r--lib/pleroma/web/admin_api/views/account_view.ex3
-rw-r--r--lib/pleroma/web/admin_api/views/config_view.ex19
-rw-r--r--lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex11
-rw-r--r--lib/pleroma/web/api_spec/helpers.ex6
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex2
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex109
-rw-r--r--lib/pleroma/web/api_spec/operations/chat_operation.ex355
-rw-r--r--lib/pleroma/web/api_spec/operations/notification_operation.ex12
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex30
-rw-r--r--lib/pleroma/web/api_spec/operations/status_operation.ex3
-rw-r--r--lib/pleroma/web/api_spec/operations/subscription_operation.ex5
-rw-r--r--lib/pleroma/web/api_spec/schemas/chat.ex75
-rw-r--r--lib/pleroma/web/api_spec/schemas/chat_message.ex41
-rw-r--r--lib/pleroma/web/common_api/activity_draft.ex9
-rw-r--r--lib/pleroma/web/common_api/common_api.ex70
-rw-r--r--lib/pleroma/web/common_api/utils.ex2
-rw-r--r--lib/pleroma/web/controller_helper.ex49
-rw-r--r--lib/pleroma/web/masto_fe_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex22
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/notification_controller.ex14
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/search_controller.ex42
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex1
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api.ex15
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex18
-rw-r--r--lib/pleroma/web/mastodon_api/views/conversation_view.ex11
-rw-r--r--lib/pleroma/web/mastodon_api/views/instance_view.ex12
-rw-r--r--lib/pleroma/web/mastodon_api/views/notification_view.ex94
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex14
-rw-r--r--lib/pleroma/web/media_proxy/invalidation.ex26
-rw-r--r--lib/pleroma/web/media_proxy/invalidations/http.ex10
-rw-r--r--lib/pleroma/web/media_proxy/invalidations/script.ex36
-rw-r--r--lib/pleroma/web/media_proxy/media_proxy.ex35
-rw-r--r--lib/pleroma/web/media_proxy/media_proxy_controller.ex3
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/chat_controller.ex174
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex10
-rw-r--r--lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex45
-rw-r--r--lib/pleroma/web/pleroma_api/views/chat_view.ex33
-rw-r--r--lib/pleroma/web/push/impl.ex24
-rw-r--r--lib/pleroma/web/push/subscription.ex2
-rw-r--r--lib/pleroma/web/rich_media/helpers.ex6
-rw-r--r--lib/pleroma/web/rich_media/parser.ex16
-rw-r--r--lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex33
-rw-r--r--lib/pleroma/web/rich_media/parsers/oembed_parser.ex22
-rw-r--r--lib/pleroma/web/rich_media/parsers/ogp.ex11
-rw-r--r--lib/pleroma/web/rich_media/parsers/twitter_card.ex15
-rw-r--r--lib/pleroma/web/router.ex14
-rw-r--r--lib/pleroma/web/streamer/streamer.ex44
-rw-r--r--lib/pleroma/web/views/streamer_view.ex23
-rw-r--r--lib/pleroma/workers/attachments_cleanup_worker.ex134
-rw-r--r--mix.exs45
-rw-r--r--priv/gettext/it/LC_MESSAGES/errors.po580
-rw-r--r--priv/repo/migrations/20200309123730_create_chats.exs16
-rw-r--r--priv/repo/migrations/20200322174133_user_raw_bio.exs9
-rw-r--r--priv/repo/migrations/20200323122421_mrf_config_move_from_instance_namespace.exs39
-rw-r--r--priv/repo/migrations/20200328193433_populate_user_raw_bio.exs25
-rw-r--r--priv/repo/migrations/20200527163635_delete_notifications_from_invisible_users.exs18
-rw-r--r--priv/repo/migrations/20200602094828_add_type_to_notifications.exs9
-rw-r--r--priv/repo/migrations/20200602125218_backfill_notification_types.exs10
-rw-r--r--priv/repo/migrations/20200602150528_create_chat_message_reference.exs20
-rw-r--r--priv/repo/migrations/20200603105113_add_unique_index_to_chat_message_references.exs7
-rw-r--r--priv/repo/migrations/20200603120448_remove_unread_from_chats.exs9
-rw-r--r--priv/repo/migrations/20200603122732_add_seen_index_to_chat_message_references.exs12
-rw-r--r--priv/repo/migrations/20200604150318_migrate_seen_to_unread_in_chat_message_references.exs30
-rw-r--r--priv/repo/migrations/20200606105430_change_type_to_enum_for_notifications.exs36
-rw-r--r--priv/repo/migrations/20200607112923_change_chat_id_to_flake.exs23
-rw-r--r--priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs4
-rw-r--r--priv/static/schemas/litepub-0.1.jsonld1
-rw-r--r--test/application_requirements_test.exs96
-rw-r--r--test/chat/message_reference_test.exs29
-rw-r--r--test/chat_test.exs61
-rw-r--r--test/config/config_db_test.exs591
-rw-r--r--test/config/deprecation_warnings_test.exs57
-rw-r--r--test/config/transfer_task_test.exs94
-rw-r--r--test/fixtures/config/temp.secret.exs2
-rw-r--r--test/fixtures/create-chat-message.json31
-rw-r--r--test/instance_static/emoji/test_pack/blank2.pngbin0 -> 95 bytes
-rw-r--r--test/instance_static/emoji/test_pack/pack.json3
-rw-r--r--test/instance_static/emoji/test_pack_nonshared/nonshared.zipbin256 -> 548 bytes
-rw-r--r--test/instance_static/emoji/test_pack_nonshared/pack.json2
-rw-r--r--test/migration_helper/notification_backfill_test.exs56
-rw-r--r--test/notification_test.exs20
-rw-r--r--test/repo_test.exs34
-rw-r--r--test/support/factory.ex20
-rw-r--r--test/tasks/config_test.exs47
-rw-r--r--test/tasks/user_test.exs30
-rw-r--r--test/upload/filter/mogrify_test.exs8
-rw-r--r--test/upload_test.exs1
-rw-r--r--test/user_test.exs10
-rw-r--r--test/web/activity_pub/activity_pub_test.exs85
-rw-r--r--test/web/activity_pub/mrf/activity_expiration_policy_test.exs77
-rw-r--r--test/web/activity_pub/mrf/hellthread_policy_test.exs18
-rw-r--r--test/web/activity_pub/mrf/mrf_test.exs4
-rw-r--r--test/web/activity_pub/mrf/user_allowlist_policy_test.exs6
-rw-r--r--test/web/activity_pub/object_validator_test.exs250
-rw-r--r--test/web/activity_pub/object_validators/types/date_time_test.exs2
-rw-r--r--test/web/activity_pub/object_validators/types/object_id_test.exs6
-rw-r--r--test/web/activity_pub/object_validators/types/recipients_test.exs2
-rw-r--r--test/web/activity_pub/object_validators/types/safe_text_test.exs30
-rw-r--r--test/web/activity_pub/pipeline_test.exs9
-rw-r--r--test/web/activity_pub/side_effects_test.exs185
-rw-r--r--test/web/activity_pub/transmogrifier/chat_message_test.exs153
-rw-r--r--test/web/activity_pub/transmogrifier/follow_handling_test.exs27
-rw-r--r--test/web/activity_pub/transmogrifier_test.exs3
-rw-r--r--test/web/admin_api/controllers/admin_api_controller_test.exs70
-rw-r--r--test/web/admin_api/controllers/config_controller_test.exs256
-rw-r--r--test/web/admin_api/controllers/media_proxy_cache_controller_test.exs145
-rw-r--r--test/web/common_api/common_api_test.exs146
-rw-r--r--test/web/federator_test.exs4
-rw-r--r--test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs80
-rw-r--r--test/web/mastodon_api/controllers/account_controller_test.exs69
-rw-r--r--test/web/mastodon_api/controllers/notification_controller_test.exs48
-rw-r--r--test/web/mastodon_api/controllers/search_controller_test.exs54
-rw-r--r--test/web/mastodon_api/controllers/status_controller_test.exs43
-rw-r--r--test/web/mastodon_api/controllers/subscription_controller_test.exs6
-rw-r--r--test/web/mastodon_api/views/account_view_test.exs5
-rw-r--r--test/web/mastodon_api/views/conversation_view_test.exs11
-rw-r--r--test/web/mastodon_api/views/notification_view_test.exs66
-rw-r--r--test/web/media_proxy/invalidation_test.exs64
-rw-r--r--test/web/media_proxy/invalidations/http_test.exs12
-rw-r--r--test/web/media_proxy/invalidations/script_test.exs20
-rw-r--r--test/web/media_proxy/media_proxy_controller_test.exs16
-rw-r--r--test/web/node_info_test.exs49
-rw-r--r--test/web/pleroma_api/controllers/chat_controller_test.exs336
-rw-r--r--test/web/pleroma_api/controllers/emoji_pack_controller_test.exs128
-rw-r--r--test/web/pleroma_api/views/chat/message_reference_view_test.exs61
-rw-r--r--test/web/pleroma_api/views/chat_view_test.exs48
-rw-r--r--test/web/push/impl_test.exs67
-rw-r--r--test/web/rich_media/parser_test.exs75
-rw-r--r--test/web/rich_media/parsers/twitter_card_test.exs130
-rw-r--r--test/web/streamer/streamer_test.exs55
-rw-r--r--test/workers/cron/purge_expired_activities_worker_test.exs30
219 files changed, 8059 insertions, 1998 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index aad28a2d8..b4bd59b43 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: elixir:1.8.1
+image: elixir:1.9.4
variables: &global_variables
POSTGRES_DB: pleroma_test
@@ -170,8 +170,7 @@ stop_review_app:
amd64:
stage: release
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0
+ image: elixir:1.10.3
only: &release-only
- stable@pleroma/pleroma
- develop@pleroma/pleroma
@@ -208,8 +207,7 @@ amd64-musl:
stage: release
artifacts: *release-artifacts
only: *release-only
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-alpine
+ image: elixir:1.10.3-alpine
cache: *release-cache
variables: *release-variables
before_script: &before-release-musl
@@ -225,8 +223,7 @@ arm:
only: *release-only
tags:
- arm32
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-arm
+ image: elixir:1.10.3
cache: *release-cache
variables: *release-variables
before_script: *before-release
@@ -238,8 +235,7 @@ arm-musl:
only: *release-only
tags:
- arm32
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-arm-alpine
+ image: elixir:1.10.3-alpine
cache: *release-cache
variables: *release-variables
before_script: *before-release-musl
@@ -251,8 +247,7 @@ arm64:
only: *release-only
tags:
- arm
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-arm64
+ image: elixir:1.10.3
cache: *release-cache
variables: *release-variables
before_script: *before-release
@@ -265,7 +260,7 @@ arm64-musl:
tags:
- arm
# TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-arm64-alpine
+ image: elixir:1.10.3-alpine
cache: *release-cache
variables: *release-variables
before_script: *before-release-musl
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a83de00f8..71963d206 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [unreleased]
### Changed
+- **Breaking:** Elixir >=1.9 is now required (was >= 1.8)
+- In Conversations, return only direct messages as `last_status`
+- Using the `only_media` filter on timelines will now exclude reblog media
+- MFR policy to set global expiration for all local Create activities
+- OGP rich media parser merged with TwitterCard
+- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
+
<details>
<summary>API Changes</summary>
@@ -24,6 +31,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking:** removed `with_move` parameter from notifications timeline.
### Added
+
+- Chats: Added support for federated chats. For details, see the docs.
- ActivityPub: Added support for existing AP ids for instances migrated from Mastodon.
- Instance: Add `background_image` to configuration and `/api/v1/instance`
- Instance: Extend `/api/v1/instance` with Pleroma-specific information.
@@ -34,17 +43,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: `filename_display_max_length` option to set filename truncate limit, if filename display enabled (0 = no limit).
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
- Mix task to create trusted OAuth App.
+- Mix task to reset MFA for user accounts
- Notifications: Added `follow_request` notification type.
- Added `:reject_deletes` group to SimplePolicy
- MRF (`EmojiStealPolicy`): New MRF Policy which allows to automatically download emojis from remote instances
+- Support pagination in emoji packs API (for packs and for files in pack)
+
<details>
<summary>API Changes</summary>
- Mastodon API: Extended `/api/v1/instance`.
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
- Mastodon API: Add support for filtering replies in public and home timelines
+- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`
- Admin API: endpoints for create/update/delete OAuth Apps.
- Admin API: endpoint for status view.
+- OTP: Add command to reload emoji packs
</details>
### Fixed
@@ -54,6 +68,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Filtering of push notifications on activities from blocked domains
- Resolving Peertube accounts with Webfinger
- `blob:` urls not being allowed by connect-src CSP
+- Mastodon API: fix `GET /api/v1/notifications` not returning the full result set
## [Unreleased (patch)]
diff --git a/README.md b/README.md
index 7fc1fd381..6ca3118fb 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,16 @@ Currently Pleroma is not packaged by any OS/Distros, but if you want to package
### Docker
While we don’t provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.
+### Compilation Troubleshooting
+If you ever encounter compilation issues during the updating of Pleroma, you can try these commands and see if they fix things:
+
+- `mix deps.clean --all`
+- `mix local.rebar`
+- `mix local.hex`
+- `rm -r _build`
+
+If you are not developing Pleroma, it is better to use the OTP release, which comes with everything precompiled.
+
## Documentation
- Latest Released revision: <https://docs.pleroma.social>
- Latest Git revision: <https://docs-develop.pleroma.social>
diff --git a/config/config.exs b/config/config.exs
index 9508ae077..a81ffcd3b 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -186,6 +186,7 @@ config :pleroma, :instance,
notify_email: "noreply@example.com",
description: "Pleroma: An efficient and flexible fediverse server",
background_image: "/images/city.jpg",
+ instance_thumbnail: "/instance/thumbnail.jpeg",
limit: 5_000,
chat_limit: 5_000,
remote_limit: 100_000,
@@ -209,7 +210,6 @@ config :pleroma, :instance,
Pleroma.Web.ActivityPub.Publisher
],
allow_relay: true,
- rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
public: true,
quarantined_instances: [],
managed_config: true,
@@ -220,8 +220,6 @@ config :pleroma, :instance,
"text/markdown",
"text/bbcode"
],
- mrf_transparency: true,
- mrf_transparency_exclusions: [],
autofollowed_nicknames: [],
max_pinned_statuses: 1,
attachment_links: false,
@@ -371,6 +369,8 @@ config :pleroma, :mrf_keyword,
config :pleroma, :mrf_subchain, match_actor: %{}
+config :pleroma, :mrf_activity_expiration, days: 365
+
config :pleroma, :mrf_vocabulary,
accept: [],
reject: []
@@ -385,7 +385,6 @@ config :pleroma, :rich_media,
ignore_tld: ["local", "localdomain", "lan"],
parsers: [
Pleroma.Web.RichMedia.Parsers.TwitterCard,
- Pleroma.Web.RichMedia.Parsers.OGP,
Pleroma.Web.RichMedia.Parsers.OEmbed
],
ttl_setters: [Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl]
@@ -406,6 +405,13 @@ config :pleroma, :media_proxy,
],
whitelist: []
+config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Http,
+ method: :purge,
+ headers: [],
+ options: []
+
+config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script, script_path: nil
+
config :pleroma, :chat, enabled: true
config :phoenix, :format_encoders, json: Jason
@@ -684,6 +690,11 @@ config :pleroma, :restrict_unauthenticated,
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
+config :pleroma, :mrf,
+ policies: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
+ transparency: true,
+ transparency_exclusions: []
+
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/config/description.exs b/config/description.exs
index 807c945e0..ded30e204 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -690,17 +690,6 @@ config :pleroma, :config_description, [
description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance"
},
%{
- key: :rewrite_policy,
- type: [:module, {:list, :module}],
- description:
- "A list of enabled MRF policies. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
- suggestions:
- Generator.list_modules_in_dir(
- "lib/pleroma/web/activity_pub/mrf",
- "Elixir.Pleroma.Web.ActivityPub.MRF."
- )
- },
- %{
key: :public,
type: :boolean,
description:
@@ -743,23 +732,6 @@ config :pleroma, :config_description, [
]
},
%{
- key: :mrf_transparency,
- label: "MRF transparency",
- type: :boolean,
- description:
- "Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
- },
- %{
- key: :mrf_transparency_exclusions,
- label: "MRF transparency exclusions",
- type: {:list, :string},
- description:
- "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
- suggestions: [
- "exclusion.com"
- ]
- },
- %{
key: :extended_nickname_format,
type: :boolean,
description:
@@ -1473,6 +1445,21 @@ config :pleroma, :config_description, [
},
%{
group: :pleroma,
+ key: :mrf_activity_expiration,
+ label: "MRF Activity Expiration Policy",
+ type: :group,
+ description: "Adds expiration to all local Create Note activities",
+ children: [
+ %{
+ key: :days,
+ type: :integer,
+ description: "Default global expiration time for all local Create activities (in days)",
+ suggestions: [90, 365]
+ }
+ ]
+ },
+ %{
+ group: :pleroma,
key: :mrf_subchain,
label: "MRF subchain",
type: :group,
@@ -1608,14 +1595,12 @@ config :pleroma, :config_description, [
# %{
# group: :pleroma,
# key: :mrf_user_allowlist,
- # type: :group,
+ # type: :map,
# description:
# "The keys in this section are the domain names that the policy should apply to." <>
# " Each key should be assigned a list of users that should be allowed through by their ActivityPub ID",
- # children: [
- # ["example.org": ["https://example.org/users/admin"]],
# suggestions: [
- # ["example.org": ["https://example.org/users/admin"]]
+ # %{"example.org" => ["https://example.org/users/admin"]}
# ]
# ]
# },
@@ -1638,6 +1623,31 @@ config :pleroma, :config_description, [
suggestions: ["https://example.com"]
},
%{
+ key: :invalidation,
+ type: :keyword,
+ descpiption: "",
+ suggestions: [
+ enabled: true,
+ provider: Pleroma.Web.MediaProxy.Invalidation.Script
+ ],
+ children: [
+ %{
+ key: :enabled,
+ type: :boolean,
+ description: "Enables invalidate media cache"
+ },
+ %{
+ key: :provider,
+ type: :module,
+ description: "Module which will be used to cache purge.",
+ suggestions: [
+ Pleroma.Web.MediaProxy.Invalidation.Script,
+ Pleroma.Web.MediaProxy.Invalidation.Http
+ ]
+ }
+ ]
+ },
+ %{
key: :proxy_opts,
type: :keyword,
description: "Options for Pleroma.ReverseProxy",
@@ -1711,6 +1721,45 @@ config :pleroma, :config_description, [
},
%{
group: :pleroma,
+ key: Pleroma.Web.MediaProxy.Invalidation.Http,
+ type: :group,
+ description: "HTTP invalidate settings",
+ children: [
+ %{
+ key: :method,
+ type: :atom,
+ description: "HTTP method of request. Default: :purge"
+ },
+ %{
+ key: :headers,
+ type: {:list, :tuple},
+ description: "HTTP headers of request.",
+ suggestions: [{"x-refresh", 1}]
+ },
+ %{
+ key: :options,
+ type: :keyword,
+ description: "Request options.",
+ suggestions: [params: %{ts: "xxx"}]
+ }
+ ]
+ },
+ %{
+ group: :pleroma,
+ key: Pleroma.Web.MediaProxy.Invalidation.Script,
+ type: :group,
+ description: "Script invalidate settings",
+ children: [
+ %{
+ key: :script_path,
+ type: :string,
+ description: "Path to shell script. Which will run purge cache.",
+ suggestions: ["./installation/nginx-cache-purge.sh.example"]
+ }
+ ]
+ },
+ %{
+ group: :pleroma,
key: :gopher,
type: :group,
description: "Gopher settings",
@@ -2091,9 +2140,7 @@ config :pleroma, :config_description, [
description:
"List of Rich Media parsers. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parsers.` part), but on adding custom module you need to use full name.",
suggestions: [
- Pleroma.Web.RichMedia.Parsers.MetaTagsParser,
Pleroma.Web.RichMedia.Parsers.OEmbed,
- Pleroma.Web.RichMedia.Parsers.OGP,
Pleroma.Web.RichMedia.Parsers.TwitterCard
]
},
@@ -3314,5 +3361,41 @@ config :pleroma, :config_description, [
suggestions: [false]
}
]
+ },
+ %{
+ group: :pleroma,
+ key: :mrf,
+ type: :group,
+ description: "General MRF settings",
+ children: [
+ %{
+ key: :policies,
+ type: [:module, {:list, :module}],
+ description:
+ "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
+ suggestions:
+ Generator.list_modules_in_dir(
+ "lib/pleroma/web/activity_pub/mrf",
+ "Elixir.Pleroma.Web.ActivityPub.MRF."
+ )
+ },
+ %{
+ key: :transparency,
+ label: "MRF transparency",
+ type: :boolean,
+ description:
+ "Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
+ },
+ %{
+ key: :transparency_exclusions,
+ label: "MRF transparency exclusions",
+ type: {:list, :string},
+ description:
+ "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
+ suggestions: [
+ "exclusion.com"
+ ]
+ }
+ ]
}
]
diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md
index 7b423653b..baf895d90 100644
--- a/docs/API/admin_api.md
+++ b/docs/API/admin_api.md
@@ -488,35 +488,39 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
### Change the user's email, password, display and settings-related fields
-- Params:
- - `email`
- - `password`
- - `name`
- - `bio`
- - `avatar`
- - `locked`
- - `no_rich_text`
- - `default_scope`
- - `banner`
- - `hide_follows`
- - `hide_followers`
- - `hide_followers_count`
- - `hide_follows_count`
- - `hide_favorites`
- - `allow_following_move`
- - `background`
- - `show_role`
- - `skip_thread_containment`
- - `fields`
- - `discoverable`
- - `actor_type`
-
-- Response:
+* Params:
+ * `email`
+ * `password`
+ * `name`
+ * `bio`
+ * `avatar`
+ * `locked`
+ * `no_rich_text`
+ * `default_scope`
+ * `banner`
+ * `hide_follows`
+ * `hide_followers`
+ * `hide_followers_count`
+ * `hide_follows_count`
+ * `hide_favorites`
+ * `allow_following_move`
+ * `background`
+ * `show_role`
+ * `skip_thread_containment`
+ * `fields`
+ * `discoverable`
+ * `actor_type`
+
+* Responses:
+
+Status: 200
```json
{"status": "success"}
```
+Status: 400
+
```json
{"errors":
{"actor_type": "is invalid"},
@@ -525,8 +529,10 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
}
```
+Status: 404
+
```json
-{"error": "Unable to update user."}
+{"error": "Not found"}
```
## `GET /api/pleroma/admin/reports`
@@ -1228,4 +1234,66 @@ Loads json generated from `config/descriptions.exs`.
- Response:
- On success: `204`, empty response
- On failure:
- - 400 Bad Request `"Invalid parameters"` when `status` is missing \ No newline at end of file
+ - 400 Bad Request `"Invalid parameters"` when `status` is missing
+
+## `GET /api/pleroma/admin/media_proxy_caches`
+
+### Get a list of all banned MediaProxy URLs in Cachex
+
+- Authentication: required
+- Params:
+- *optional* `page`: **integer** page number
+- *optional* `page_size`: **integer** number of log entries per page (default is `50`)
+
+- Response:
+
+``` json
+{
+ "urls": [
+ "http://example.com/media/a688346.jpg",
+ "http://example.com/media/fb1f4d.jpg"
+ ]
+}
+
+```
+
+## `POST /api/pleroma/admin/media_proxy_caches/delete`
+
+### Remove a banned MediaProxy URL from Cachex
+
+- Authentication: required
+- Params:
+ - `urls` (array)
+
+- Response:
+
+``` json
+{
+ "urls": [
+ "http://example.com/media/a688346.jpg",
+ "http://example.com/media/fb1f4d.jpg"
+ ]
+}
+
+```
+
+## `POST /api/pleroma/admin/media_proxy_caches/purge`
+
+### Purge a MediaProxy URL
+
+- Authentication: required
+- Params:
+ - `urls` (array)
+ - `ban` (boolean)
+
+- Response:
+
+``` json
+{
+ "urls": [
+ "http://example.com/media/a688346.jpg",
+ "http://example.com/media/fb1f4d.jpg"
+ ]
+}
+
+```
diff --git a/docs/API/chats.md b/docs/API/chats.md
new file mode 100644
index 000000000..aa6119670
--- /dev/null
+++ b/docs/API/chats.md
@@ -0,0 +1,248 @@
+# Chats
+
+Chats are a way to represent an IM-style conversation between two actors. They are not the same as direct messages and they are not `Status`es, even though they have a lot in common.
+
+## Why Chats?
+
+There are no 'visibility levels' in ActivityPub, their definition is purely a Mastodon convention. Direct Messaging between users on the fediverse has mostly been modeled by using ActivityPub addressing following Mastodon conventions on normal `Note` objects. In this case, a 'direct message' would be a message that has no followers addressed and also does not address the special public actor, but just the recipients in the `to` field. It would still be a `Note` and is presented with other `Note`s as a `Status` in the API.
+
+This is an awkward setup for a few reasons:
+
+- As DMs generally still follow the usual `Status` conventions, it is easy to accidentally pull somebody into a DM thread by mentioning them. (e.g. "I hate @badguy so much")
+- It is possible to go from a publicly addressed `Status` to a DM reply, back to public, then to a 'followers only' reply, and so on. This can be become very confusing, as it is unclear which user can see which part of the conversation.
+- The standard `Status` format of implicit addressing also leads to rather ugly results if you try to display the messages as a chat, because all the recipients are always mentioned by name in the message.
+- As direct messages are posted with the same api call (and usually same frontend component) as public messages, accidentally making a public message private or vice versa can happen easily. Client bugs can also lead to this, accidentally making private messages public.
+
+As a measure to improve this situation, the `Conversation` concept and related Pleroma extensions were introduced. While it made it possible to work around a few of the issues, many of the problems remained and it didn't see much adoption because it was too complicated to use correctly.
+
+## Chats explained
+For this reasons, Chats are a new and different entity, both in the API as well as in ActivityPub. A quick overview:
+
+- Chats are meant to represent an instant message conversation between two actors. For now these are only 1-on-1 conversations, but the other actor can be a group in the future.
+- Chat messages have the ActivityPub type `ChatMessage`. They are not `Note`s. Servers that don't understand them will just drop them.
+- The only addressing allowed in `ChatMessage`s is one single ActivityPub actor in the `to` field.
+- There's always only one Chat between two actors. If you start chatting with someone and later start a 'new' Chat, the old Chat will be continued.
+- `ChatMessage`s are posted with a different api, making it very hard to accidentally send a message to the wrong person.
+- `ChatMessage`s don't show up in the existing timelines.
+- Chats can never go from private to public. They are always private between the two actors.
+
+## Caveats
+
+- Chats are NOT E2E encrypted (yet). Security is still the same as email.
+
+## API
+
+In general, the way to send a `ChatMessage` is to first create a `Chat`, then post a message to that `Chat`. `Group`s will later be supported by making them a sub-type of `Account`.
+
+This is the overview of using the API. The API is also documented via OpenAPI, so you can view it and play with it by pointing SwaggerUI or a similar OpenAPI tool to `https://yourinstance.tld/api/openapi`.
+
+### Creating or getting a chat.
+
+To create or get an existing Chat for a certain recipient (identified by Account ID)
+you can call:
+
+`POST /api/v1/pleroma/chats/by-account-id/:account_id`
+
+The account id is the normal FlakeId of the user
+```
+POST /api/v1/pleroma/chats/by-account-id/someflakeid
+```
+
+If you already have the id of a chat, you can also use
+
+```
+GET /api/v1/pleroma/chats/:id
+```
+
+There will only ever be ONE Chat for you and a given recipient, so this call
+will return the same Chat if you already have one with that user.
+
+Returned data:
+
+```json
+{
+ "account": {
+ "id": "someflakeid",
+ "username": "somenick",
+ ...
+ },
+ "id" : "1",
+ "unread" : 2,
+ "last_message" : {...}, // The last message in that chat
+ "updated_at": "2020-04-21T15:11:46.000Z"
+}
+```
+
+### Marking a chat as read
+
+To mark a number of messages in a chat up to a certain message as read, you can use
+
+`POST /api/v1/pleroma/chats/:id/read`
+
+
+Parameters:
+- last_read_id: Given this id, all chat messages until this one will be marked as read. Required.
+
+
+Returned data:
+
+```json
+{
+ "account": {
+ "id": "someflakeid",
+ "username": "somenick",
+ ...
+ },
+ "id" : "1",
+ "unread" : 0,
+ "updated_at": "2020-04-21T15:11:46.000Z"
+}
+```
+
+### Marking a single chat message as read
+
+To set the `unread` property of a message to `false`
+
+`POST /api/v1/pleroma/chats/:id/messages/:message_id/read`
+
+Returned data:
+
+The modified chat message
+
+### Getting a list of Chats
+
+`GET /api/v1/pleroma/chats`
+
+This will return a list of chats that you have been involved in, sorted by their
+last update (so new chats will be at the top).
+
+Returned data:
+
+```json
+[
+ {
+ "account": {
+ "id": "someflakeid",
+ "username": "somenick",
+ ...
+ },
+ "id" : "1",
+ "unread" : 2,
+ "last_message" : {...}, // The last message in that chat
+ "updated_at": "2020-04-21T15:11:46.000Z"
+ }
+]
+```
+
+The recipient of messages that are sent to this chat is given by their AP ID.
+No pagination is implemented for now.
+
+### Getting the messages for a Chat
+
+For a given Chat id, you can get the associated messages with
+
+`GET /api/v1/pleroma/chats/:id/messages`
+
+This will return all messages, sorted by most recent to least recent. The usual
+pagination options are implemented.
+
+Returned data:
+
+```json
+[
+ {
+ "account_id": "someflakeid",
+ "chat_id": "1",
+ "content": "Check this out :firefox:",
+ "created_at": "2020-04-21T15:11:46.000Z",
+ "emojis": [
+ {
+ "shortcode": "firefox",
+ "static_url": "https://dontbulling.me/emoji/Firefox.gif",
+ "url": "https://dontbulling.me/emoji/Firefox.gif",
+ "visible_in_picker": false
+ }
+ ],
+ "id": "13",
+ "unread": true
+ },
+ {
+ "account_id": "someflakeid",
+ "chat_id": "1",
+ "content": "Whats' up?",
+ "created_at": "2020-04-21T15:06:45.000Z",
+ "emojis": [],
+ "id": "12",
+ "unread": false
+ }
+]
+```
+
+### Posting a chat message
+
+Posting a chat message for given Chat id works like this:
+
+`POST /api/v1/pleroma/chats/:id/messages`
+
+Parameters:
+- content: The text content of the message. Optional if media is attached.
+- media_id: The id of an upload that will be attached to the message.
+
+Currently, no formatting beyond basic escaping and emoji is implemented.
+
+Returned data:
+
+```json
+{
+ "account_id": "someflakeid",
+ "chat_id": "1",
+ "content": "Check this out :firefox:",
+ "created_at": "2020-04-21T15:11:46.000Z",
+ "emojis": [
+ {
+ "shortcode": "firefox",
+ "static_url": "https://dontbulling.me/emoji/Firefox.gif",
+ "url": "https://dontbulling.me/emoji/Firefox.gif",
+ "visible_in_picker": false
+ }
+ ],
+ "id": "13",
+ "unread": false
+}
+```
+
+### Deleting a chat message
+
+Deleting a chat message for given Chat id works like this:
+
+`DELETE /api/v1/pleroma/chats/:chat_id/messages/:message_id`
+
+Returned data is the deleted message.
+
+### Notifications
+
+There's a new `pleroma:chat_mention` notification, which has this form. It is not given out in the notifications endpoint by default, you need to explicitly request it with `include_types[]=pleroma:chat_mention`:
+
+```json
+{
+ "id": "someid",
+ "type": "pleroma:chat_mention",
+ "account": { ... } // User account of the sender,
+ "chat_message": {
+ "chat_id": "1",
+ "id": "10",
+ "content": "Hello",
+ "account_id": "someflakeid",
+ "unread": false
+ },
+ "created_at": "somedate"
+}
+```
+
+### Streaming
+
+There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field.
+
+### Web Push
+
+If you want to receive push messages for this type, you'll need to add the `pleroma:chat_mention` type to your alerts in the push subscription.
diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index 434ade9a4..be3c802af 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -230,3 +230,7 @@ Has theses additional parameters (which are the same as in Pleroma-API):
Has these additional fields under the `pleroma` object:
- `unread_count`: contains number unread notifications
+
+## Streaming
+
+There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field.
diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md
index 70d4755b7..b7eee5192 100644
--- a/docs/API/pleroma_api.md
+++ b/docs/API/pleroma_api.md
@@ -450,18 +450,44 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
* Response: JSON, list with updated files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message.
## `GET /api/pleroma/emoji/packs`
+
### Lists local custom emoji packs
+
* Method `GET`
* Authentication: not required
-* Params: None
-* Response: JSON, "ok" and 200 status and the JSON hashmap of pack name to pack contents
+* Params:
+ * `page`: page number for packs (default 1)
+ * `page_size`: page size for packs (default 50)
+* Response: `packs` key with JSON hashmap of pack name to pack contents and `count` key for count of packs.
+
+```json
+{
+ "packs": {
+ "pack_name": {...}, // pack contents
+ ...
+ },
+ "count": 0 // packs count
+}
+```
## `GET /api/pleroma/emoji/packs/:name`
+
### Get pack.json for the pack
+
* Method `GET`
* Authentication: not required
-* Params: None
-* Response: JSON, pack json with `files` and `pack` keys with 200 status or 404 if the pack does not exist
+* Params:
+ * `page`: page number for files (default 1)
+ * `page_size`: page size for files (default 30)
+* Response: JSON, pack json with `files`, `files_count` and `pack` keys with 200 status or 404 if the pack does not exist.
+
+```json
+{
+ "files": {...},
+ "files_count": 0, // emoji count in pack
+ "pack": {...}
+}
+```
## `GET /api/pleroma/emoji/packs/:name/archive`
### Requests a local pack archive from the instance
diff --git a/docs/administration/CLI_tasks/emoji.md b/docs/administration/CLI_tasks/emoji.md
index 3d524a52b..ddcb7e62c 100644
--- a/docs/administration/CLI_tasks/emoji.md
+++ b/docs/administration/CLI_tasks/emoji.md
@@ -44,3 +44,11 @@ Currently, only .zip archives are recognized as remote pack files and packs are
The manifest entry will either be written to a newly created `pack_name.json` file (pack name is asked in questions) or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
The file list will be written to the file specified previously, *replacing* that file. You _should_ check that the file list doesn't contain anything you don't need in the pack, that is, anything that is not an emoji (the whole pack is downloaded, but only emoji files are extracted).
+
+## Reload emoji packs
+
+```sh tab="OTP"
+./bin/pleroma_ctl emoji reload
+```
+
+This command only works with OTP releases.
diff --git a/docs/administration/CLI_tasks/user.md b/docs/administration/CLI_tasks/user.md
index afeb8d52f..1e6f4a8b4 100644
--- a/docs/administration/CLI_tasks/user.md
+++ b/docs/administration/CLI_tasks/user.md
@@ -135,6 +135,16 @@ mix pleroma.user reset_password <nickname>
```
+## Disable Multi Factor Authentication (MFA/2FA) for a user
+```sh tab="OTP"
+ ./bin/pleroma_ctl user reset_mfa <nickname>
+```
+
+```sh tab="From Source"
+mix pleroma.user reset_mfa <nickname>
+```
+
+
## Set the value of the given user's settings
```sh tab="OTP"
./bin/pleroma_ctl user set <nickname> [option ...]
diff --git a/docs/ap_extensions.md b/docs/ap_extensions.md
new file mode 100644
index 000000000..c4550a1ac
--- /dev/null
+++ b/docs/ap_extensions.md
@@ -0,0 +1,35 @@
+# ChatMessages
+
+ChatMessages are the messages sent in 1-on-1 chats. They are similar to
+`Note`s, but the addresing is done by having a single AP actor in the `to`
+field. Addressing multiple actors is not allowed. These messages are always
+private, there is no public version of them. They are created with a `Create`
+activity.
+
+Example:
+
+```json
+{
+ "actor": "http://2hu.gensokyo/users/raymoo",
+ "id": "http://2hu.gensokyo/objects/1",
+ "object": {
+ "attributedTo": "http://2hu.gensokyo/users/raymoo",
+ "content": "You expected a cute girl? Too bad.",
+ "id": "http://2hu.gensokyo/objects/2",
+ "published": "2020-02-12T14:08:20Z",
+ "to": [
+ "http://2hu.gensokyo/users/marisa"
+ ],
+ "type": "ChatMessage"
+ },
+ "published": "2018-02-12T14:08:20Z",
+ "to": [
+ "http://2hu.gensokyo/users/marisa"
+ ],
+ "type": "Create"
+}
+```
+
+This setup does not prevent multi-user chats, but these will have to go through
+a `Group`, which will be the recipient of the messages and then `Announce` them
+to the users in the `Group`.
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 505acb293..6759d5e93 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -36,30 +36,15 @@ To add configuration to your config file, you can copy it from the base config.
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
-* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
- * `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default).
- * `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production.
- * `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)).
- * `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive).
- * `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)).
- * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)).
- * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
- * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
- * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
- * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
- * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
- * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
-* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
+* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``.
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
-* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
-* `mrf_transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
older software for theses nicknames.
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
-* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses.
+* `attachment_links`: Set to true to enable automatically adding attachment link text to statuses.
* `welcome_message`: A message that will be send to a newly registered users as a direct message.
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`).
@@ -77,11 +62,30 @@ To add configuration to your config file, you can copy it from the base config.
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
+## Message rewrite facility
+
+### :mrf
+* `policies`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
+ * `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default).
+ * `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production.
+ * `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)).
+ * `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive).
+ * `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)).
+ * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)).
+ * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
+ * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
+ * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
+ * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
+ * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
+ * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
+* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
+* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
+
## Federation
### MRF policies
!!! note
- Configuring MRF policies is not enough for them to take effect. You have to enable them by specifying their module in `rewrite_policy` under [:instance](#instance) section.
+ Configuring MRF policies is not enough for them to take effect. You have to enable them by specifying their module in `policies` under [:mrf](#mrf) section.
#### :mrf_simple
* `media_removal`: List of instances to remove media from.
@@ -137,8 +141,9 @@ their ActivityPub ID.
An example:
```elixir
-config :pleroma, :mrf_user_allowlist,
- "example.org": ["https://example.org/users/admin"]
+config :pleroma, :mrf_user_allowlist, %{
+ "example.org" => ["https://example.org/users/admin"]
+}
```
#### :mrf_object_age
@@ -154,6 +159,10 @@ config :pleroma, :mrf_user_allowlist,
* `rejected_shortcodes`: Regex-list of shortcodes to reject
* `size_limit`: File size limit (in bytes), checked before an emoji is saved to the disk
+#### :mrf_activity_expiration
+
+* `days`: Default global expiration time for all local Create activities (in days)
+
### :activitypub
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
* `outgoing_blocks`: Whether to federate blocks to other instances
@@ -262,7 +271,7 @@ This section describe PWA manifest instance-specific values. Currently this opti
#### Pleroma.Web.MediaProxy.Invalidation.Script
-This strategy allow perform external bash script to purge cache.
+This strategy allow perform external shell script to purge cache.
Urls of attachments pass to script as arguments.
* `script_path`: path to external script.
@@ -278,8 +287,8 @@ config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script,
This strategy allow perform custom http request to purge cache.
* `method`: http method. default is `purge`
-* `headers`: http headers. default is empty
-* `options`: request options. default is empty
+* `headers`: http headers.
+* `options`: request options.
Example:
```elixir
@@ -963,13 +972,13 @@ config :pleroma, :database_config_whitelist, [
Restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
-* `timelines` - public and federated timelines
- * `local` - public timeline
+* `timelines`: public and federated timelines
+ * `local`: public timeline
* `federated`
-* `profiles` - user profiles
+* `profiles`: user profiles
* `local`
* `remote`
-* `activities` - statuses
+* `activities`: statuses
* `local`
* `remote`
diff --git a/docs/configuration/howto_theming_your_instance.md b/docs/configuration/howto_theming_your_instance.md
index d0daf5b25..cfa00f538 100644
--- a/docs/configuration/howto_theming_your_instance.md
+++ b/docs/configuration/howto_theming_your_instance.md
@@ -60,7 +60,7 @@ Example of `my-awesome-theme.json` where we add the name "My Awesome Theme"
### Set as default theme
-Now we can set the new theme as default in the [Pleroma FE configuration](General-tips-for-customizing-Pleroma-FE.md).
+Now we can set the new theme as default in the [Pleroma FE configuration](../../../frontend/CONFIGURATION).
Example of adding the new theme in the back-end config files
```elixir
diff --git a/docs/configuration/mrf.md b/docs/configuration/mrf.md
index d48d0cc99..31c66e098 100644
--- a/docs/configuration/mrf.md
+++ b/docs/configuration/mrf.md
@@ -34,9 +34,9 @@ config :pleroma, :instance,
To use `SimplePolicy`, you must enable it. Do so by adding the following to your `:instance` config object, so that it looks like this:
```elixir
-config :pleroma, :instance,
+config :pleroma, :mrf,
[...]
- rewrite_policy: Pleroma.Web.ActivityPub.MRF.SimplePolicy
+ policies: Pleroma.Web.ActivityPub.MRF.SimplePolicy
```
Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_simple` config object. These groups are:
@@ -58,8 +58,8 @@ Servers should be configured as lists.
This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`:
```elixir
-config :pleroma, :instance,
- rewrite_policy: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
+config :pleroma, :mrf,
+ policies: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
config :pleroma, :mrf_simple,
media_removal: ["illegalporn.biz"],
@@ -75,7 +75,7 @@ The effects of MRF policies can be very drastic. It is important to use this fun
## Writing your own MRF Policy
-As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `rewrite_policy` config setting.
+As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `policies` config setting.
For example, here is a sample policy module which rewrites all messages to "new message content":
@@ -125,8 +125,8 @@ end
If you save this file as `lib/pleroma/web/activity_pub/mrf/rewrite_policy.ex`, it will be included when you next rebuild Pleroma. You can enable it in the configuration like so:
```elixir
-config :pleroma, :instance,
- rewrite_policy: [
+config :pleroma, :mrf,
+ policies: [
Pleroma.Web.ActivityPub.MRF.SimplePolicy,
Pleroma.Web.ActivityPub.MRF.RewritePolicy
]
diff --git a/docs/configuration/storing_remote_media.md b/docs/configuration/storing_remote_media.md
index 7e91fe7d9..c01985d25 100644
--- a/docs/configuration/storing_remote_media.md
+++ b/docs/configuration/storing_remote_media.md
@@ -33,6 +33,6 @@ as soon as the post is received by your instance.
Add to your `prod.secret.exs`:
```
-config :pleroma, :instance,
- rewrite_policy: [Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
+config :pleroma, :mrf,
+ policies: [Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
```
diff --git a/docs/dev.md b/docs/dev.md
index f1b4cbf8b..9c749c17c 100644
--- a/docs/dev.md
+++ b/docs/dev.md
@@ -20,4 +20,4 @@ This document contains notes and guidelines for Pleroma developers.
## Auth-related configuration, OAuth consumer mode etc.
-See `Authentication` section of [`docs/configuration/cheatsheet.md`](docs/configuration/cheatsheet.md#authentication).
+See `Authentication` section of [the configuration cheatsheet](configuration/cheatsheet.md#authentication).
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 000000000..1a90d0a8d
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,26 @@
+# Introduction to Pleroma
+## What is Pleroma?
+Pleroma is a federated social networking platform, compatible with Mastodon and other ActivityPub implementations. It is free software licensed under the AGPLv3.
+It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
+It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
+One account on an instance is enough to talk to the entire fediverse!
+
+## How can I use it?
+
+Pleroma instances are already widely deployed, a list can be found at <https://the-federation.info/pleroma> and <https://fediverse.network/pleroma>.
+
+If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
+Installation instructions can be found in the installation section of these docs.
+
+## I got an account, now what?
+Great! Now you can explore the fediverse! Open the login page for your Pleroma instance (e.g. <https://pleroma.soykaf.com>) and login with your username and password. (If you don't have an account yet, click on Register)
+
+### Pleroma-FE
+The default front-end used by Pleroma is Pleroma-FE. You can find more information on what it is and how to use it in the [Introduction to Pleroma-FE](../frontend).
+
+### Mastodon interface
+If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too!
+Just add a "/web" after your instance url (e.g. <https://pleroma.soycaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC!
+The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation.
+
+Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md
index 2a9b8f6ff..c726d559f 100644
--- a/docs/installation/alpine_linux_en.md
+++ b/docs/installation/alpine_linux_en.md
@@ -225,10 +225,7 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
#### Further reading
-* [Backup your instance](../administration/backup.md)
-* [Hardening your instance](../configuration/hardening.md)
-* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
-* [Updating your instance](../administration/updating.md)
+{! backend/installation/further_reading.include !}
## Questions
diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md
index 8370986ad..bf9cfb488 100644
--- a/docs/installation/arch_linux_en.md
+++ b/docs/installation/arch_linux_en.md
@@ -200,10 +200,7 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
#### Further reading
-* [Backup your instance](../administration/backup.md)
-* [Hardening your instance](../configuration/hardening.md)
-* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
-* [Updating your instance](../administration/updating.md)
+{! backend/installation/further_reading.include !}
## Questions
diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md
index 2c20d521a..8ae5044b5 100644
--- a/docs/installation/debian_based_en.md
+++ b/docs/installation/debian_based_en.md
@@ -186,10 +186,7 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
#### Further reading
-* [Backup your instance](../administration/backup.md)
-* [Hardening your instance](../configuration/hardening.md)
-* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
-* [Updating your instance](../administration/updating.md)
+{! backend/installation/further_reading.include !}
## Questions
diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md
index 1e5a9be91..42e91cda7 100644
--- a/docs/installation/debian_based_jp.md
+++ b/docs/installation/debian_based_jp.md
@@ -175,10 +175,7 @@ sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress
#### その他の設定とカスタマイズ
-* [Backup your instance](../administration/backup.md)
-* [Hardening your instance](../configuration/hardening.md)
-* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
-* [Updating your instance](../administration/updating.md)
+{! backend/installation/further_reading.include !}
## 質問ある?
diff --git a/docs/installation/further_reading.include b/docs/installation/further_reading.include
new file mode 100644
index 000000000..46752c722
--- /dev/null
+++ b/docs/installation/further_reading.include
@@ -0,0 +1,5 @@
+* [How Federation Works/Why is my Federated Timeline empty?](https://blog.soykaf.com/post/how-federation-works/)
+* [Backup your instance](../administration/backup.md)
+* [Updating your instance](../administration/updating.md)
+* [Hardening your instance](../configuration/hardening.md)
+* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md
index 1e61373cc..32152aea7 100644
--- a/docs/installation/gentoo_en.md
+++ b/docs/installation/gentoo_en.md
@@ -283,10 +283,7 @@ If you opted to allow sudo for the `pleroma` user but would like to remove the a
#### Further reading
-* [Backup your instance](../administration/backup.md)
-* [Hardening your instance](../configuration/hardening.md)
-* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
-* [Updating your instance](../administration/updating.md)
+{! backend/installation/further_reading.include !}
## Questions
diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md
index 6a922a27e..3626acc69 100644
--- a/docs/installation/netbsd_en.md
+++ b/docs/installation/netbsd_en.md
@@ -196,3 +196,11 @@ incorrect timestamps. You should have ntpd running.
## Instances running NetBSD
* <https://catgirl.science>
+
+#### Further reading
+
+{! backend/installation/further_reading.include !}
+
+## Questions
+
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md
index e8c5d844c..5dbe24f75 100644
--- a/docs/installation/openbsd_en.md
+++ b/docs/installation/openbsd_en.md
@@ -242,3 +242,11 @@ If your instance is up and running, you can create your first user with administ
```
LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
```
+
+#### Further reading
+
+{! backend/installation/further_reading.include !}
+
+## Questions
+
+Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md
index 86135cd20..e4f822d1c 100644
--- a/docs/installation/otp_en.md
+++ b/docs/installation/otp_en.md
@@ -270,10 +270,7 @@ This will create an account withe the username of 'joeuser' with the email addre
## Further reading
-* [Backup your instance](../administration/backup.md)
-* [Hardening your instance](../configuration/hardening.md)
-* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
-* [Updating your instance](../administration/updating.md)
+{! backend/installation/further_reading.include !}
## Questions
diff --git a/docs/introduction.md b/docs/introduction.md
deleted file mode 100644
index a915c143c..000000000
--- a/docs/introduction.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# Introduction to Pleroma
-## What is Pleroma?
-Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
-It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
-It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
-One account on an instance is enough to talk to the entire fediverse!
-
-## How can I use it?
-
-Pleroma instances are already widely deployed, a list can be found at <http://distsn.org/pleroma-instances.html>. Information on all existing fediverse instances can be found at <https://fediverse.network/>.
-
-If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
-Installation instructions can be found in the installation section of these docs.
-
-## I got an account, now what?
-Great! Now you can explore the fediverse! Open the login page for your Pleroma instance (e.g. <https://pleroma.soykaf.com>) and login with your username and password. (If you don't have an account yet, click on Register)
-
-At this point you will have two columns in front of you.
-
-### Left column
-
-- first block: here you can see your avatar, your nickname and statistics (Statuses, Following, Followers). Clicking your profile pic will open your profile.
-Under that you have a text form which allows you to post new statuses. The number on the bottom of the text form is a character counter, every instance can have a different character limit (the default is 5000).
-If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person.
-Under the text form there are also several visibility options and there is the option to use rich text.
-Under that the icon on the left is for uploading media files and attach them to your post. There is also an emoji-picker and an option to post a poll.
-To post your status, simply press Submit.
-On the top right you will also see a wrench icon. This opens your personal settings.
-
-- second block: Here you can switch between the different timelines:
- - Timeline: all the people that you follow
- - Interactions: here you can switch between different timelines where there was interaction with your account. There is Mentions, Repeats and Favorites, and New follows
- - Direct Messages: these are the Direct Messages sent to you
- - Public Timeline: all the statutes from the local instance
- - The Whole Known Network: all public posts the instance knows about, both local and remote!
- - About: This isn't a Timeline but shows relevant info about the instance. You can find a list of the moderators and admins, Terms of Service, MRF policies and enabled features.
-- Optional third block: This is the Instance panel that can be activated, but is deactivated by default. It's fully customisable and by default has links to the pleroma-fe and Mastodon-fe.
-- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
-
-### Right column
-This is where the interesting stuff happens!
-Depending on the timeline you will see different statuses, but each status has a standard structure:
-
-- Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the reply-to status). Clicking on the profile pic will uncollapse the user's profile.
-- A `+` button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
-- An arrow icon allows you to open the status on the instance where it's originating from.
-- The text of the status, including mentions and attachements. If you click on a mention, it will automatically open the profile page of that person.
-- Three buttons (left to right): Reply, Repeat, Favorite. There is also a forth button, this is a dropdown menu for simple moderation like muting the conversation or, if you have moderation rights, delete the status from the server.
-
-### Top right
-
-- The magnifier icon opens the search screen where you can search for statuses, people and hashtags. It's also possible to import statusses from remote servers by pasting the url to the post in the search field.
-- The gear icon gives you general settings
-- If you have admin rights, you'll see an icon that opens the admin interface
-- The last icon is to log out
-
-### Bottom right
-On the bottom right you have a chatbox. Here you can communicate with people on the same instance in realtime. It is local-only, for now, but there are plans to make it extendable to the entire fediverse!
-
-### Mastodon interface
-If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too!
-Just add a "/web" after your instance url (e.g. <https://pleroma.soycaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC!
-The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation.
-
-Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
diff --git a/elixir_buildpack.config b/elixir_buildpack.config
index c23b08fb8..946408c12 100644
--- a/elixir_buildpack.config
+++ b/elixir_buildpack.config
@@ -1,2 +1,2 @@
-elixir_version=1.8.2
-erlang_version=21.3.7
+elixir_version=1.9.4
+erlang_version=22.3.4.1
diff --git a/installation/nginx-cache-purge.sh.example b/installation/nginx-cache-purge.sh.example
index b2915321c..5f6cbb128 100755
--- a/installation/nginx-cache-purge.sh.example
+++ b/installation/nginx-cache-purge.sh.example
@@ -13,7 +13,7 @@ CACHE_DIRECTORY="/tmp/pleroma-media-cache"
## $3 - (optional) the number of parallel processes to run for grep.
get_cache_files() {
local max_parallel=${3-16}
- find $2 -maxdepth 2 -type d | xargs -P $max_parallel -n 1 grep -E Rl "^KEY:.*$1" | sort -u
+ find $2 -maxdepth 2 -type d | xargs -P $max_parallel -n 1 grep -E -Rl "^KEY:.*$1" | sort -u
}
## Removes an item from the given cache zone.
@@ -37,4 +37,4 @@ purge() {
}
-purge $1
+purge $@
diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx
index 688be3e71..d301ca615 100644
--- a/installation/pleroma.nginx
+++ b/installation/pleroma.nginx
@@ -37,18 +37,17 @@ server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
- ssl_session_timeout 5m;
+ ssl_session_timeout 1d;
+ ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
+ ssl_session_tickets off;
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem;
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
- # Add TLSv1.0 to support older devices
- ssl_protocols TLSv1.2;
- # Uncomment line below if you want to support older devices (Before Android 4.4.2, IE 8, etc.)
- # ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
+ ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
- ssl_prefer_server_ciphers on;
+ ssl_prefer_server_ciphers off;
# In case of an old server with an OpenSSL version of 1.0.2 or below,
# leave only prime256v1 or comment out the following line.
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
index 5c9ef6904..d5129d410 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -52,6 +52,7 @@ defmodule Mix.Tasks.Pleroma.Config do
defp do_migrate_to_db(config_file) do
if File.exists?(config_file) do
+ shell_info("Migrating settings from file: #{Path.expand(config_file)}")
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
@@ -72,8 +73,7 @@ defmodule Mix.Tasks.Pleroma.Config do
group
|> Pleroma.Config.Loader.filter_group(settings)
|> Enum.each(fn {key, value} ->
- key = inspect(key)
- {:ok, _} = ConfigDB.update_or_create(%{group: inspect(group), key: key, value: value})
+ {:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
shell_info("Settings for key #{key} migrated.")
end)
@@ -131,12 +131,9 @@ defmodule Mix.Tasks.Pleroma.Config do
end
defp write(config, file) do
- value =
- config.value
- |> ConfigDB.from_binary()
- |> inspect(limit: :infinity)
+ value = inspect(config.value, limit: :infinity)
- IO.write(file, "config #{config.group}, #{config.key}, #{value}\r\n\r\n")
+ IO.write(file, "config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
config
end
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index 29a5fa99c..f4eaeac98 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -237,6 +237,12 @@ defmodule Mix.Tasks.Pleroma.Emoji do
end
end
+ def run(["reload"]) do
+ start_pleroma()
+ Pleroma.Emoji.reload()
+ IO.puts("Emoji packs have been reloaded.")
+ end
+
defp fetch_and_decode(from) do
with {:ok, json} <- fetch(from) do
Jason.decode!(json)
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 3635c02bc..bca7e87bf 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -144,6 +144,18 @@ defmodule Mix.Tasks.Pleroma.User do
end
end
+ def run(["reset_mfa", nickname]) do
+ start_pleroma()
+
+ with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
+ {:ok, _token} <- Pleroma.MFA.disable(user) do
+ shell_info("Multi-Factor Authentication disabled for #{user.nickname}")
+ else
+ _ ->
+ shell_error("No local user #{nickname}")
+ end
+ end
+
def run(["deactivate", nickname]) do
start_pleroma()
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 6213d0eb7..c3cea8d2a 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -24,16 +24,6 @@ defmodule Pleroma.Activity do
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
- # https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
- @mastodon_notification_types %{
- "Create" => "mention",
- "Follow" => ["follow", "follow_request"],
- "Announce" => "reblog",
- "Like" => "favourite",
- "Move" => "move",
- "EmojiReact" => "pleroma:emoji_reaction"
- }
-
schema "activities" do
field(:data, :map)
field(:local, :boolean, default: true)
@@ -41,6 +31,10 @@ defmodule Pleroma.Activity do
field(:recipients, {:array, :string}, default: [])
field(:thread_muted?, :boolean, virtual: true)
+ # A field that can be used if you need to join some kind of other
+ # id to order / paginate this field by
+ field(:pagination_id, :string, virtual: true)
+
# This is a fake relation,
# do not use outside of with_preloaded_user_actor/with_joined_user_actor
has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id)
@@ -300,32 +294,6 @@ defmodule Pleroma.Activity do
def follow_accepted?(_), do: false
- @spec mastodon_notification_type(Activity.t()) :: String.t() | nil
-
- for {ap_type, type} <- @mastodon_notification_types, not is_list(type) do
- def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),
- do: unquote(type)
- end
-
- def mastodon_notification_type(%Activity{data: %{"type" => "Follow"}} = activity) do
- if follow_accepted?(activity) do
- "follow"
- else
- "follow_request"
- end
- end
-
- def mastodon_notification_type(%Activity{}), do: nil
-
- @spec from_mastodon_notification_type(String.t()) :: String.t() | nil
- @doc "Converts Mastodon notification type to AR activity type"
- def from_mastodon_notification_type(type) do
- with {k, _v} <-
- Enum.find(@mastodon_notification_types, fn {_k, v} -> type in List.wrap(v) end) do
- k
- end
- end
-
def all_by_actor_and_id(actor, status_ids \\ [])
def all_by_actor_and_id(_actor, []), do: []
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 9d3d92b38..9615af122 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -39,7 +39,7 @@ defmodule Pleroma.Application do
Pleroma.HTML.compile_scrubbers()
Config.DeprecationWarnings.warn()
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
- Pleroma.Repo.check_migrations_applied!()
+ Pleroma.ApplicationRequirements.verify!()
setup_instrumenters()
load_custom_modules()
@@ -148,7 +148,8 @@ defmodule Pleroma.Application do
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
build_cachex("web_resp", limit: 2500),
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
- build_cachex("failed_proxy_url", limit: 2500)
+ build_cachex("failed_proxy_url", limit: 2500),
+ build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000)
]
end
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
new file mode 100644
index 000000000..88575a498
--- /dev/null
+++ b/lib/pleroma/application_requirements.ex
@@ -0,0 +1,107 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ApplicationRequirements do
+ @moduledoc """
+ The module represents the collection of validations to runs before start server.
+ """
+
+ defmodule VerifyError, do: defexception([:message])
+
+ import Ecto.Query
+
+ require Logger
+
+ @spec verify!() :: :ok | VerifyError.t()
+ def verify! do
+ :ok
+ |> check_migrations_applied!()
+ |> check_rum!()
+ |> handle_result()
+ end
+
+ defp handle_result(:ok), do: :ok
+ defp handle_result({:error, message}), do: raise(VerifyError, message: message)
+
+ # Checks for pending migrations.
+ #
+ def check_migrations_applied!(:ok) do
+ unless Pleroma.Config.get(
+ [:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
+ false
+ ) do
+ {_, res, _} =
+ Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
+ down_migrations =
+ Ecto.Migrator.migrations(repo)
+ |> Enum.reject(fn
+ {:up, _, _} -> true
+ {:down, _, _} -> false
+ end)
+
+ if length(down_migrations) > 0 do
+ down_migrations_text =
+ Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
+
+ Logger.error(
+ "The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
+ )
+
+ {:error, "Unapplied Migrations detected"}
+ else
+ :ok
+ end
+ end)
+
+ res
+ else
+ :ok
+ end
+ end
+
+ def check_migrations_applied!(result), do: result
+
+ # Checks for settings of RUM indexes.
+ #
+ defp check_rum!(:ok) do
+ {_, res, _} =
+ Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
+ migrate =
+ from(o in "columns",
+ where: o.table_name == "objects",
+ where: o.column_name == "fts_content"
+ )
+ |> repo.exists?(prefix: "information_schema")
+
+ setting = Pleroma.Config.get([:database, :rum_enabled], false)
+
+ do_check_rum!(setting, migrate)
+ end)
+
+ res
+ end
+
+ defp check_rum!(result), do: result
+
+ defp do_check_rum!(setting, migrate) do
+ case {setting, migrate} do
+ {true, false} ->
+ Logger.error(
+ "Use `RUM` index is enabled, but were not applied migrations for it.\nIf you want to start Pleroma anyway, set\nconfig :pleroma, :database, rum_enabled: false\nOtherwise apply the following migrations:\n`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`"
+ )
+
+ {:error, "Unapplied RUM Migrations detected"}
+
+ {false, true} ->
+ Logger.error(
+ "Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\nIf you want to use `RUM`, set\nconfig :pleroma, :database, rum_enabled: true\nOtherwise roll `RUM` migrations back.\n`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`"
+ )
+
+ {:error, "RUM Migrations detected"}
+
+ _ ->
+ :ok
+ end
+ end
+end
diff --git a/lib/pleroma/chat.ex b/lib/pleroma/chat.ex
new file mode 100644
index 000000000..24a86371e
--- /dev/null
+++ b/lib/pleroma/chat.ex
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Chat do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ @moduledoc """
+ Chat keeps a reference to ChatMessage conversations between a user and an recipient. The recipient can be a user (for now) or a group (not implemented yet).
+
+ It is a helper only, to make it easy to display a list of chats with other people, ordered by last bump. The actual messages are retrieved by querying the recipients of the ChatMessages.
+ """
+
+ @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
+
+ schema "chats" do
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+ field(:recipient, :string)
+
+ timestamps()
+ end
+
+ def changeset(struct, params) do
+ struct
+ |> cast(params, [:user_id, :recipient])
+ |> validate_change(:recipient, fn
+ :recipient, recipient ->
+ case User.get_cached_by_ap_id(recipient) do
+ nil -> [recipient: "must be an existing user"]
+ _ -> []
+ end
+ end)
+ |> validate_required([:user_id, :recipient])
+ |> unique_constraint(:user_id, name: :chats_user_id_recipient_index)
+ end
+
+ def get_by_id(id) do
+ __MODULE__
+ |> Repo.get(id)
+ end
+
+ def get(user_id, recipient) do
+ __MODULE__
+ |> Repo.get_by(user_id: user_id, recipient: recipient)
+ end
+
+ def get_or_create(user_id, recipient) do
+ %__MODULE__{}
+ |> changeset(%{user_id: user_id, recipient: recipient})
+ |> Repo.insert(
+ # Need to set something, otherwise we get nothing back at all
+ on_conflict: [set: [recipient: recipient]],
+ returning: true,
+ conflict_target: [:user_id, :recipient]
+ )
+ end
+
+ def bump_or_create(user_id, recipient) do
+ %__MODULE__{}
+ |> changeset(%{user_id: user_id, recipient: recipient})
+ |> Repo.insert(
+ on_conflict: [set: [updated_at: NaiveDateTime.utc_now()]],
+ returning: true,
+ conflict_target: [:user_id, :recipient]
+ )
+ end
+end
diff --git a/lib/pleroma/chat/message_reference.ex b/lib/pleroma/chat/message_reference.ex
new file mode 100644
index 000000000..131ae0186
--- /dev/null
+++ b/lib/pleroma/chat/message_reference.ex
@@ -0,0 +1,117 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Chat.MessageReference do
+ @moduledoc """
+ A reference that builds a relation between an AP chat message that a user can see and whether it has been seen
+ by them, or should be displayed to them. Used to build the chat view that is presented to the user.
+ """
+
+ use Ecto.Schema
+
+ alias Pleroma.Chat
+ alias Pleroma.Object
+ alias Pleroma.Repo
+
+ import Ecto.Changeset
+ import Ecto.Query
+
+ @primary_key {:id, FlakeId.Ecto.Type, autogenerate: true}
+
+ schema "chat_message_references" do
+ belongs_to(:object, Object)
+ belongs_to(:chat, Chat, type: FlakeId.Ecto.CompatType)
+
+ field(:unread, :boolean, default: true)
+
+ timestamps()
+ end
+
+ def changeset(struct, params) do
+ struct
+ |> cast(params, [:object_id, :chat_id, :unread])
+ |> validate_required([:object_id, :chat_id, :unread])
+ end
+
+ def get_by_id(id) do
+ __MODULE__
+ |> Repo.get(id)
+ |> Repo.preload(:object)
+ end
+
+ def delete(cm_ref) do
+ cm_ref
+ |> Repo.delete()
+ end
+
+ def delete_for_object(%{id: object_id}) do
+ from(cr in __MODULE__,
+ where: cr.object_id == ^object_id
+ )
+ |> Repo.delete_all()
+ end
+
+ def for_chat_and_object(%{id: chat_id}, %{id: object_id}) do
+ __MODULE__
+ |> Repo.get_by(chat_id: chat_id, object_id: object_id)
+ |> Repo.preload(:object)
+ end
+
+ def for_chat_query(chat) do
+ from(cr in __MODULE__,
+ where: cr.chat_id == ^chat.id,
+ order_by: [desc: :id],
+ preload: [:object]
+ )
+ end
+
+ def last_message_for_chat(chat) do
+ chat
+ |> for_chat_query()
+ |> limit(1)
+ |> Repo.one()
+ end
+
+ def create(chat, object, unread) do
+ params = %{
+ chat_id: chat.id,
+ object_id: object.id,
+ unread: unread
+ }
+
+ %__MODULE__{}
+ |> changeset(params)
+ |> Repo.insert()
+ end
+
+ def unread_count_for_chat(chat) do
+ chat
+ |> for_chat_query()
+ |> where([cmr], cmr.unread == true)
+ |> Repo.aggregate(:count)
+ end
+
+ def mark_as_read(cm_ref) do
+ cm_ref
+ |> changeset(%{unread: false})
+ |> Repo.update()
+ end
+
+ def set_all_seen_for_chat(chat, last_read_id \\ nil) do
+ query =
+ chat
+ |> for_chat_query()
+ |> exclude(:order_by)
+ |> exclude(:preload)
+ |> where([cmr], cmr.unread == true)
+
+ if last_read_id do
+ query
+ |> where([cmr], cmr.id <= ^last_read_id)
+ else
+ query
+ end
+ |> Repo.update_all(set: [unread: false])
+ end
+end
diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex
index 2b43d4c36..1a89d8895 100644
--- a/lib/pleroma/config/config_db.ex
+++ b/lib/pleroma/config/config_db.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.ConfigDB do
use Ecto.Schema
import Ecto.Changeset
- import Ecto.Query
+ import Ecto.Query, only: [select: 3]
import Pleroma.Web.Gettext
alias __MODULE__
@@ -14,16 +14,6 @@ defmodule Pleroma.ConfigDB do
@type t :: %__MODULE__{}
- @full_key_update [
- {:pleroma, :ecto_repos},
- {:quack, :meta},
- {:mime, :types},
- {:cors_plug, [:max_age, :methods, :expose, :headers]},
- {:auto_linker, :opts},
- {:swarm, :node_blacklist},
- {:logger, :backends}
- ]
-
@full_subkey_update [
{:pleroma, :assets, :mascots},
{:pleroma, :emoji, :groups},
@@ -32,14 +22,10 @@ defmodule Pleroma.ConfigDB do
{:pleroma, :mrf_keyword, :replace}
]
- @regex ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
-
- @delimiters ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
-
schema "config" do
- field(:key, :string)
- field(:group, :string)
- field(:value, :binary)
+ field(:key, Pleroma.EctoType.Config.Atom)
+ field(:group, Pleroma.EctoType.Config.Atom)
+ field(:value, Pleroma.EctoType.Config.BinaryValue)
field(:db, {:array, :string}, virtual: true, default: [])
timestamps()
@@ -51,10 +37,6 @@ defmodule Pleroma.ConfigDB do
|> select([c], {c.group, c.key, c.value})
|> Repo.all()
|> Enum.reduce([], fn {group, key, value}, acc ->
- group = ConfigDB.from_string(group)
- key = ConfigDB.from_string(key)
- value = from_binary(value)
-
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
end)
end
@@ -64,50 +46,41 @@ defmodule Pleroma.ConfigDB do
@spec changeset(ConfigDB.t(), map()) :: Changeset.t()
def changeset(config, params \\ %{}) do
- params = Map.put(params, :value, transform(params[:value]))
-
config
|> cast(params, [:key, :group, :value])
|> validate_required([:key, :group, :value])
|> unique_constraint(:key, name: :config_group_key_index)
end
- @spec create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
- def create(params) do
+ defp create(params) do
%ConfigDB{}
|> changeset(params)
|> Repo.insert()
end
- @spec update(ConfigDB.t(), map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
- def update(%ConfigDB{} = config, %{value: value}) do
+ defp update(%ConfigDB{} = config, %{value: value}) do
config
|> changeset(%{value: value})
|> Repo.update()
end
- @spec get_db_keys(ConfigDB.t()) :: [String.t()]
- def get_db_keys(%ConfigDB{} = config) do
- config.value
- |> ConfigDB.from_binary()
- |> get_db_keys(config.key)
- end
-
@spec get_db_keys(keyword(), any()) :: [String.t()]
def get_db_keys(value, key) do
- if Keyword.keyword?(value) do
- value |> Keyword.keys() |> Enum.map(&convert(&1))
- else
- [convert(key)]
- end
+ keys =
+ if Keyword.keyword?(value) do
+ Keyword.keys(value)
+ else
+ [key]
+ end
+
+ Enum.map(keys, &to_json_types(&1))
end
@spec merge_group(atom(), atom(), keyword(), keyword()) :: keyword()
def merge_group(group, key, old_value, new_value) do
- new_keys = to_map_set(new_value)
+ new_keys = to_mapset(new_value)
- intersect_keys =
- old_value |> to_map_set() |> MapSet.intersection(new_keys) |> MapSet.to_list()
+ intersect_keys = old_value |> to_mapset() |> MapSet.intersection(new_keys) |> MapSet.to_list()
merged_value = ConfigDB.merge(old_value, new_value)
@@ -120,12 +93,10 @@ defmodule Pleroma.ConfigDB do
[]
end)
|> List.flatten()
- |> Enum.reduce(merged_value, fn subkey, acc ->
- Keyword.put(acc, subkey, new_value[subkey])
- end)
+ |> Enum.reduce(merged_value, &Keyword.put(&2, &1, new_value[&1]))
end
- defp to_map_set(keyword) do
+ defp to_mapset(keyword) do
keyword
|> Keyword.keys()
|> MapSet.new()
@@ -159,57 +130,55 @@ defmodule Pleroma.ConfigDB do
@spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
def update_or_create(params) do
+ params = Map.put(params, :value, to_elixir_types(params[:value]))
search_opts = Map.take(params, [:group, :key])
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
- {:partial_update, true, config} <-
- {:partial_update, can_be_partially_updated?(config), config},
- old_value <- from_binary(config.value),
- transformed_value <- do_transform(params[:value]),
- {:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config},
- new_value <-
- merge_group(
- ConfigDB.from_string(config.group),
- ConfigDB.from_string(config.key),
- old_value,
- transformed_value
- ) do
- ConfigDB.update(config, %{value: new_value})
+ {_, true, config} <- {:partial_update, can_be_partially_updated?(config), config},
+ {_, true, config} <-
+ {:can_be_merged, is_list(params[:value]) and is_list(config.value), config} do
+ new_value = merge_group(config.group, config.key, config.value, params[:value])
+ update(config, %{value: new_value})
else
{reason, false, config} when reason in [:partial_update, :can_be_merged] ->
- ConfigDB.update(config, params)
+ update(config, params)
nil ->
- ConfigDB.create(params)
+ create(params)
end
end
defp can_be_partially_updated?(%ConfigDB{} = config), do: not only_full_update?(config)
- defp only_full_update?(%ConfigDB{} = config) do
- config_group = ConfigDB.from_string(config.group)
- config_key = ConfigDB.from_string(config.key)
-
- Enum.any?(@full_key_update, fn
- {group, key} when is_list(key) ->
- config_group == group and config_key in key
-
- {group, key} ->
- config_group == group and config_key == key
+ defp only_full_update?(%ConfigDB{group: group, key: key}) do
+ full_key_update = [
+ {:pleroma, :ecto_repos},
+ {:quack, :meta},
+ {:mime, :types},
+ {:cors_plug, [:max_age, :methods, :expose, :headers]},
+ {:auto_linker, :opts},
+ {:swarm, :node_blacklist},
+ {:logger, :backends}
+ ]
+
+ Enum.any?(full_key_update, fn
+ {s_group, s_key} ->
+ group == s_group and ((is_list(s_key) and key in s_key) or key == s_key)
end)
end
- @spec delete(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
+ @spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
+ def delete(%ConfigDB{} = config), do: Repo.delete(config)
+
def delete(params) do
search_opts = Map.delete(params, :subkeys)
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
{config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]},
- old_value <- from_binary(config.value),
- keys <- Enum.map(sub_keys, &do_transform_string(&1)),
- {:partial_remove, config, new_value} when new_value != [] <-
- {:partial_remove, config, Keyword.drop(old_value, keys)} do
- ConfigDB.update(config, %{value: new_value})
+ keys <- Enum.map(sub_keys, &string_to_elixir_types(&1)),
+ {_, config, new_value} when new_value != [] <-
+ {:partial_remove, config, Keyword.drop(config.value, keys)} do
+ update(config, %{value: new_value})
else
{:partial_remove, config, []} ->
Repo.delete(config)
@@ -225,37 +194,32 @@ defmodule Pleroma.ConfigDB do
end
end
- @spec from_binary(binary()) :: term()
- def from_binary(binary), do: :erlang.binary_to_term(binary)
-
- @spec from_binary_with_convert(binary()) :: any()
- def from_binary_with_convert(binary) do
- binary
- |> from_binary()
- |> do_convert()
+ @spec to_json_types(term()) :: map() | list() | boolean() | String.t()
+ def to_json_types(entity) when is_list(entity) do
+ Enum.map(entity, &to_json_types/1)
end
- @spec from_string(String.t()) :: atom() | no_return()
- def from_string(string), do: do_transform_string(string)
+ def to_json_types(%Regex{} = entity), do: inspect(entity)
- @spec convert(any()) :: any()
- def convert(entity), do: do_convert(entity)
-
- defp do_convert(entity) when is_list(entity) do
- for v <- entity, into: [], do: do_convert(v)
+ def to_json_types(entity) when is_map(entity) do
+ Map.new(entity, fn {k, v} -> {to_json_types(k), to_json_types(v)} end)
end
- defp do_convert(%Regex{} = entity), do: inspect(entity)
+ def to_json_types({:args, args}) when is_list(args) do
+ arguments =
+ Enum.map(args, fn
+ arg when is_tuple(arg) -> inspect(arg)
+ arg -> to_json_types(arg)
+ end)
- defp do_convert(entity) when is_map(entity) do
- for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)}
+ %{"tuple" => [":args", arguments]}
end
- defp do_convert({:proxy_url, {type, :localhost, port}}) do
- %{"tuple" => [":proxy_url", %{"tuple" => [do_convert(type), "localhost", port]}]}
+ def to_json_types({:proxy_url, {type, :localhost, port}}) do
+ %{"tuple" => [":proxy_url", %{"tuple" => [to_json_types(type), "localhost", port]}]}
end
- defp do_convert({:proxy_url, {type, host, port}}) when is_tuple(host) do
+ def to_json_types({:proxy_url, {type, host, port}}) when is_tuple(host) do
ip =
host
|> :inet_parse.ntoa()
@@ -264,66 +228,64 @@ defmodule Pleroma.ConfigDB do
%{
"tuple" => [
":proxy_url",
- %{"tuple" => [do_convert(type), ip, port]}
+ %{"tuple" => [to_json_types(type), ip, port]}
]
}
end
- defp do_convert({:proxy_url, {type, host, port}}) do
+ def to_json_types({:proxy_url, {type, host, port}}) do
%{
"tuple" => [
":proxy_url",
- %{"tuple" => [do_convert(type), to_string(host), port]}
+ %{"tuple" => [to_json_types(type), to_string(host), port]}
]
}
end
- defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
+ def to_json_types({:partial_chain, entity}),
+ do: %{"tuple" => [":partial_chain", inspect(entity)]}
- defp do_convert(entity) when is_tuple(entity) do
+ def to_json_types(entity) when is_tuple(entity) do
value =
entity
|> Tuple.to_list()
- |> do_convert()
+ |> to_json_types()
%{"tuple" => value}
end
- defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
+ def to_json_types(entity) when is_binary(entity), do: entity
+
+ def to_json_types(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
entity
end
- defp do_convert(entity)
- when is_atom(entity) and entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
+ def to_json_types(entity) when entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
":#{entity}"
end
- defp do_convert(entity) when is_atom(entity), do: inspect(entity)
+ def to_json_types(entity) when is_atom(entity), do: inspect(entity)
- defp do_convert(entity) when is_binary(entity), do: entity
+ @spec to_elixir_types(boolean() | String.t() | map() | list()) :: term()
+ def to_elixir_types(%{"tuple" => [":args", args]}) when is_list(args) do
+ arguments =
+ Enum.map(args, fn arg ->
+ if String.contains?(arg, ["{", "}"]) do
+ {elem, []} = Code.eval_string(arg)
+ elem
+ else
+ to_elixir_types(arg)
+ end
+ end)
- @spec transform(any()) :: binary() | no_return()
- def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do
- entity
- |> do_transform()
- |> to_binary()
+ {:args, arguments}
end
- def transform(entity), do: to_binary(entity)
-
- @spec transform_with_out_binary(any()) :: any()
- def transform_with_out_binary(entity), do: do_transform(entity)
-
- @spec to_binary(any()) :: binary()
- def to_binary(entity), do: :erlang.term_to_binary(entity)
-
- defp do_transform(%Regex{} = entity), do: entity
-
- defp do_transform(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
- {:proxy_url, {do_transform_string(type), parse_host(host), port}}
+ def to_elixir_types(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
+ {:proxy_url, {string_to_elixir_types(type), parse_host(host), port}}
end
- defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
+ def to_elixir_types(%{"tuple" => [":partial_chain", entity]}) do
{partial_chain, []} =
entity
|> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
@@ -332,25 +294,51 @@ defmodule Pleroma.ConfigDB do
{:partial_chain, partial_chain}
end
- defp do_transform(%{"tuple" => entity}) do
- Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
+ def to_elixir_types(%{"tuple" => entity}) do
+ Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1)))
end
- defp do_transform(entity) when is_map(entity) do
- for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)}
+ def to_elixir_types(entity) when is_map(entity) do
+ Map.new(entity, fn {k, v} -> {to_elixir_types(k), to_elixir_types(v)} end)
end
- defp do_transform(entity) when is_list(entity) do
- for v <- entity, into: [], do: do_transform(v)
+ def to_elixir_types(entity) when is_list(entity) do
+ Enum.map(entity, &to_elixir_types/1)
end
- defp do_transform(entity) when is_binary(entity) do
+ def to_elixir_types(entity) when is_binary(entity) do
entity
|> String.trim()
- |> do_transform_string()
+ |> string_to_elixir_types()
end
- defp do_transform(entity), do: entity
+ def to_elixir_types(entity), do: entity
+
+ @spec string_to_elixir_types(String.t()) ::
+ atom() | Regex.t() | module() | String.t() | no_return()
+ def string_to_elixir_types("~r" <> _pattern = regex) do
+ pattern =
+ ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
+
+ delimiters = ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
+
+ with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
+ Regex.named_captures(pattern, regex),
+ {:ok, {leading, closing}} <- find_valid_delimiter(delimiters, pattern, regex_delimiter),
+ {result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
+ result
+ end
+ end
+
+ def string_to_elixir_types(":" <> atom), do: String.to_atom(atom)
+
+ def string_to_elixir_types(value) do
+ if module_name?(value) do
+ String.to_existing_atom("Elixir." <> value)
+ else
+ value
+ end
+ end
defp parse_host("localhost"), do: :localhost
@@ -387,27 +375,8 @@ defmodule Pleroma.ConfigDB do
end
end
- defp do_transform_string("~r" <> _pattern = regex) do
- with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
- Regex.named_captures(@regex, regex),
- {:ok, {leading, closing}} <- find_valid_delimiter(@delimiters, pattern, regex_delimiter),
- {result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
- result
- end
- end
-
- defp do_transform_string(":" <> atom), do: String.to_atom(atom)
-
- defp do_transform_string(value) do
- if is_module_name?(value) do
- String.to_existing_atom("Elixir." <> value)
- else
- value
- end
- end
-
- @spec is_module_name?(String.t()) :: boolean()
- def is_module_name?(string) do
+ @spec module_name?(String.t()) :: boolean()
+ def module_name?(string) do
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
string in ["Oban", "Ueberauth", "ExSyslogger"]
end
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index c39a8984b..0a6c724fb 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -3,10 +3,25 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.DeprecationWarnings do
+ alias Pleroma.Config
+
require Logger
+ alias Pleroma.Config
+
+ @type config_namespace() :: [atom()]
+ @type config_map() :: {config_namespace(), config_namespace(), String.t()}
+
+ @mrf_config_map [
+ {[:instance, :rewrite_policy], [:mrf, :policies],
+ "\n* `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies`"},
+ {[:instance, :mrf_transparency], [:mrf, :transparency],
+ "\n* `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency`"},
+ {[:instance, :mrf_transparency_exclusions], [:mrf, :transparency_exclusions],
+ "\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
+ ]
def check_hellthread_threshold do
- if Pleroma.Config.get([:mrf_hellthread, :threshold]) do
+ if Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
!!!DEPRECATION WARNING!!!
You are using the old configuration mechanism for the hellthread filter. Please check config.md.
@@ -14,7 +29,59 @@ defmodule Pleroma.Config.DeprecationWarnings do
end
end
+ def mrf_user_allowlist do
+ config = Config.get(:mrf_user_allowlist)
+
+ if config && Enum.any?(config, fn {k, _} -> is_atom(k) end) do
+ rewritten =
+ Enum.reduce(Config.get(:mrf_user_allowlist), Map.new(), fn {k, v}, acc ->
+ Map.put(acc, to_string(k), v)
+ end)
+
+ Config.put(:mrf_user_allowlist, rewritten)
+
+ Logger.error("""
+ !!!DEPRECATION WARNING!!!
+ As of Pleroma 2.0.7, the `mrf_user_allowlist` setting changed of format.
+ Pleroma 2.1 will remove support for the old format. Please change your configuration to match this:
+
+ config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)}
+ """)
+ end
+ end
+
def warn do
check_hellthread_threshold()
+ mrf_user_allowlist()
+ check_old_mrf_config()
+ end
+
+ def check_old_mrf_config do
+ warning_preface = """
+ !!!DEPRECATION WARNING!!!
+ Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later:
+ """
+
+ move_namespace_and_warn(@mrf_config_map, warning_preface)
+ end
+
+ @spec move_namespace_and_warn([config_map()], String.t()) :: :ok
+ def move_namespace_and_warn(config_map, warning_preface) do
+ warning =
+ Enum.reduce(config_map, "", fn
+ {old, new, err_msg}, acc ->
+ old_config = Config.get(old)
+
+ if old_config do
+ Config.put(new, old_config)
+ acc <> err_msg
+ else
+ acc
+ end
+ end)
+
+ if warning != "" do
+ Logger.warn(warning_preface <> warning)
+ end
end
end
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index c02b70e96..eb86b8ff4 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -28,10 +28,6 @@ defmodule Pleroma.Config.TransferTask do
{:pleroma, Pleroma.Captcha, [:seconds_valid]},
{:pleroma, Pleroma.Upload, [:proxy_remote]},
{:pleroma, :instance, [:upload_limit]},
- {:pleroma, :email_notifications, [:digest]},
- {:pleroma, :oauth2, [:clean_expired_tokens]},
- {:pleroma, Pleroma.ActivityExpiration, [:enabled]},
- {:pleroma, Pleroma.ScheduledActivity, [:enabled]},
{:pleroma, :gopher, [:enabled]}
]
@@ -48,7 +44,7 @@ defmodule Pleroma.Config.TransferTask do
{logger, other} =
(Repo.all(ConfigDB) ++ deleted_settings)
- |> Enum.map(&transform_and_merge/1)
+ |> Enum.map(&merge_with_default/1)
|> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end)
logger
@@ -92,11 +88,7 @@ defmodule Pleroma.Config.TransferTask do
end
end
- defp transform_and_merge(%{group: group, key: key, value: value} = setting) do
- group = ConfigDB.from_string(group)
- key = ConfigDB.from_string(key)
- value = ConfigDB.from_binary(value)
-
+ defp merge_with_default(%{group: group, key: key, value: value} = setting) do
default = Config.Holder.default_config(group, key)
merged =
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index ce7bd2396..8bc3e85d6 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -162,10 +162,13 @@ defmodule Pleroma.Conversation.Participation do
for_user(user, params)
|> Enum.map(fn participation ->
activity_id =
- ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
- user: user,
- blocking_user: user
- })
+ ActivityPub.fetch_latest_direct_activity_id_for_context(
+ participation.conversation.ap_id,
+ %{
+ user: user,
+ blocking_user: user
+ }
+ )
%{
participation
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/date_time.ex
index 4f412fcde..d852c0abd 100644
--- a/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/date_time.ex
@@ -1,4 +1,8 @@
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime do
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime do
@moduledoc """
The AP standard defines the date fields in AP as xsd:DateTime. Elixir's
DateTime can't parse this, but it can parse the related iso8601. This
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/object_id.ex
index f71f76370..8034235b0 100644
--- a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/object_id.ex
@@ -1,4 +1,8 @@
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID do
use Ecto.Type
def type, do: :string
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
new file mode 100644
index 000000000..205527a96
--- /dev/null
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/recipients.ex
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients do
+ use Ecto.Type
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID
+
+ def type, do: {:array, ObjectID}
+
+ def cast(object) when is_binary(object) do
+ cast([object])
+ end
+
+ def cast(data) when is_list(data) do
+ data
+ |> Enum.reduce_while({:ok, []}, fn element, {:ok, list} ->
+ case ObjectID.cast(element) do
+ {:ok, id} ->
+ {:cont, {:ok, [id | list]}}
+
+ _ ->
+ {:halt, :error}
+ end
+ end)
+ end
+
+ def cast(_) do
+ :error
+ end
+
+ def dump(data) do
+ {:ok, data}
+ end
+
+ def load(data) do
+ {:ok, data}
+ end
+end
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex
new file mode 100644
index 000000000..7f0405c7b
--- /dev/null
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/safe_text.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.SafeText do
+ use Ecto.Type
+
+ alias Pleroma.HTML
+
+ def type, do: :string
+
+ def cast(str) when is_binary(str) do
+ {:ok, HTML.filter_tags(str)}
+ end
+
+ def cast(_), do: :error
+
+ def dump(data) do
+ {:ok, data}
+ end
+
+ def load(data) do
+ {:ok, data}
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/uri.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/uri.ex
index 24845bcc0..2054c26be 100644
--- a/lib/pleroma/web/activity_pub/object_validators/types/uri.ex
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/uri.ex
@@ -1,4 +1,8 @@
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Uri do
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Uri do
use Ecto.Type
def type, do: :string
diff --git a/lib/pleroma/ecto_type/config/atom.ex b/lib/pleroma/ecto_type/config/atom.ex
new file mode 100644
index 000000000..df565d432
--- /dev/null
+++ b/lib/pleroma/ecto_type/config/atom.ex
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.Config.Atom do
+ use Ecto.Type
+
+ def type, do: :atom
+
+ def cast(key) when is_atom(key) do
+ {:ok, key}
+ end
+
+ def cast(key) when is_binary(key) do
+ {:ok, Pleroma.ConfigDB.string_to_elixir_types(key)}
+ end
+
+ def cast(_), do: :error
+
+ def load(key) do
+ {:ok, Pleroma.ConfigDB.string_to_elixir_types(key)}
+ end
+
+ def dump(key) when is_atom(key), do: {:ok, inspect(key)}
+ def dump(_), do: :error
+end
diff --git a/lib/pleroma/ecto_type/config/binary_value.ex b/lib/pleroma/ecto_type/config/binary_value.ex
new file mode 100644
index 000000000..bbd2608c5
--- /dev/null
+++ b/lib/pleroma/ecto_type/config/binary_value.ex
@@ -0,0 +1,27 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.Config.BinaryValue do
+ use Ecto.Type
+
+ def type, do: :term
+
+ def cast(value) when is_binary(value) do
+ if String.valid?(value) do
+ {:ok, value}
+ else
+ {:ok, :erlang.binary_to_term(value)}
+ end
+ end
+
+ def cast(value), do: {:ok, value}
+
+ def load(value) when is_binary(value) do
+ {:ok, :erlang.binary_to_term(value)}
+ end
+
+ def dump(value) do
+ {:ok, :erlang.term_to_binary(value)}
+ end
+end
diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex
index 14a5185be..787ff8141 100644
--- a/lib/pleroma/emoji/pack.ex
+++ b/lib/pleroma/emoji/pack.ex
@@ -1,6 +1,7 @@
defmodule Pleroma.Emoji.Pack do
- @derive {Jason.Encoder, only: [:files, :pack]}
+ @derive {Jason.Encoder, only: [:files, :pack, :files_count]}
defstruct files: %{},
+ files_count: 0,
pack_file: nil,
path: nil,
pack: %{},
@@ -8,6 +9,7 @@ defmodule Pleroma.Emoji.Pack do
@type t() :: %__MODULE__{
files: %{String.t() => Path.t()},
+ files_count: non_neg_integer(),
pack_file: Path.t(),
path: Path.t(),
pack: map(),
@@ -16,7 +18,7 @@ defmodule Pleroma.Emoji.Pack do
alias Pleroma.Emoji
- @spec create(String.t()) :: :ok | {:error, File.posix()} | {:error, :empty_values}
+ @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
def create(name) do
with :ok <- validate_not_empty([name]),
dir <- Path.join(emoji_path(), name),
@@ -26,10 +28,27 @@ defmodule Pleroma.Emoji.Pack do
end
end
- @spec show(String.t()) :: {:ok, t()} | {:error, atom()}
- def show(name) do
+ defp paginate(entities, 1, page_size), do: Enum.take(entities, page_size)
+
+ defp paginate(entities, page, page_size) do
+ entities
+ |> Enum.chunk_every(page_size)
+ |> Enum.at(page - 1)
+ end
+
+ @spec show(keyword()) :: {:ok, t()} | {:error, atom()}
+ def show(opts) do
+ name = opts[:name]
+
with :ok <- validate_not_empty([name]),
{:ok, pack} <- load_pack(name) do
+ shortcodes =
+ pack.files
+ |> Map.keys()
+ |> paginate(opts[:page], opts[:page_size])
+
+ pack = Map.put(pack, :files, Map.take(pack.files, shortcodes))
+
{:ok, validate_pack(pack)}
end
end
@@ -120,10 +139,10 @@ defmodule Pleroma.Emoji.Pack do
end
end
- @spec list_local() :: {:ok, map()}
- def list_local do
+ @spec list_local(keyword()) :: {:ok, map(), non_neg_integer()}
+ def list_local(opts) do
with {:ok, results} <- list_packs_dir() do
- packs =
+ all_packs =
results
|> Enum.map(fn name ->
case load_pack(name) do
@@ -132,9 +151,13 @@ defmodule Pleroma.Emoji.Pack do
end
end)
|> Enum.reject(&is_nil/1)
+
+ packs =
+ all_packs
+ |> paginate(opts[:page], opts[:page_size])
|> Map.new(fn pack -> {pack.name, validate_pack(pack)} end)
- {:ok, packs}
+ {:ok, packs, length(all_packs)}
end
end
@@ -146,7 +169,7 @@ defmodule Pleroma.Emoji.Pack do
end
end
- @spec download(String.t(), String.t(), String.t()) :: :ok | {:error, atom()}
+ @spec download(String.t(), String.t(), String.t()) :: {:ok, t()} | {:error, atom()}
def download(name, url, as) do
uri = url |> String.trim() |> URI.parse()
@@ -197,7 +220,12 @@ defmodule Pleroma.Emoji.Pack do
|> Map.put(:path, Path.dirname(pack_file))
|> Map.put(:name, name)
- {:ok, pack}
+ files_count =
+ pack.files
+ |> Map.keys()
+ |> length()
+
+ {:ok, Map.put(pack, :files_count, files_count)}
else
{:error, :not_found}
end
@@ -296,7 +324,9 @@ defmodule Pleroma.Emoji.Pack do
# Otherwise, they'd have to download it from external-src
pack.pack["share-files"] &&
Enum.all?(pack.files, fn {_, file} ->
- File.exists?(Path.join(pack.path, file))
+ pack.path
+ |> Path.join(file)
+ |> File.exists?()
end)
end
@@ -440,7 +470,7 @@ defmodule Pleroma.Emoji.Pack do
# with the API so it should be sufficient
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
- {:ok, results}
+ {:ok, Enum.sort(results)}
else
{:create_dir, {:error, e}} -> {:error, :create_dir, e}
{:ls, {:error, e}} -> {:error, :ls, e}
diff --git a/lib/pleroma/migration_helper/notification_backfill.ex b/lib/pleroma/migration_helper/notification_backfill.ex
new file mode 100644
index 000000000..b3770307a
--- /dev/null
+++ b/lib/pleroma/migration_helper/notification_backfill.ex
@@ -0,0 +1,85 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.MigrationHelper.NotificationBackfill do
+ alias Pleroma.Notification
+ alias Pleroma.Object
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ import Ecto.Query
+
+ def fill_in_notification_types do
+ query =
+ from(n in Pleroma.Notification,
+ where: is_nil(n.type),
+ preload: :activity
+ )
+
+ query
+ |> Repo.chunk_stream(100)
+ |> Enum.each(fn notification ->
+ type =
+ notification.activity
+ |> type_from_activity()
+
+ notification
+ |> Notification.changeset(%{type: type})
+ |> Repo.update()
+ end)
+ end
+
+ # This is copied over from Notifications to keep this stable.
+ defp type_from_activity(%{data: %{"type" => type}} = activity) do
+ case type do
+ "Follow" ->
+ accepted_function = fn activity ->
+ with %User{} = follower <- User.get_by_ap_id(activity.data["actor"]),
+ %User{} = followed <- User.get_by_ap_id(activity.data["object"]) do
+ Pleroma.FollowingRelationship.following?(follower, followed)
+ end
+ end
+
+ if accepted_function.(activity) do
+ "follow"
+ else
+ "follow_request"
+ end
+
+ "Announce" ->
+ "reblog"
+
+ "Like" ->
+ "favourite"
+
+ "Move" ->
+ "move"
+
+ "EmojiReact" ->
+ "pleroma:emoji_reaction"
+
+ # Compatibility with old reactions
+ "EmojiReaction" ->
+ "pleroma:emoji_reaction"
+
+ "Create" ->
+ activity
+ |> type_from_activity_object()
+
+ t ->
+ raise "No notification type for activity type #{t}"
+ end
+ end
+
+ defp type_from_activity_object(%{data: %{"type" => "Create", "object" => %{}}}), do: "mention"
+
+ defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
+ object = Object.get_by_ap_id(activity.data["object"])
+
+ case object && object.data["type"] do
+ "ChatMessage" -> "pleroma:chat_mention"
+ _ -> "mention"
+ end
+ end
+end
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 7eca55ac9..9ee9606be 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -30,12 +30,29 @@ defmodule Pleroma.Notification do
schema "notifications" do
field(:seen, :boolean, default: false)
+ # This is an enum type in the database. If you add a new notification type,
+ # remember to add a migration to add it to the `notifications_type` enum
+ # as well.
+ field(:type, :string)
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
timestamps()
end
+ def update_notification_type(user, activity) do
+ with %__MODULE__{} = notification <-
+ Repo.get_by(__MODULE__, user_id: user.id, activity_id: activity.id) do
+ type =
+ activity
+ |> type_from_activity()
+
+ notification
+ |> changeset(%{type: type})
+ |> Repo.update()
+ end
+ end
+
@spec unread_notifications_count(User.t()) :: integer()
def unread_notifications_count(%User{id: user_id}) do
from(q in __MODULE__,
@@ -44,9 +61,21 @@ defmodule Pleroma.Notification do
|> Repo.aggregate(:count, :id)
end
+ @notification_types ~w{
+ favourite
+ follow
+ follow_request
+ mention
+ move
+ pleroma:chat_mention
+ pleroma:emoji_reaction
+ reblog
+ }
+
def changeset(%Notification{} = notification, attrs) do
notification
- |> cast(attrs, [:seen])
+ |> cast(attrs, [:seen, :type])
+ |> validate_inclusion(:type, @notification_types)
end
@spec last_read_query(User.t()) :: Ecto.Queryable.t()
@@ -137,8 +166,16 @@ defmodule Pleroma.Notification do
query
|> join(:left, [n, a], mutated_activity in Pleroma.Activity,
on:
- fragment("?->>'context'", a.data) ==
- fragment("?->>'context'", mutated_activity.data) and
+ fragment(
+ "COALESCE((?->'object')->>'id', ?->>'object')",
+ a.data,
+ a.data
+ ) ==
+ fragment(
+ "COALESCE((?->'object')->>'id', ?->>'object')",
+ mutated_activity.data,
+ mutated_activity.data
+ ) and
fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
fragment("?->>'type'", mutated_activity.data) == "Create",
as: :mutated_activity
@@ -300,42 +337,95 @@ defmodule Pleroma.Notification do
end
end
- def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
- object = Object.normalize(activity)
+ def create_notifications(activity, options \\ [])
+
+ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
+ object = Object.normalize(activity, false)
if object && object.data["type"] == "Answer" do
{:ok, []}
else
- do_create_notifications(activity)
+ do_create_notifications(activity, options)
end
end
- def create_notifications(%Activity{data: %{"type" => type}} = activity)
+ def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
when type in ["Follow", "Like", "Announce", "Move", "EmojiReact"] do
- do_create_notifications(activity)
+ do_create_notifications(activity, options)
end
- def create_notifications(_), do: {:ok, []}
+ def create_notifications(_, _), do: {:ok, []}
+
+ defp do_create_notifications(%Activity{} = activity, options) do
+ do_send = Keyword.get(options, :do_send, true)
- defp do_create_notifications(%Activity{} = activity) do
{enabled_receivers, disabled_receivers} = get_notified_from_activity(activity)
potential_receivers = enabled_receivers ++ disabled_receivers
notifications =
Enum.map(potential_receivers, fn user ->
- do_send = user in enabled_receivers
+ do_send = do_send && user in enabled_receivers
create_notification(activity, user, do_send)
end)
{:ok, notifications}
end
+ defp type_from_activity(%{data: %{"type" => type}} = activity) do
+ case type do
+ "Follow" ->
+ if Activity.follow_accepted?(activity) do
+ "follow"
+ else
+ "follow_request"
+ end
+
+ "Announce" ->
+ "reblog"
+
+ "Like" ->
+ "favourite"
+
+ "Move" ->
+ "move"
+
+ "EmojiReact" ->
+ "pleroma:emoji_reaction"
+
+ # Compatibility with old reactions
+ "EmojiReaction" ->
+ "pleroma:emoji_reaction"
+
+ "Create" ->
+ activity
+ |> type_from_activity_object()
+
+ t ->
+ raise "No notification type for activity type #{t}"
+ end
+ end
+
+ defp type_from_activity_object(%{data: %{"type" => "Create", "object" => %{}}}), do: "mention"
+
+ defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
+ object = Object.get_by_ap_id(activity.data["object"])
+
+ case object && object.data["type"] do
+ "ChatMessage" -> "pleroma:chat_mention"
+ _ -> "mention"
+ end
+ end
+
# TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do
unless skip?(activity, user) do
{:ok, %{notification: notification}} =
Multi.new()
- |> Multi.insert(:notification, %Notification{user_id: user.id, activity: activity})
+ |> Multi.insert(:notification, %Notification{
+ user_id: user.id,
+ activity: activity,
+ type: type_from_activity(activity)
+ })
|> Marker.multi_set_last_read_id(user, "notifications")
|> Repo.transaction()
@@ -459,6 +549,7 @@ defmodule Pleroma.Notification do
def skip?(%Activity{} = activity, %User{} = user) do
[
:self,
+ :invisible,
:followers,
:follows,
:non_followers,
@@ -475,6 +566,12 @@ defmodule Pleroma.Notification do
activity.data["actor"] == user.ap_id
end
+ def skip?(:invisible, %Activity{} = activity, _) do
+ actor = activity.data["actor"]
+ user = User.get_cached_by_ap_id(actor)
+ User.invisible?(user)
+ end
+
def skip?(
:followers,
%Activity{} = activity,
@@ -527,4 +624,12 @@ defmodule Pleroma.Notification do
end
def skip?(_, _, _), do: false
+
+ def for_user_and_activity(user, activity) do
+ from(n in __MODULE__,
+ where: n.user_id == ^user.id,
+ where: n.activity_id == ^activity.id
+ )
+ |> Repo.one()
+ end
end
diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex
index 1b99e44f9..9a3795769 100644
--- a/lib/pleroma/pagination.ex
+++ b/lib/pleroma/pagination.ex
@@ -64,6 +64,12 @@ defmodule Pleroma.Pagination do
@spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()]
def paginate(query, options, method \\ :keyset, table_binding \\ nil)
+ def paginate(list, options, _method, _table_binding) when is_list(list) do
+ offset = options[:offset] || 0
+ limit = options[:limit] || 0
+ Enum.slice(list, offset, limit)
+ end
+
def paginate(query, options, :keyset, table_binding) do
query
|> restrict(:min_id, options, table_binding)
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 6a339b32c..1420a9611 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -113,6 +113,10 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
add_source(acc, host)
end)
+ media_proxy_base_url =
+ if Config.get([:media_proxy, :base_url]),
+ do: URI.parse(Config.get([:media_proxy, :base_url])).host
+
upload_base_url =
if Config.get([Pleroma.Upload, :base_url]),
do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host
@@ -122,6 +126,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host
[]
+ |> add_source(media_proxy_base_url)
|> add_source(upload_base_url)
|> add_source(s3_endpoint)
|> add_source(media_proxy_whitelist)
diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/plugs/uploaded_media.ex
index 94147e0c4..40984cfc0 100644
--- a/lib/pleroma/plugs/uploaded_media.ex
+++ b/lib/pleroma/plugs/uploaded_media.ex
@@ -10,6 +10,8 @@ defmodule Pleroma.Plugs.UploadedMedia do
import Pleroma.Web.Gettext
require Logger
+ alias Pleroma.Web.MediaProxy
+
@behaviour Plug
# no slashes
@path "media"
@@ -35,8 +37,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
%{query_params: %{"name" => name}} = conn ->
name = String.replace(name, "\"", "\\\"")
- conn
- |> put_resp_header("content-disposition", "filename=\"#{name}\"")
+ put_resp_header(conn, "content-disposition", "filename=\"#{name}\"")
conn ->
conn
@@ -47,7 +48,8 @@ defmodule Pleroma.Plugs.UploadedMedia do
with uploader <- Keyword.fetch!(config, :uploader),
proxy_remote = Keyword.get(config, :proxy_remote, false),
- {:ok, get_method} <- uploader.get_file(file) do
+ {:ok, get_method} <- uploader.get_file(file),
+ false <- media_is_banned(conn, get_method) do
get_media(conn, get_method, proxy_remote, opts)
else
_ ->
@@ -59,6 +61,14 @@ defmodule Pleroma.Plugs.UploadedMedia do
def call(conn, _opts), do: conn
+ defp media_is_banned(%{request_path: path} = _conn, {:static_dir, _}) do
+ MediaProxy.in_banned_urls(Pleroma.Web.base_url() <> path)
+ end
+
+ defp media_is_banned(_, {:url, url}), do: MediaProxy.in_banned_urls(url)
+
+ defp media_is_banned(_, _), do: false
+
defp get_media(conn, {:static_dir, directory}, _, opts) do
static_opts =
Map.get(opts, :static_plug_opts)
diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index f62138466..f317e4d58 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -8,11 +8,10 @@ defmodule Pleroma.Repo do
adapter: Ecto.Adapters.Postgres,
migration_timestamps: [type: :naive_datetime_usec]
+ import Ecto.Query
require Logger
- defmodule Instrumenter do
- use Prometheus.EctoInstrumenter
- end
+ defmodule Instrumenter, do: use(Prometheus.EctoInstrumenter)
@doc """
Dynamically loads the repository url from the
@@ -50,36 +49,30 @@ defmodule Pleroma.Repo do
end
end
- def check_migrations_applied!() do
- unless Pleroma.Config.get(
- [:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
- false
- ) do
- Ecto.Migrator.with_repo(__MODULE__, fn repo ->
- down_migrations =
- Ecto.Migrator.migrations(repo)
- |> Enum.reject(fn
- {:up, _, _} -> true
- {:down, _, _} -> false
- end)
-
- if length(down_migrations) > 0 do
- down_migrations_text =
- Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
-
- Logger.error(
- "The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
- )
+ def chunk_stream(query, chunk_size) do
+ # We don't actually need start and end funcitons of resource streaming,
+ # but it seems to be the only way to not fetch records one-by-one and
+ # have individual records be the elements of the stream, instead of
+ # lists of records
+ Stream.resource(
+ fn -> 0 end,
+ fn
+ last_id ->
+ query
+ |> order_by(asc: :id)
+ |> where([r], r.id > ^last_id)
+ |> limit(^chunk_size)
+ |> all()
+ |> case do
+ [] ->
+ {:halt, last_id}
- raise Pleroma.Repo.UnappliedMigrationsError
- end
- end)
- else
- :ok
- end
+ records ->
+ last_id = List.last(records).id
+ {records, last_id}
+ end
+ end,
+ fn _ -> :ok end
+ )
end
end
-
-defmodule Pleroma.Repo.UnappliedMigrationsError do
- defexception message: "Unapplied Migrations detected"
-end
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index d01728361..3aa6909d2 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -5,10 +5,10 @@
defmodule Pleroma.Signature do
@behaviour HTTPSignatures.Adapter
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Keys
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
def key_id_to_actor_id(key_id) do
uri =
@@ -24,7 +24,7 @@ defmodule Pleroma.Signature do
maybe_ap_id = URI.to_string(uri)
- case Types.ObjectID.cast(maybe_ap_id) do
+ case ObjectValidators.ObjectID.cast(maybe_ap_id) do
{:ok, ap_id} ->
{:ok, ap_id}
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 1be1a3a5b..797555bff 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -67,6 +67,7 @@ defmodule Pleroma.Upload do
{:ok,
%{
"type" => opts.activity_type,
+ "mediaType" => upload.content_type,
"url" => [
%{
"type" => "Link",
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index c5c74d132..1d70a37ef 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -14,6 +14,7 @@ defmodule Pleroma.User do
alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Emoji
alias Pleroma.FollowingRelationship
alias Pleroma.Formatter
@@ -30,7 +31,6 @@ defmodule Pleroma.User do
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
@@ -79,6 +79,7 @@ defmodule Pleroma.User do
schema "users" do
field(:bio, :string)
+ field(:raw_bio, :string)
field(:email, :string)
field(:name, :string)
field(:nickname, :string)
@@ -115,7 +116,7 @@ defmodule Pleroma.User do
field(:is_admin, :boolean, default: false)
field(:show_role, :boolean, default: true)
field(:settings, :map, default: nil)
- field(:uri, Types.Uri, default: nil)
+ field(:uri, ObjectValidators.Uri, default: nil)
field(:hide_followers_count, :boolean, default: false)
field(:hide_follows_count, :boolean, default: false)
field(:hide_followers, :boolean, default: false)
@@ -262,37 +263,60 @@ defmodule Pleroma.User do
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
def account_status(%User{confirmation_pending: true}) do
- case Config.get([:instance, :account_activation_required]) do
- true -> :confirmation_pending
- _ -> :active
+ if Config.get([:instance, :account_activation_required]) do
+ :confirmation_pending
+ else
+ :active
end
end
def account_status(%User{}), do: :active
- @spec visible_for?(User.t(), User.t() | nil) :: boolean()
- def visible_for?(user, for_user \\ nil)
+ @spec visible_for(User.t(), User.t() | nil) ::
+ :visible
+ | :invisible
+ | :restricted_unauthenticated
+ | :deactivated
+ | :confirmation_pending
+ def visible_for(user, for_user \\ nil)
- def visible_for?(%User{invisible: true}, _), do: false
+ def visible_for(%User{invisible: true}, _), do: :invisible
- def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true
+ def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
- def visible_for?(%User{local: local} = user, nil) do
- cfg_key =
- if local,
- do: :local,
- else: :remote
+ def visible_for(%User{} = user, nil) do
+ if restrict_unauthenticated?(user) do
+ :restrict_unauthenticated
+ else
+ visible_account_status(user)
+ end
+ end
- if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
- do: false,
- else: account_status(user) == :active
+ def visible_for(%User{} = user, for_user) do
+ if superuser?(for_user) do
+ :visible
+ else
+ visible_account_status(user)
+ end
end
- def visible_for?(%User{} = user, for_user) do
- account_status(user) == :active || superuser?(for_user)
+ def visible_for(_, _), do: :invisible
+
+ defp restrict_unauthenticated?(%User{local: local}) do
+ config_key = if local, do: :local, else: :remote
+
+ Config.get([:restrict_unauthenticated, :profiles, config_key], false)
end
- def visible_for?(_, _), do: false
+ defp visible_account_status(user) do
+ status = account_status(user)
+
+ if status in [:active, :password_reset_pending] do
+ :visible
+ else
+ status
+ end
+ end
@spec superuser?(User.t()) :: boolean()
def superuser?(%User{local: true, is_admin: true}), do: true
@@ -432,6 +456,7 @@ defmodule Pleroma.User do
params,
[
:bio,
+ :raw_bio,
:name,
:emoji,
:avatar,
@@ -463,6 +488,7 @@ defmodule Pleroma.User do
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
+ |> validate_inclusion(:actor_type, ["Person", "Service"])
|> put_fields()
|> put_emoji()
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
@@ -607,7 +633,16 @@ defmodule Pleroma.User do
struct
|> confirmation_changeset(need_confirmation: need_confirmation?)
- |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation, :emoji])
+ |> cast(params, [
+ :bio,
+ :raw_bio,
+ :email,
+ :name,
+ :nickname,
+ :password,
+ :password_confirmation,
+ :emoji
+ ])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
@@ -747,7 +782,6 @@ defmodule Pleroma.User do
follower
|> update_following_count()
- |> set_cache()
end
end
@@ -776,7 +810,6 @@ defmodule Pleroma.User do
{:ok, follower} =
follower
|> update_following_count()
- |> set_cache()
{:ok, follower, followed}
@@ -1128,35 +1161,25 @@ defmodule Pleroma.User do
])
end
+ @spec update_follower_count(User.t()) :: {:ok, User.t()}
def update_follower_count(%User{} = user) do
if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
- follower_count_query =
- User.Query.build(%{followers: user, deactivated: false})
- |> select([u], %{count: count(u.id)})
-
- User
- |> where(id: ^user.id)
- |> join(:inner, [u], s in subquery(follower_count_query))
- |> update([u, s],
- set: [follower_count: s.count]
- )
- |> select([u], u)
- |> Repo.update_all([])
- |> case do
- {1, [user]} -> set_cache(user)
- _ -> {:error, user}
- end
+ follower_count = FollowingRelationship.follower_count(user)
+
+ user
+ |> follow_information_changeset(%{follower_count: follower_count})
+ |> update_and_set_cache
else
{:ok, maybe_fetch_follow_information(user)}
end
end
- @spec update_following_count(User.t()) :: User.t()
+ @spec update_following_count(User.t()) :: {:ok, User.t()}
def update_following_count(%User{local: false} = user) do
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
- maybe_fetch_follow_information(user)
+ {:ok, maybe_fetch_follow_information(user)}
else
- user
+ {:ok, user}
end
end
@@ -1165,7 +1188,7 @@ defmodule Pleroma.User do
user
|> follow_information_changeset(%{following_count: following_count})
- |> Repo.update!()
+ |> update_and_set_cache()
end
def set_unread_conversation_count(%User{local: true} = user) do
@@ -1488,6 +1511,7 @@ defmodule Pleroma.User do
end)
delete_user_activities(user)
+ delete_notifications_from_user_activities(user)
delete_outgoing_pending_follow_requests(user)
@@ -1576,6 +1600,13 @@ defmodule Pleroma.User do
})
end
+ def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
+ Notification
+ |> join(:inner, [n], activity in assoc(n, :activity))
+ |> where([n, a], fragment("? = ?", a.actor, ^ap_id))
+ |> Repo.delete_all()
+ end
+
def delete_user_activities(%User{ap_id: ap_id} = user) do
ap_id
|> Activity.Queries.by_actor()
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index eb73c95fe..3e4d0a2be 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
+ alias Pleroma.ActivityExpiration
alias Pleroma.Config
alias Pleroma.Constants
alias Pleroma.Conversation
@@ -31,25 +32,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
require Logger
require Pleroma.Constants
- # For Announce activities, we filter the recipients based on following status for any actors
- # that match actual users. See issue #164 for more information about why this is necessary.
- defp get_recipients(%{"type" => "Announce"} = data) do
- to = Map.get(data, "to", [])
- cc = Map.get(data, "cc", [])
- bcc = Map.get(data, "bcc", [])
- actor = User.get_cached_by_ap_id(data["actor"])
-
- recipients =
- Enum.filter(Enum.concat([to, cc, bcc]), fn recipient ->
- case User.get_cached_by_ap_id(recipient) do
- nil -> true
- user -> User.following?(user, actor)
- end
- end)
-
- {recipients, to, cc}
- end
-
defp get_recipients(%{"type" => "Create"} = data) do
to = Map.get(data, "to", [])
cc = Map.get(data, "cc", [])
@@ -112,7 +94,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp increase_poll_votes_if_vote(_create_data), do: :noop
+ @object_types ["ChatMessage"]
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
+ def persist(%{"type" => type} = object, meta) when type in @object_types do
+ with {:ok, object} <- Object.create(object) do
+ {:ok, object, meta}
+ end
+ end
+
def persist(object, meta) do
with local <- Keyword.fetch!(meta, :local),
{recipients, _, _} <- get_recipients(object),
@@ -139,12 +128,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:containment, :ok} <- {:containment, Containment.contain_child(map)},
{:ok, map, object} <- insert_full_object(map) do
{:ok, activity} =
- Repo.insert(%Activity{
+ %Activity{
data: map,
local: local,
actor: map["actor"],
recipients: recipients
- })
+ }
+ |> Repo.insert()
+ |> maybe_create_activity_expiration()
# Splice in the child object if we have one.
activity = Maps.put_if_present(activity, :object, object)
@@ -182,6 +173,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
stream_out_participations(participations)
end
+ defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do
+ with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
+ {:ok, activity}
+ end
+ end
+
+ defp maybe_create_activity_expiration(result), do: result
+
defp create_or_bump_conversation(activity, actor) do
with {:ok, conversation} <- Conversation.create_or_bump_for(activity),
%User{} = user <- User.get_cached_by_ap_id(actor) do
@@ -211,7 +210,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
conversation = Repo.preload(conversation, :participations)
last_activity_id =
- fetch_latest_activity_id_for_context(conversation.ap_id, %{
+ fetch_latest_direct_activity_id_for_context(conversation.ap_id, %{
user: user,
blocking_user: user
})
@@ -344,20 +343,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- @spec follow(User.t(), User.t(), String.t() | nil, boolean()) ::
+ @spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
{:ok, Activity.t()} | {:error, any()}
- def follow(follower, followed, activity_id \\ nil, local \\ true) do
+ def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
with {:ok, result} <-
- Repo.transaction(fn -> do_follow(follower, followed, activity_id, local) end) do
+ Repo.transaction(fn -> do_follow(follower, followed, activity_id, local, opts) end) do
result
end
end
- defp do_follow(follower, followed, activity_id, local) do
+ defp do_follow(follower, followed, activity_id, local, opts) do
+ skip_notify_and_stream = Keyword.get(opts, :skip_notify_and_stream, false)
data = make_follow_data(follower, followed, activity_id)
with {:ok, activity} <- insert(data, local),
- _ <- notify_and_stream(activity),
+ _ <- skip_notify_and_stream || notify_and_stream(activity),
:ok <- maybe_federate(activity) do
{:ok, activity}
else
@@ -517,11 +517,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Repo.all()
end
- @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
+ @spec fetch_latest_direct_activity_id_for_context(String.t(), keyword() | map()) ::
FlakeId.Ecto.CompatType.t() | nil
- def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
+ def fetch_latest_direct_activity_id_for_context(context, opts \\ %{}) do
context
|> fetch_activities_for_context_query(Map.merge(%{skip_preload: true}, opts))
+ |> restrict_visibility(%{visibility: "direct"})
|> limit(1)
|> select([a], a.id)
|> Repo.one()
@@ -702,6 +703,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ defp restrict_announce_object_actor(_query, %{announce_filtering_user: _, skip_preload: true}) do
+ raise "Can't use the child object without preloading!"
+ end
+
+ defp restrict_announce_object_actor(query, %{announce_filtering_user: %{ap_id: actor}}) do
+ from(
+ [activity, object] in query,
+ where:
+ fragment(
+ "?->>'type' != ? or ?->>'actor' != ?",
+ activity.data,
+ "Announce",
+ object.data,
+ ^actor
+ )
+ )
+ end
+
+ defp restrict_announce_object_actor(query, _), do: query
+
defp restrict_since(query, %{since_id: ""}), do: query
defp restrict_since(query, %{since_id: since_id}) do
@@ -813,7 +834,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_media(query, %{only_media: true}) do
from(
- [_activity, object] in query,
+ [activity, object] in query,
+ where: fragment("(?)->>'type' = ?", activity.data, "Create"),
where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
)
end
@@ -1000,6 +1022,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ defp exclude_chat_messages(query, %{include_chat_messages: true}), do: query
+
+ defp exclude_chat_messages(query, _) do
+ if has_named_binding?(query, :object) do
+ from([activity, object: o] in query,
+ where: fragment("not(?->>'type' = ?)", o.data, "ChatMessage")
+ )
+ else
+ query
+ end
+ end
+
defp exclude_invisible_actors(query, %{invisible_actors: true}), do: query
defp exclude_invisible_actors(query, _opts) do
@@ -1113,8 +1147,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_pinned(opts)
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|> restrict_instance(opts)
+ |> restrict_announce_object_actor(opts)
|> Activity.restrict_deactivated_users()
|> exclude_poll_votes(opts)
+ |> exclude_chat_messages(opts)
|> exclude_invisible_actors(opts)
|> exclude_visibility(opts)
end
@@ -1138,12 +1174,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Activity.Queries.by_type("Like")
|> Activity.with_joined_object()
|> Object.with_joined_activity()
- |> select([_like, object, activity], %{activity | object: object})
+ |> select([like, object, activity], %{activity | object: object, pagination_id: like.id})
|> order_by([like, _, _], desc_nulls_last: like.id)
|> Pagination.fetch_paginated(
Map.merge(params, %{skip_order: true}),
- pagination,
- :object_activity
+ pagination
)
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index f0b5c6e93..220c4fe52 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -514,7 +514,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
{new_user, for_user}
end
- # TODO: Add support for "object" field
@doc """
Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>
@@ -525,6 +524,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
Response:
- HTTP Code: 201 Created
- HTTP Body: ActivityPub object to be inserted into another's `attachment` field
+
+ Note: Will not point to a URL with a `Location` header because no standalone Activity has been created.
"""
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index 51b74414a..1aac62c69 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
This module encodes our addressing policies and general shape of our objects.
"""
+ alias Pleroma.Emoji
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
@@ -65,6 +66,42 @@ defmodule Pleroma.Web.ActivityPub.Builder do
}, []}
end
+ def create(actor, object, recipients) do
+ {:ok,
+ %{
+ "id" => Utils.generate_activity_id(),
+ "actor" => actor.ap_id,
+ "to" => recipients,
+ "object" => object,
+ "type" => "Create",
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601()
+ }, []}
+ end
+
+ def chat_message(actor, recipient, content, opts \\ []) do
+ basic = %{
+ "id" => Utils.generate_object_id(),
+ "actor" => actor.ap_id,
+ "type" => "ChatMessage",
+ "to" => [recipient],
+ "content" => content,
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "emoji" => Emoji.Formatter.get_emoji_map(content)
+ }
+
+ case opts[:attachment] do
+ %Object{data: attachment_data} ->
+ {
+ :ok,
+ Map.put(basic, "attachment", attachment_data),
+ []
+ }
+
+ _ ->
+ {:ok, basic, []}
+ end
+ end
+
@spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
def tombstone(actor, id) do
{:ok,
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index a0b3af432..206d6af52 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -8,18 +8,15 @@ defmodule Pleroma.Web.ActivityPub.MRF do
def filter(policies, %{} = object) do
policies
|> Enum.reduce({:ok, object}, fn
- policy, {:ok, object} ->
- policy.filter(object)
-
- _, error ->
- error
+ policy, {:ok, object} -> policy.filter(object)
+ _, error -> error
end)
end
def filter(%{} = object), do: get_policies() |> filter(object)
def get_policies do
- Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies()
+ Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
end
defp get_policies(policy) when is_atom(policy), do: [policy]
@@ -54,7 +51,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
get_policies()
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
- exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
+ exclusions = Pleroma.Config.get([:mrf, :transparency_exclusions])
base =
%{
diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
new file mode 100644
index 000000000..8e47f1e02
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
@@ -0,0 +1,43 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
+ @moduledoc "Adds expiration to all local Create activities"
+ @behaviour Pleroma.Web.ActivityPub.MRF
+
+ @impl true
+ def filter(activity) do
+ activity =
+ if note?(activity) and local?(activity) do
+ maybe_add_expiration(activity)
+ else
+ activity
+ end
+
+ {:ok, activity}
+ end
+
+ @impl true
+ def describe, do: {:ok, %{}}
+
+ defp local?(%{"id" => id}) do
+ String.starts_with?(id, Pleroma.Web.Endpoint.url())
+ end
+
+ defp note?(activity) do
+ match?(%{"type" => "Create", "object" => %{"type" => "Note"}}, activity)
+ end
+
+ defp maybe_add_expiration(activity) do
+ days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
+ expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: days)
+
+ with %{"expires_at" => existing_expires_at} <- activity,
+ :lt <- NaiveDateTime.compare(existing_expires_at, expires_at) do
+ activity
+ else
+ _ -> Map.put(activity, "expires_at", expires_at)
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
index 1764bc789..f6b2c4415 100644
--- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -13,8 +13,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
defp delist_message(message, threshold) when threshold > 0 do
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
+ to = message["to"] || []
+ cc = message["cc"] || []
- follower_collection? = Enum.member?(message["to"] ++ message["cc"], follower_collection)
+ follower_collection? = Enum.member?(to ++ cc, follower_collection)
message =
case get_recipient_count(message) do
@@ -71,7 +73,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
end
@impl true
- def filter(%{"type" => "Create"} = message) do
+ def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message)
+ when object_type in ~w{Note Article} do
reject_threshold =
Pleroma.Config.get(
[:mrf_hellthread, :reject_threshold],
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index b7dcb1b86..9cea6bcf9 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -3,21 +3,23 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance"
@behaviour Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.MRF
+
require Pleroma.Constants
defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts =
- Pleroma.Config.get([:mrf_simple, :accept])
+ Config.get([:mrf_simple, :accept])
|> MRF.subdomains_regex()
cond do
accepts == [] -> {:ok, object}
- actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
+ actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil}
end
@@ -25,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_reject(%{host: actor_host} = _actor_info, object) do
rejects =
- Pleroma.Config.get([:mrf_simple, :reject])
+ Config.get([:mrf_simple, :reject])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
@@ -41,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
)
when length(child_attachment) > 0 do
media_removal =
- Pleroma.Config.get([:mrf_simple, :media_removal])
+ Config.get([:mrf_simple, :media_removal])
|> MRF.subdomains_regex()
object =
@@ -65,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
} = object
) do
media_nsfw =
- Pleroma.Config.get([:mrf_simple, :media_nsfw])
+ Config.get([:mrf_simple, :media_nsfw])
|> MRF.subdomains_regex()
object =
@@ -85,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
timeline_removal =
- Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
+ Config.get([:mrf_simple, :federated_timeline_removal])
|> MRF.subdomains_regex()
object =
@@ -108,7 +110,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
report_removal =
- Pleroma.Config.get([:mrf_simple, :report_removal])
+ Config.get([:mrf_simple, :report_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
@@ -122,7 +124,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
avatar_removal =
- Pleroma.Config.get([:mrf_simple, :avatar_removal])
+ Config.get([:mrf_simple, :avatar_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do
@@ -136,7 +138,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
banner_removal =
- Pleroma.Config.get([:mrf_simple, :banner_removal])
+ Config.get([:mrf_simple, :banner_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
@@ -197,10 +199,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@impl true
def describe do
- exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
+ exclusions = Config.get([:mrf, :transparency_exclusions])
mrf_simple =
- Pleroma.Config.get(:mrf_simple)
+ Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|> Enum.into(%{})
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
index a927a4ed8..651aed70f 100644
--- a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
allow_list =
Config.get(
- [:mrf_user_allowlist, String.to_atom(actor_info.host)],
+ [:mrf_user_allowlist, actor_info.host],
[]
)
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 2599067a8..6a83a2c33 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -9,13 +9,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
the system.
"""
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
@@ -43,8 +45,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def validate(%{"type" => "Like"} = object, meta) do
with {:ok, object} <-
- object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object |> Map.from_struct())
+ object
+ |> LikeValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "ChatMessage"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> ChatMessageValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
{:ok, object, meta}
end
end
@@ -59,6 +73,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
+ def validate(%{"type" => "Create", "object" => object} = create_activity, meta) do
+ with {:ok, object_data} <- cast_and_apply(object),
+ meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
+ {:ok, create_activity} <-
+ create_activity
+ |> CreateChatMessageValidator.cast_and_validate(meta)
+ |> Ecto.Changeset.apply_action(:insert) do
+ create_activity = stringify_keys(create_activity)
+ {:ok, create_activity, meta}
+ end
+ end
+
def validate(%{"type" => "Announce"} = object, meta) do
with {:ok, object} <-
object
@@ -69,19 +95,32 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
+ def cast_and_apply(%{"type" => "ChatMessage"} = object) do
+ ChatMessageValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
+
def stringify_keys(%{__struct__: _} = object) do
object
|> Map.from_struct()
|> stringify_keys
end
- def stringify_keys(object) do
+ def stringify_keys(object) when is_map(object) do
object
- |> Map.new(fn {key, val} -> {to_string(key), val} end)
+ |> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
end
+ def stringify_keys(object) when is_list(object) do
+ object
+ |> Enum.map(&stringify_keys/1)
+ end
+
+ def stringify_keys(object), do: object
+
def fetch_actor(object) do
- with {:ok, actor} <- Types.ObjectID.cast(object["actor"]) do
+ with {:ok, actor} <- ObjectValidators.ObjectID.cast(object["actor"]) do
User.get_or_fetch_by_ap_id(actor)
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
index 40f861f47..6f757f49c 100644
--- a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@@ -19,14 +19,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:object, Types.ObjectID)
- field(:actor, Types.ObjectID)
+ field(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
field(:context, :string, autogenerate: {Utils, :generate_context_id, []})
- field(:to, Types.Recipients, default: [])
- field(:cc, Types.Recipients, default: [])
- field(:published, Types.DateTime)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:published, ObjectValidators.DateTime)
end
def cast_and_validate(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
new file mode 100644
index 000000000..f53bb02be
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -0,0 +1,80 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
+ use Ecto.Schema
+
+ alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
+
+ import Ecto.Changeset
+
+ @primary_key false
+ embedded_schema do
+ field(:type, :string)
+ field(:mediaType, :string, default: "application/octet-stream")
+ field(:name, :string)
+
+ embeds_many(:url, UrlObjectValidator)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ def changeset(struct, data) do
+ data =
+ data
+ |> fix_media_type()
+ |> fix_url()
+
+ struct
+ |> cast(data, [:type, :mediaType, :name])
+ |> cast_embed(:url, required: true)
+ end
+
+ def fix_media_type(data) do
+ data =
+ data
+ |> Map.put_new("mediaType", data["mimeType"])
+
+ if MIME.valid?(data["mediaType"]) do
+ data
+ else
+ data
+ |> Map.put("mediaType", "application/octet-stream")
+ end
+ end
+
+ def fix_url(data) do
+ case data["url"] do
+ url when is_binary(url) ->
+ data
+ |> Map.put(
+ "url",
+ [
+ %{
+ "href" => url,
+ "type" => "Link",
+ "mediaType" => data["mediaType"]
+ }
+ ]
+ )
+
+ _ ->
+ data
+ end
+ end
+
+ def validate_data(cng) do
+ cng
+ |> validate_required([:mediaType, :url, :type])
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
new file mode 100644
index 000000000..c481d79e0
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
@@ -0,0 +1,123 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.Transmogrifier, only: [fix_emoji: 1]
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:type, :string)
+ field(:content, ObjectValidators.SafeText)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:published, ObjectValidators.DateTime)
+ field(:emoji, :map, default: %{})
+
+ embeds_one(:attachment, AttachmentValidator)
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ def fix(data) do
+ data
+ |> fix_emoji()
+ |> fix_attachment()
+ |> Map.put_new("actor", data["attributedTo"])
+ end
+
+ # Throws everything but the first one away
+ def fix_attachment(%{"attachment" => [attachment | _]} = data) do
+ data
+ |> Map.put("attachment", attachment)
+ end
+
+ def fix_attachment(data), do: data
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, List.delete(__schema__(:fields), :attachment))
+ |> cast_embed(:attachment)
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["ChatMessage"])
+ |> validate_required([:id, :actor, :to, :type, :published])
+ |> validate_content_or_attachment()
+ |> validate_length(:to, is: 1)
+ |> validate_length(:content, max: Pleroma.Config.get([:instance, :remote_limit]))
+ |> validate_local_concern()
+ end
+
+ def validate_content_or_attachment(cng) do
+ attachment = get_field(cng, :attachment)
+
+ if attachment do
+ cng
+ else
+ cng
+ |> validate_required([:content])
+ end
+ end
+
+ @doc """
+ Validates the following
+ - If both users are in our system
+ - If at least one of the users in this ChatMessage is a local user
+ - If the recipient is not blocking the actor
+ """
+ def validate_local_concern(cng) do
+ with actor_ap <- get_field(cng, :actor),
+ {_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
+ {_, %User{} = recipient} <-
+ {:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
+ {_, false} <- {:blocking_actor?, User.blocks?(recipient, actor)},
+ {_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
+ cng
+ else
+ {:blocking_actor?, true} ->
+ cng
+ |> add_error(:actor, "actor is blocked by recipient")
+
+ {:local?, false} ->
+ cng
+ |> add_error(:actor, "actor and recipient are both remote")
+
+ {:find_actor, _} ->
+ cng
+ |> add_error(:actor, "can't find user")
+
+ {:find_recipient, _} ->
+ cng
+ |> add_error(:to, "can't find user")
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
new file mode 100644
index 000000000..7269f9ff0
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
@@ -0,0 +1,91 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+# NOTES
+# - Can probably be a generic create validator
+# - doesn't embed, will only get the object id
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
+ use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ alias Pleroma.Object
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:type, :string)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:object, ObjectValidators.ObjectID)
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_data(data) do
+ cast(%__MODULE__{}, data, __schema__(:fields))
+ end
+
+ def cast_and_validate(data, meta \\ []) do
+ cast_data(data)
+ |> validate_data(meta)
+ end
+
+ def validate_data(cng, meta \\ []) do
+ cng
+ |> validate_required([:id, :actor, :to, :type, :object])
+ |> validate_inclusion(:type, ["Create"])
+ |> validate_actor_presence()
+ |> validate_recipients_match(meta)
+ |> validate_actors_match(meta)
+ |> validate_object_nonexistence()
+ end
+
+ def validate_object_nonexistence(cng) do
+ cng
+ |> validate_change(:object, fn :object, object_id ->
+ if Object.get_cached_by_ap_id(object_id) do
+ [{:object, "The object to create already exists"}]
+ else
+ []
+ end
+ end)
+ end
+
+ def validate_actors_match(cng, meta) do
+ object_actor = meta[:object_data]["actor"]
+
+ cng
+ |> validate_change(:actor, fn :actor, actor ->
+ if actor == object_actor do
+ []
+ else
+ [{:actor, "Actor doesn't match with object actor"}]
+ end
+ end)
+ end
+
+ def validate_recipients_match(cng, meta) do
+ object_recipients = meta[:object_data]["to"] || []
+
+ cng
+ |> validate_change(:to, fn :to, recipients ->
+ activity_set = MapSet.new(recipients)
+ object_set = MapSet.new(object_recipients)
+
+ if MapSet.equal?(activity_set, object_set) do
+ []
+ else
+ [{:to, "Recipients don't match with object recipients"}]
+ end
+ end)
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
index 926804ce7..316bd0c07 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
@@ -5,16 +5,16 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
import Ecto.Changeset
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
- field(:actor, Types.ObjectID)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:actor, ObjectValidators.ObjectID)
field(:type, :string)
field(:to, {:array, :string})
field(:cc, {:array, :string})
diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
index f42c03510..93a7b0e0b 100644
--- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
@@ -6,8 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
use Ecto.Schema
alias Pleroma.Activity
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -15,13 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:actor, Types.ObjectID)
- field(:to, Types.Recipients, default: [])
- field(:cc, Types.Recipients, default: [])
- field(:deleted_activity_id, Types.ObjectID)
- field(:object, Types.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:deleted_activity_id, ObjectValidators.ObjectID)
+ field(:object, ObjectValidators.ObjectID)
end
def cast_data(data) do
@@ -46,12 +46,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
Answer
Article
Audio
+ ChatMessage
Event
Note
Page
Question
- Video
Tombstone
+ Video
}
def validate_data(cng) do
cng
diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
index e87519c59..a543af1f8 100644
--- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -14,10 +14,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:object, Types.ObjectID)
- field(:actor, Types.ObjectID)
+ field(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
field(:context, :string)
field(:content, :string)
field(:to, {:array, :string}, default: [])
diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
index 034f25492..493e4c247 100644
--- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.Utils
import Ecto.Changeset
@@ -15,13 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:object, Types.ObjectID)
- field(:actor, Types.ObjectID)
+ field(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
field(:context, :string)
- field(:to, Types.Recipients, default: [])
- field(:cc, Types.Recipients, default: [])
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do
@@ -67,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
with {[], []} <- {to, cc},
%Object{data: %{"actor" => actor}} <- Object.get_cached_by_ap_id(object),
- {:ok, actor} <- Types.ObjectID.cast(actor) do
+ {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
cng
|> put_change(:to, [actor])
else
diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
index 462a5620a..56b93dde8 100644
--- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
@@ -5,14 +5,14 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
use Ecto.Schema
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:to, {:array, :string}, default: [])
field(:cc, {:array, :string}, default: [])
field(:bto, {:array, :string}, default: [])
@@ -22,10 +22,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
field(:type, :string)
field(:content, :string)
field(:context, :string)
- field(:actor, Types.ObjectID)
- field(:attributedTo, Types.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:attributedTo, ObjectValidators.ObjectID)
field(:summary, :string)
- field(:published, Types.DateTime)
+ field(:published, ObjectValidators.DateTime)
# TODO: Write type
field(:emoji, :map, default: %{})
field(:sensitive, :boolean, default: false)
@@ -35,13 +35,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inRepyTo, :string)
- field(:uri, Types.Uri)
+ field(:uri, ObjectValidators.Uri)
field(:likes, {:array, :string}, default: [])
field(:announcements, {:array, :string}, default: [])
# see if needed
- field(:conversation, :string)
field(:context_id, :string)
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex b/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex
deleted file mode 100644
index 48fe61e1a..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients do
- use Ecto.Type
-
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
-
- def type, do: {:array, ObjectID}
-
- def cast(object) when is_binary(object) do
- cast([object])
- end
-
- def cast(data) when is_list(data) do
- data
- |> Enum.reduce({:ok, []}, fn element, acc ->
- case {acc, ObjectID.cast(element)} do
- {:error, _} -> :error
- {_, :error} -> :error
- {{:ok, list}, {:ok, id}} -> {:ok, [id | list]}
- end
- end)
- end
-
- def cast(_) do
- :error
- end
-
- def dump(data) do
- {:ok, data}
- end
-
- def load(data) do
- {:ok, data}
- end
-end
diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
index d0ba418e8..e8d2d39c1 100644
--- a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
use Ecto.Schema
alias Pleroma.Activity
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -14,10 +14,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:object, Types.ObjectID)
- field(:actor, Types.ObjectID)
+ field(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
field(:to, {:array, :string}, default: [])
field(:cc, {:array, :string}, default: [])
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
new file mode 100644
index 000000000..f64fac46d
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ import Ecto.Changeset
+ @primary_key false
+
+ embedded_schema do
+ field(:type, :string)
+ field(:href, ObjectValidators.Uri)
+ field(:mediaType, :string)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, __schema__(:fields))
+ |> validate_required([:type, :href, :mediaType])
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 0c54c4b23..6875c47f6 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -17,6 +17,10 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
{:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
def common_pipeline(object, meta) do
case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
+ {:ok, {:ok, activity, meta}} ->
+ SideEffects.handle_after_transaction(meta)
+ {:ok, activity, meta}
+
{:ok, value} ->
value
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index fb6275450..1a1cc675c 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -6,12 +6,17 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
collection, and so on.
"""
alias Pleroma.Activity
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.Push
+ alias Pleroma.Web.Streamer
def handle(object, meta \\ [])
@@ -27,6 +32,24 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, object, meta}
end
+ # Tasks this handles
+ # - Actually create object
+ # - Rollback if we couldn't create it
+ # - Set up notifications
+ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
+ with {:ok, _object, meta} <- handle_object_creation(meta[:object_data], meta) do
+ {:ok, notifications} = Notification.create_notifications(activity, do_send: false)
+
+ meta =
+ meta
+ |> add_notifications(notifications)
+
+ {:ok, activity, meta}
+ else
+ e -> Repo.rollback(e)
+ end
+ end
+
# Tasks this handles:
# - Add announce to object
# - Set up notification
@@ -88,6 +111,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
Object.decrease_replies_count(in_reply_to)
end
+ MessageReference.delete_for_object(deleted_object)
+
ActivityPub.stream_out(object)
ActivityPub.stream_out_participations(deleted_object, user)
:ok
@@ -112,6 +137,39 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, object, meta}
end
+ def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
+ with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
+ actor = User.get_cached_by_ap_id(object.data["actor"])
+ recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
+
+ streamables =
+ [[actor, recipient], [recipient, actor]]
+ |> Enum.map(fn [user, other_user] ->
+ if user.local do
+ {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
+ {:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id)
+
+ {
+ ["user", "user:pleroma_chat"],
+ {user, %{cm_ref | chat: chat, object: object}}
+ }
+ end
+ end)
+ |> Enum.filter(& &1)
+
+ meta =
+ meta
+ |> add_streamables(streamables)
+
+ {:ok, object, meta}
+ end
+ end
+
+ # Nothing to do
+ def handle_object_creation(object) do
+ {:ok, object}
+ end
+
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
{:ok, _} <- Utils.remove_like_from_object(object, liked_object),
@@ -148,4 +206,43 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
+
+ defp send_notifications(meta) do
+ Keyword.get(meta, :notifications, [])
+ |> Enum.each(fn notification ->
+ Streamer.stream(["user", "user:notification"], notification)
+ Push.send(notification)
+ end)
+
+ meta
+ end
+
+ defp send_streamables(meta) do
+ Keyword.get(meta, :streamables, [])
+ |> Enum.each(fn {topics, items} ->
+ Streamer.stream(topics, items)
+ end)
+
+ meta
+ end
+
+ defp add_streamables(meta, streamables) do
+ existing = Keyword.get(meta, :streamables, [])
+
+ meta
+ |> Keyword.put(:streamables, streamables ++ existing)
+ end
+
+ defp add_notifications(meta, notifications) do
+ existing = Keyword.get(meta, :notifications, [])
+
+ meta
+ |> Keyword.put(:notifications, notifications ++ existing)
+ end
+
+ def handle_after_transaction(meta) do
+ meta
+ |> send_notifications()
+ |> send_streamables()
+ end
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index fda1c71df..1c60ef8f5 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -8,8 +8,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"""
alias Pleroma.Activity
alias Pleroma.EarmarkRenderer
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.FollowingRelationship
alias Pleroma.Maps
+ alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Object.Containment
alias Pleroma.Repo
@@ -17,7 +19,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@@ -171,8 +172,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
object
|> Map.put("inReplyTo", replied_object.data["id"])
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
- |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|> Map.put("context", replied_object.data["context"] || object["conversation"])
+ |> Map.drop(["conversation"])
else
e ->
Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
@@ -206,7 +207,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
object
|> Map.put("context", context)
- |> Map.put("conversation", context)
+ |> Map.drop(["conversation"])
end
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
@@ -221,9 +222,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
media_type =
cond do
- is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"]
- is_binary(data["mediaType"]) -> data["mediaType"]
- is_binary(data["mimeType"]) -> data["mimeType"]
+ is_map(url) && MIME.valid?(url["mediaType"]) -> url["mediaType"]
+ MIME.valid?(data["mediaType"]) -> data["mediaType"]
+ MIME.valid?(data["mimeType"]) -> data["mimeType"]
true -> nil
end
@@ -457,7 +458,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
to: data["to"],
object: object,
actor: user,
- context: object["conversation"],
+ context: object["context"],
local: false,
published: data["published"],
additional:
@@ -527,7 +528,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
{:ok, %User{} = follower} <-
User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
- {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
+ {:ok, activity} <-
+ ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
{_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
{_, false} <- {:user_locked, User.locked?(followed)},
@@ -570,6 +572,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
:noop
end
+ ActivityPub.notify_and_stream(activity)
{:ok, activity}
else
_e ->
@@ -590,6 +593,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
User.update_follower_count(followed)
User.update_following_count(follower)
+ Notification.update_notification_type(followed, follow_activity)
+
ActivityPub.accept(%{
to: follow_activity.data["to"],
type: "Accept",
@@ -657,6 +662,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> handle_incoming(options)
end
+ def handle_incoming(
+ %{"type" => "Create", "object" => %{"type" => "ChatMessage"}} = data,
+ _options
+ ) do
+ with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
+ {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+ {:ok, activity}
+ end
+ end
+
def handle_incoming(%{"type" => type} = data, _options)
when type in ["Like", "EmojiReact", "Announce"] do
with :ok <- ObjectValidator.fetch_actor_and_object(data),
@@ -710,7 +725,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
else
{:error, {:validate_object, _}} = e ->
# Check if we have a create activity for this
- with {:ok, object_id} <- Types.ObjectID.cast(data["object"]),
+ with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
%Activity{data: %{"actor" => actor}} <-
Activity.create_by_object_ap_id(object_id) |> Repo.one(),
# We have one, insert a tombstone and retry
@@ -1108,6 +1123,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "attributedTo", attributed_to)
end
+ # TODO: Revisit this
+ def prepare_attachments(%{"type" => "ChatMessage"} = object), do: object
+
def prepare_attachments(object) do
attachments =
object
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
index 335862340..f9545d895 100644
--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -111,8 +111,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action: "delete"
})
- conn
- |> json(nicknames)
+ json(conn, nicknames)
end
def user_follow(%{assigns: %{user: admin}} = conn, %{
@@ -131,8 +130,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
})
end
- conn
- |> json("ok")
+ json(conn, "ok")
end
def user_unfollow(%{assigns: %{user: admin}} = conn, %{
@@ -151,8 +149,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
})
end
- conn
- |> json("ok")
+ json(conn, "ok")
end
def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
@@ -191,8 +188,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action: "create"
})
- conn
- |> json(res)
+ json(conn, res)
{:error, id, changeset, _} ->
res =
@@ -363,8 +359,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
filters
|> String.split(",")
|> Enum.filter(&Enum.member?(@filters, &1))
- |> Enum.map(&String.to_atom(&1))
- |> Enum.into(%{}, &{&1, true})
+ |> Enum.map(&String.to_atom/1)
+ |> Map.new(&{&1, true})
end
def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
@@ -568,10 +564,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
{:error, changeset} ->
errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
- json(conn, %{errors: errors})
+ {:errors, errors}
_ ->
- json(conn, %{error: "Unable to update user."})
+ {:error, :not_found}
end
end
@@ -616,7 +612,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
def reload_emoji(conn, _params) do
Pleroma.Emoji.reload()
- conn |> json("ok")
+ json(conn, "ok")
end
def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
@@ -630,7 +626,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action: "confirm_email"
})
- conn |> json("")
+ json(conn, "")
end
def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
@@ -644,7 +640,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action: "resend_confirmation_email"
})
- conn |> json("")
+ json(conn, "")
end
def stats(conn, params) do
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
index d6e2019bc..7f60470cb 100644
--- a/lib/pleroma/web/admin_api/controllers/config_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex
@@ -33,7 +33,11 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
def show(conn, %{only_db: true}) do
with :ok <- configurable_from_database() do
configs = Pleroma.Repo.all(ConfigDB)
- render(conn, "index.json", %{configs: configs})
+
+ render(conn, "index.json", %{
+ configs: configs,
+ need_reboot: Restarter.Pleroma.need_reboot?()
+ })
end
end
@@ -61,17 +65,20 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
value
end
- %{
- group: ConfigDB.convert(group),
- key: ConfigDB.convert(key),
- value: ConfigDB.convert(merged_value)
+ %ConfigDB{
+ group: group,
+ key: key,
+ value: merged_value
}
|> Pleroma.Maps.put_if_present(:db, db)
end)
end)
|> List.flatten()
- json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
+ render(conn, "index.json", %{
+ configs: merged,
+ need_reboot: Restarter.Pleroma.need_reboot?()
+ })
end
end
@@ -91,24 +98,17 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
{deleted, updated} =
results
- |> Enum.map(fn {:ok, config} ->
- Map.put(config, :db, ConfigDB.get_db_keys(config))
- end)
- |> Enum.split_with(fn config ->
- Ecto.get_meta(config, :state) == :deleted
+ |> Enum.map(fn {:ok, %{key: key, value: value} = config} ->
+ Map.put(config, :db, ConfigDB.get_db_keys(value, key))
end)
+ |> Enum.split_with(&(Ecto.get_meta(&1, :state) == :deleted))
Config.TransferTask.load_and_update_env(deleted, false)
if not Restarter.Pleroma.need_reboot?() do
changed_reboot_settings? =
(updated ++ deleted)
- |> Enum.any?(fn config ->
- group = ConfigDB.from_string(config.group)
- key = ConfigDB.from_string(config.key)
- value = ConfigDB.from_binary(config.value)
- Config.TransferTask.pleroma_need_restart?(group, key, value)
- end)
+ |> Enum.any?(&Config.TransferTask.pleroma_need_restart?(&1.group, &1.key, &1.value))
if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
end
diff --git a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
index 82965936d..34d90db07 100644
--- a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
@@ -17,6 +17,12 @@ defmodule Pleroma.Web.AdminAPI.FallbackController do
|> json(%{error: reason})
end
+ def call(conn, {:errors, errors}) do
+ conn
+ |> put_status(:bad_request)
+ |> json(%{errors: errors})
+ end
+
def call(conn, {:param_cast, _}) do
conn
|> put_status(:bad_request)
diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
new file mode 100644
index 000000000..e2759d59f
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.ApiSpec.Admin, as: Spec
+ alias Pleroma.Web.MediaProxy
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:media_proxy_caches"], admin: true} when action in [:index]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:media_proxy_caches"], admin: true} when action in [:purge, :delete]
+ )
+
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Spec.MediaProxyCacheOperation
+
+ def index(%{assigns: %{user: _}} = conn, params) do
+ cursor =
+ :banned_urls_cache
+ |> :ets.table([{:traverse, {:select, Cachex.Query.create(true, :key)}}])
+ |> :qlc.cursor()
+
+ urls =
+ case params.page do
+ 1 ->
+ :qlc.next_answers(cursor, params.page_size)
+
+ _ ->
+ :qlc.next_answers(cursor, (params.page - 1) * params.page_size)
+ :qlc.next_answers(cursor, params.page_size)
+ end
+
+ :qlc.delete_cursor(cursor)
+
+ render(conn, "index.json", urls: urls)
+ end
+
+ def delete(%{assigns: %{user: _}, body_params: %{urls: urls}} = conn, _) do
+ MediaProxy.remove_from_banned_urls(urls)
+ render(conn, "index.json", urls: urls)
+ end
+
+ def purge(%{assigns: %{user: _}, body_params: %{urls: urls, ban: ban}} = conn, _) do
+ MediaProxy.Invalidation.purge(urls)
+
+ if ban do
+ MediaProxy.put_in_banned_urls(urls)
+ end
+
+ render(conn, "index.json", urls: urls)
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index 120159527..e1e929632 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -76,7 +76,8 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
"local" => user.local,
"roles" => User.roles(user),
"tags" => user.tags || [],
- "confirmation_pending" => user.confirmation_pending
+ "confirmation_pending" => user.confirmation_pending,
+ "url" => user.uri || user.ap_id
}
end
diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex
index 587ef760e..d2d8b5907 100644
--- a/lib/pleroma/web/admin_api/views/config_view.ex
+++ b/lib/pleroma/web/admin_api/views/config_view.ex
@@ -5,23 +5,20 @@
defmodule Pleroma.Web.AdminAPI.ConfigView do
use Pleroma.Web, :view
+ alias Pleroma.ConfigDB
+
def render("index.json", %{configs: configs} = params) do
- map = %{
- configs: render_many(configs, __MODULE__, "show.json", as: :config)
+ %{
+ configs: render_many(configs, __MODULE__, "show.json", as: :config),
+ need_reboot: params[:need_reboot]
}
-
- if params[:need_reboot] do
- Map.put(map, :need_reboot, true)
- else
- map
- end
end
def render("show.json", %{config: config}) do
map = %{
- key: config.key,
- group: config.group,
- value: Pleroma.ConfigDB.from_binary_with_convert(config.value)
+ key: ConfigDB.to_json_types(config.key),
+ group: ConfigDB.to_json_types(config.group),
+ value: ConfigDB.to_json_types(config.value)
}
if config.db != [] do
diff --git a/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex b/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
new file mode 100644
index 000000000..c97400beb
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
@@ -0,0 +1,11 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.MediaProxyCacheView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{urls: urls}) do
+ %{urls: urls}
+ end
+end
diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
index a9cfe0fed..a258e8421 100644
--- a/lib/pleroma/web/api_spec/helpers.ex
+++ b/lib/pleroma/web/api_spec/helpers.ex
@@ -40,6 +40,12 @@ defmodule Pleroma.Web.ApiSpec.Helpers do
"Return the newest items newer than this ID"
),
Operation.parameter(
+ :offset,
+ :query,
+ %Schema{type: :integer, default: 0},
+ "Return items past this number of items"
+ ),
+ Operation.parameter(
:limit,
:query,
%Schema{type: :integer, default: 20},
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 20572f8ea..9bde8fc0d 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -102,6 +102,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{
200 => Operation.response("Account", "application/json", Account),
+ 401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
@@ -142,6 +143,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
] ++ pagination_params(),
responses: %{
200 => Operation.response("Statuses", "application/json", array_of_statuses()),
+ 401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
diff --git a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
new file mode 100644
index 000000000..0358cfbad
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
@@ -0,0 +1,109 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Admin", "MediaProxyCache"],
+ summary: "Fetch a paginated list of all banned MediaProxy URLs in Cachex",
+ operationId: "AdminAPI.MediaProxyCacheController.index",
+ security: [%{"oAuth" => ["read:media_proxy_caches"]}],
+ parameters: [
+ Operation.parameter(
+ :page,
+ :query,
+ %Schema{type: :integer, default: 1},
+ "Page"
+ ),
+ Operation.parameter(
+ :page_size,
+ :query,
+ %Schema{type: :integer, default: 50},
+ "Number of statuses to return"
+ )
+ ],
+ responses: %{
+ 200 => success_response()
+ }
+ }
+ end
+
+ def delete_operation do
+ %Operation{
+ tags: ["Admin", "MediaProxyCache"],
+ summary: "Remove a banned MediaProxy URL from Cachex",
+ operationId: "AdminAPI.MediaProxyCacheController.delete",
+ security: [%{"oAuth" => ["write:media_proxy_caches"]}],
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ type: :object,
+ required: [:urls],
+ properties: %{
+ urls: %Schema{type: :array, items: %Schema{type: :string, format: :uri}}
+ }
+ },
+ required: true
+ ),
+ responses: %{
+ 200 => success_response(),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def purge_operation do
+ %Operation{
+ tags: ["Admin", "MediaProxyCache"],
+ summary: "Purge and optionally ban a MediaProxy URL",
+ operationId: "AdminAPI.MediaProxyCacheController.purge",
+ security: [%{"oAuth" => ["write:media_proxy_caches"]}],
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ type: :object,
+ required: [:urls],
+ properties: %{
+ urls: %Schema{type: :array, items: %Schema{type: :string, format: :uri}},
+ ban: %Schema{type: :boolean, default: true}
+ }
+ },
+ required: true
+ ),
+ responses: %{
+ 200 => success_response(),
+ 400 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp success_response do
+ Operation.response("Array of banned MediaProxy URLs in Cachex", "application/json", %Schema{
+ type: :object,
+ properties: %{
+ urls: %Schema{
+ type: :array,
+ items: %Schema{
+ type: :string,
+ format: :uri,
+ description: "MediaProxy URLs"
+ }
+ }
+ }
+ })
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex
new file mode 100644
index 000000000..cf299bfc2
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex
@@ -0,0 +1,355 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.ChatOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+ alias Pleroma.Web.ApiSpec.Schemas.Chat
+ alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ @spec open_api_operation(atom) :: Operation.t()
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def mark_as_read_operation do
+ %Operation{
+ tags: ["chat"],
+ summary: "Mark all messages in the chat as read",
+ operationId: "ChatController.mark_as_read",
+ parameters: [Operation.parameter(:id, :path, :string, "The ID of the Chat")],
+ requestBody: request_body("Parameters", mark_as_read()),
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The updated chat",
+ "application/json",
+ Chat
+ )
+ },
+ security: [
+ %{
+ "oAuth" => ["write:chats"]
+ }
+ ]
+ }
+ end
+
+ def mark_message_as_read_operation do
+ %Operation{
+ tags: ["chat"],
+ summary: "Mark one message in the chat as read",
+ operationId: "ChatController.mark_message_as_read",
+ parameters: [
+ Operation.parameter(:id, :path, :string, "The ID of the Chat"),
+ Operation.parameter(:message_id, :path, :string, "The ID of the message")
+ ],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The read ChatMessage",
+ "application/json",
+ ChatMessage
+ )
+ },
+ security: [
+ %{
+ "oAuth" => ["write:chats"]
+ }
+ ]
+ }
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["chat"],
+ summary: "Create a chat",
+ operationId: "ChatController.show",
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "The id of the chat",
+ required: true,
+ example: "1234"
+ )
+ ],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The existing chat",
+ "application/json",
+ Chat
+ )
+ },
+ security: [
+ %{
+ "oAuth" => ["read"]
+ }
+ ]
+ }
+ end
+
+ def create_operation do
+ %Operation{
+ tags: ["chat"],
+ summary: "Create a chat",
+ operationId: "ChatController.create",
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "The account id of the recipient of this chat",
+ required: true,
+ example: "someflakeid"
+ )
+ ],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The created or existing chat",
+ "application/json",
+ Chat
+ )
+ },
+ security: [
+ %{
+ "oAuth" => ["write:chats"]
+ }
+ ]
+ }
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["chat"],
+ summary: "Get a list of chats that you participated in",
+ operationId: "ChatController.index",
+ parameters: pagination_params(),
+ responses: %{
+ 200 => Operation.response("The chats of the user", "application/json", chats_response())
+ },
+ security: [
+ %{
+ "oAuth" => ["read:chats"]
+ }
+ ]
+ }
+ end
+
+ def messages_operation do
+ %Operation{
+ tags: ["chat"],
+ summary: "Get the most recent messages of the chat",
+ operationId: "ChatController.messages",
+ parameters:
+ [Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++
+ pagination_params(),
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The messages in the chat",
+ "application/json",
+ chat_messages_response()
+ )
+ },
+ security: [
+ %{
+ "oAuth" => ["read:chats"]
+ }
+ ]
+ }
+ end
+
+ def post_chat_message_operation do
+ %Operation{
+ tags: ["chat"],
+ summary: "Post a message to the chat",
+ operationId: "ChatController.post_chat_message",
+ parameters: [
+ Operation.parameter(:id, :path, :string, "The ID of the Chat")
+ ],
+ requestBody: request_body("Parameters", chat_message_create()),
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The newly created ChatMessage",
+ "application/json",
+ ChatMessage
+ ),
+ 400 => Operation.response("Bad Request", "application/json", ApiError)
+ },
+ security: [
+ %{
+ "oAuth" => ["write:chats"]
+ }
+ ]
+ }
+ end
+
+ def delete_message_operation do
+ %Operation{
+ tags: ["chat"],
+ summary: "delete_message",
+ operationId: "ChatController.delete_message",
+ parameters: [
+ Operation.parameter(:id, :path, :string, "The ID of the Chat"),
+ Operation.parameter(:message_id, :path, :string, "The ID of the message")
+ ],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The deleted ChatMessage",
+ "application/json",
+ ChatMessage
+ )
+ },
+ security: [
+ %{
+ "oAuth" => ["write:chats"]
+ }
+ ]
+ }
+ end
+
+ def chats_response do
+ %Schema{
+ title: "ChatsResponse",
+ description: "Response schema for multiple Chats",
+ type: :array,
+ items: Chat,
+ example: [
+ %{
+ "account" => %{
+ "pleroma" => %{
+ "is_admin" => false,
+ "confirmation_pending" => false,
+ "hide_followers_count" => false,
+ "is_moderator" => false,
+ "hide_favorites" => true,
+ "ap_id" => "https://dontbulling.me/users/lain",
+ "hide_follows_count" => false,
+ "hide_follows" => false,
+ "background_image" => nil,
+ "skip_thread_containment" => false,
+ "hide_followers" => false,
+ "relationship" => %{},
+ "tags" => []
+ },
+ "avatar" =>
+ "https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
+ "following_count" => 0,
+ "header_static" => "https://originalpatchou.li/images/banner.png",
+ "source" => %{
+ "sensitive" => false,
+ "note" => "lain",
+ "pleroma" => %{
+ "discoverable" => false,
+ "actor_type" => "Person"
+ },
+ "fields" => []
+ },
+ "statuses_count" => 1,
+ "locked" => false,
+ "created_at" => "2020-04-16T13:40:15.000Z",
+ "display_name" => "lain",
+ "fields" => [],
+ "acct" => "lain@dontbulling.me",
+ "id" => "9u6Qw6TAZANpqokMkK",
+ "emojis" => [],
+ "avatar_static" =>
+ "https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
+ "username" => "lain",
+ "followers_count" => 0,
+ "header" => "https://originalpatchou.li/images/banner.png",
+ "bot" => false,
+ "note" => "lain",
+ "url" => "https://dontbulling.me/users/lain"
+ },
+ "id" => "1",
+ "unread" => 2
+ }
+ ]
+ }
+ end
+
+ def chat_messages_response do
+ %Schema{
+ title: "ChatMessagesResponse",
+ description: "Response schema for multiple ChatMessages",
+ type: :array,
+ items: ChatMessage,
+ example: [
+ %{
+ "emojis" => [
+ %{
+ "static_url" => "https://dontbulling.me/emoji/Firefox.gif",
+ "visible_in_picker" => false,
+ "shortcode" => "firefox",
+ "url" => "https://dontbulling.me/emoji/Firefox.gif"
+ }
+ ],
+ "created_at" => "2020-04-21T15:11:46.000Z",
+ "content" => "Check this out :firefox:",
+ "id" => "13",
+ "chat_id" => "1",
+ "actor_id" => "someflakeid",
+ "unread" => false
+ },
+ %{
+ "actor_id" => "someflakeid",
+ "content" => "Whats' up?",
+ "id" => "12",
+ "chat_id" => "1",
+ "emojis" => [],
+ "created_at" => "2020-04-21T15:06:45.000Z",
+ "unread" => false
+ }
+ ]
+ }
+ end
+
+ def chat_message_create do
+ %Schema{
+ title: "ChatMessageCreateRequest",
+ description: "POST body for creating an chat message",
+ type: :object,
+ properties: %{
+ content: %Schema{
+ type: :string,
+ description: "The content of your message. Optional if media_id is present"
+ },
+ media_id: %Schema{type: :string, description: "The id of an upload"}
+ },
+ example: %{
+ "content" => "Hey wanna buy feet pics?",
+ "media_id" => "134234"
+ }
+ }
+ end
+
+ def mark_as_read do
+ %Schema{
+ title: "MarkAsReadRequest",
+ description: "POST body for marking a number of chat messages as read",
+ type: :object,
+ required: [:last_read_id],
+ properties: %{
+ last_read_id: %Schema{
+ type: :string,
+ description: "The content of your message."
+ }
+ },
+ example: %{
+ "last_read_id" => "abcdef12456"
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex
index 46e72f8bf..f09be64cb 100644
--- a/lib/pleroma/web/api_spec/operations/notification_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex
@@ -163,6 +163,13 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
description:
"Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.",
nullable: true
+ },
+ pleroma: %Schema{
+ type: :object,
+ properties: %{
+ is_seen: %Schema{type: :boolean},
+ is_muted: %Schema{type: :boolean}
+ }
}
},
example: %{
@@ -170,7 +177,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
"type" => "mention",
"created_at" => "2019-11-23T07:49:02.064Z",
"account" => Account.schema().example,
- "status" => Status.schema().example
+ "status" => Status.schema().example,
+ "pleroma" => %{"is_seen" => false, "is_muted" => false}
}
}
end
@@ -183,8 +191,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
"favourite",
"reblog",
"mention",
- "poll",
"pleroma:emoji_reaction",
+ "pleroma:chat_mention",
"move",
"follow_request"
],
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
index 567688ff5..b2b4f8713 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
@@ -33,6 +33,20 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
tags: ["Emoji Packs"],
summary: "Lists local custom emoji packs",
operationId: "PleromaAPI.EmojiPackController.index",
+ parameters: [
+ Operation.parameter(
+ :page,
+ :query,
+ %Schema{type: :integer, default: 1},
+ "Page"
+ ),
+ Operation.parameter(
+ :page_size,
+ :query,
+ %Schema{type: :integer, default: 50},
+ "Number of emoji packs to return"
+ )
+ ],
responses: %{
200 => emoji_packs_response()
}
@@ -44,7 +58,21 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
tags: ["Emoji Packs"],
summary: "Show emoji pack",
operationId: "PleromaAPI.EmojiPackController.show",
- parameters: [name_param()],
+ parameters: [
+ name_param(),
+ Operation.parameter(
+ :page,
+ :query,
+ %Schema{type: :integer, default: 1},
+ "Page"
+ ),
+ Operation.parameter(
+ :page_size,
+ :query,
+ %Schema{type: :integer, default: 30},
+ "Number of emoji to return"
+ )
+ ],
responses: %{
200 => Operation.response("Emoji Pack", "application/json", emoji_pack()),
400 => Operation.response("Bad Request", "application/json", ApiError),
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
index ca9db01e5..0b7fad793 100644
--- a/lib/pleroma/web/api_spec/operations/status_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -333,7 +333,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
%Operation{
tags: ["Statuses"],
summary: "Favourited statuses",
- description: "Statuses the user has favourited",
+ description:
+ "Statuses the user has favourited. Please note that you have to use the link headers to paginate this. You can not build the query parameters yourself.",
operationId: "StatusController.favourites",
parameters: pagination_params(),
security: [%{"oAuth" => ["read:favourites"]}],
diff --git a/lib/pleroma/web/api_spec/operations/subscription_operation.ex b/lib/pleroma/web/api_spec/operations/subscription_operation.ex
index c575a87e6..775dd795d 100644
--- a/lib/pleroma/web/api_spec/operations/subscription_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/subscription_operation.ex
@@ -141,6 +141,11 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation do
allOf: [BooleanLike],
nullable: true,
description: "Receive poll notifications?"
+ },
+ "pleroma:chat_mention": %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Receive chat notifications?"
}
}
}
diff --git a/lib/pleroma/web/api_spec/schemas/chat.ex b/lib/pleroma/web/api_spec/schemas/chat.ex
new file mode 100644
index 000000000..b4986b734
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/chat.ex
@@ -0,0 +1,75 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Chat do
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "Chat",
+ description: "Response schema for a Chat",
+ type: :object,
+ properties: %{
+ id: %Schema{type: :string},
+ account: %Schema{type: :object},
+ unread: %Schema{type: :integer},
+ last_message: ChatMessage,
+ updated_at: %Schema{type: :string, format: :"date-time"}
+ },
+ example: %{
+ "account" => %{
+ "pleroma" => %{
+ "is_admin" => false,
+ "confirmation_pending" => false,
+ "hide_followers_count" => false,
+ "is_moderator" => false,
+ "hide_favorites" => true,
+ "ap_id" => "https://dontbulling.me/users/lain",
+ "hide_follows_count" => false,
+ "hide_follows" => false,
+ "background_image" => nil,
+ "skip_thread_containment" => false,
+ "hide_followers" => false,
+ "relationship" => %{},
+ "tags" => []
+ },
+ "avatar" =>
+ "https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
+ "following_count" => 0,
+ "header_static" => "https://originalpatchou.li/images/banner.png",
+ "source" => %{
+ "sensitive" => false,
+ "note" => "lain",
+ "pleroma" => %{
+ "discoverable" => false,
+ "actor_type" => "Person"
+ },
+ "fields" => []
+ },
+ "statuses_count" => 1,
+ "locked" => false,
+ "created_at" => "2020-04-16T13:40:15.000Z",
+ "display_name" => "lain",
+ "fields" => [],
+ "acct" => "lain@dontbulling.me",
+ "id" => "9u6Qw6TAZANpqokMkK",
+ "emojis" => [],
+ "avatar_static" =>
+ "https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
+ "username" => "lain",
+ "followers_count" => 0,
+ "header" => "https://originalpatchou.li/images/banner.png",
+ "bot" => false,
+ "note" => "lain",
+ "url" => "https://dontbulling.me/users/lain"
+ },
+ "id" => "1",
+ "unread" => 2,
+ "last_message" => ChatMessage.schema().example(),
+ "updated_at" => "2020-04-21T15:06:45.000Z"
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex
new file mode 100644
index 000000000..3ee85aa76
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
+ alias OpenApiSpex.Schema
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "ChatMessage",
+ description: "Response schema for a ChatMessage",
+ nullable: true,
+ type: :object,
+ properties: %{
+ id: %Schema{type: :string},
+ account_id: %Schema{type: :string, description: "The Mastodon API id of the actor"},
+ chat_id: %Schema{type: :string},
+ content: %Schema{type: :string, nullable: true},
+ created_at: %Schema{type: :string, format: :"date-time"},
+ emojis: %Schema{type: :array},
+ attachment: %Schema{type: :object, nullable: true}
+ },
+ example: %{
+ "account_id" => "someflakeid",
+ "chat_id" => "1",
+ "content" => "hey you again",
+ "created_at" => "2020-04-21T15:06:45.000Z",
+ "emojis" => [
+ %{
+ "static_url" => "https://dontbulling.me/emoji/Firefox.gif",
+ "visible_in_picker" => false,
+ "shortcode" => "firefox",
+ "url" => "https://dontbulling.me/emoji/Firefox.gif"
+ }
+ ],
+ "id" => "14",
+ "attachment" => nil
+ }
+ })
+end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index 3f1a50b96..9bcb9f587 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -197,6 +197,13 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
defp changes(draft) do
direct? = draft.visibility == "direct"
+ additional = %{"cc" => draft.cc, "directMessage" => direct?}
+
+ additional =
+ case draft.expires_at do
+ %NaiveDateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at)
+ _ -> additional
+ end
changes =
%{
@@ -204,7 +211,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
actor: draft.user,
context: draft.context,
object: draft.object,
- additional: %{"cc" => draft.cc, "directMessage" => direct?}
+ additional: additional
}
|> Utils.maybe_add_list_data(draft.user, draft.visibility)
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index dbb3d7ade..04e081a8e 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.ActivityExpiration
alias Pleroma.Conversation.Participation
alias Pleroma.FollowingRelationship
+ alias Pleroma.Formatter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.ThreadMute
@@ -24,6 +25,53 @@ defmodule Pleroma.Web.CommonAPI do
require Pleroma.Constants
require Logger
+ def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
+ with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
+ :ok <- validate_chat_content_length(content, !!maybe_attachment),
+ {_, {:ok, chat_message_data, _meta}} <-
+ {:build_object,
+ Builder.chat_message(
+ user,
+ recipient.ap_id,
+ content |> format_chat_content,
+ attachment: maybe_attachment
+ )},
+ {_, {:ok, create_activity_data, _meta}} <-
+ {:build_create_activity, Builder.create(user, chat_message_data, [recipient.ap_id])},
+ {_, {:ok, %Activity{} = activity, _meta}} <-
+ {:common_pipeline,
+ Pipeline.common_pipeline(create_activity_data,
+ local: true
+ )} do
+ {:ok, activity}
+ end
+ end
+
+ defp format_chat_content(nil), do: nil
+
+ defp format_chat_content(content) do
+ {text, _, _} =
+ content
+ |> Formatter.html_escape("text/plain")
+ |> Formatter.linkify()
+ |> (fn {text, mentions, tags} ->
+ {String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
+ end).()
+
+ text
+ end
+
+ defp validate_chat_content_length(_, true), do: :ok
+ defp validate_chat_content_length(nil, false), do: {:error, :no_content}
+
+ defp validate_chat_content_length(content, _) do
+ if String.length(content) <= Pleroma.Config.get([:instance, :chat_limit]) do
+ :ok
+ else
+ {:error, :content_too_long}
+ end
+ end
+
def unblock(blocker, blocked) do
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
@@ -73,6 +121,7 @@ defmodule Pleroma.Web.CommonAPI do
object: follow_activity.data["id"],
type: "Accept"
}) do
+ Notification.update_notification_type(followed, follow_activity)
{:ok, follower}
end
end
@@ -374,20 +423,10 @@ defmodule Pleroma.Web.CommonAPI do
def post(user, %{status: _} = data) do
with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do
- draft.changes
- |> ActivityPub.create(draft.preview?)
- |> maybe_create_activity_expiration(draft.expires_at)
+ ActivityPub.create(draft.changes, draft.preview?)
end
end
- defp maybe_create_activity_expiration({:ok, activity}, %NaiveDateTime{} = expires_at) do
- with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
- {:ok, activity}
- end
- end
-
- defp maybe_create_activity_expiration(result, _), do: result
-
def pin(id, %{ap_id: user_ap_id} = user) do
with %Activity{
actor: ^user_ap_id,
@@ -427,12 +466,13 @@ defmodule Pleroma.Web.CommonAPI do
{:ok, activity}
end
- def thread_muted?(%{id: nil} = _user, _activity), do: false
-
- def thread_muted?(user, activity) do
- ThreadMute.exists?(user.id, activity.data["context"])
+ def thread_muted?(%User{id: user_id}, %{data: %{"context" => context}})
+ when is_binary("context") do
+ ThreadMute.exists?(user_id, context)
end
+ def thread_muted?(_, _), do: false
+
def report(user, data) do
with {:ok, account} <- get_reported_account(data.account_id),
{:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 6ec489f9a..15594125f 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -429,7 +429,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
%Activity{data: %{"to" => _to, "type" => type} = data} = activity
)
when type == "Create" do
- object = Object.normalize(activity)
+ object = Object.normalize(activity, false)
object_data =
cond do
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 5d67d75b5..69946fb81 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -57,35 +57,36 @@ defmodule Pleroma.Web.ControllerHelper do
end
end
+ @id_keys Pagination.page_keys() -- ["limit", "order"]
+ defp build_pagination_fields(conn, min_id, max_id, extra_params) do
+ params =
+ conn.params
+ |> Map.drop(Map.keys(conn.path_params))
+ |> Map.merge(extra_params)
+ |> Map.drop(@id_keys)
+
+ %{
+ "next" => current_url(conn, Map.put(params, :max_id, max_id)),
+ "prev" => current_url(conn, Map.put(params, :min_id, min_id)),
+ "id" => current_url(conn)
+ }
+ end
+
def get_pagination_fields(conn, activities, extra_params \\ %{}) do
case List.last(activities) do
- %{id: max_id} ->
- params =
- conn.params
- |> Map.drop(Map.keys(conn.path_params))
- |> Map.merge(extra_params)
- |> Map.drop(Pagination.page_keys() -- ["limit", "order"])
+ %{pagination_id: max_id} when not is_nil(max_id) ->
+ %{pagination_id: min_id} =
+ activities
+ |> List.first()
+
+ build_pagination_fields(conn, min_id, max_id, extra_params)
- min_id =
+ %{id: max_id} ->
+ %{id: min_id} =
activities
|> List.first()
- |> Map.get(:id)
-
- fields = %{
- "next" => current_url(conn, Map.put(params, :max_id, max_id)),
- "prev" => current_url(conn, Map.put(params, :min_id, min_id))
- }
-
- # Generating an `id` without already present pagination keys would
- # need a query-restriction with an `q.id >= ^id` or `q.id <= ^id`
- # instead of the `q.id > ^min_id` and `q.id < ^max_id`.
- # This is because we only have ids present inside of the page, while
- # `min_id`, `since_id` and `max_id` requires to know one outside of it.
- if Map.take(conn.params, Pagination.page_keys() -- ["limit", "order"]) != [] do
- Map.put(fields, "id", current_url(conn, conn.params))
- else
- fields
- end
+
+ build_pagination_fields(conn, min_id, max_id, extra_params)
_ ->
%{}
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
index d0d8bc8eb..43ec70021 100644
--- a/lib/pleroma/web/masto_fe_controller.ex
+++ b/lib/pleroma/web/masto_fe_controller.ex
@@ -49,7 +49,7 @@ defmodule Pleroma.Web.MastoFEController do
|> render("manifest.json")
end
- @doc "PUT /api/web/settings"
+ @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
with {:ok, _} <- User.mastodon_settings_update(user, settings) do
json(conn, %{})
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 7cdd8f458..d50e7c5dd 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -165,6 +165,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end)
|> Maps.put_if_present(:name, params[:display_name])
|> Maps.put_if_present(:bio, params[:note])
+ |> Maps.put_if_present(:raw_bio, params[:note])
|> Maps.put_if_present(:avatar, params[:avatar])
|> Maps.put_if_present(:banner, params[:header])
|> Maps.put_if_present(:background, params[:pleroma_background_image])
@@ -176,6 +177,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:pleroma_settings_store, params[:pleroma_settings_store])
|> Maps.put_if_present(:default_scope, params[:default_scope])
|> Maps.put_if_present(:default_scope, params["source"]["privacy"])
+ |> Maps.put_if_present(:actor_type, params[:bot], fn bot ->
+ if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
+ end)
|> Maps.put_if_present(:actor_type, params[:actor_type])
changeset = User.update_changeset(user, user_params)
@@ -230,17 +234,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
@doc "GET /api/v1/accounts/:id"
def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
- true <- User.visible_for?(user, for_user) do
+ :visible <- User.visible_for(user, for_user) do
render(conn, "show.json", user: user, for: for_user)
else
- _e -> render_error(conn, :not_found, "Can't find user")
+ error -> user_visibility_error(conn, error)
end
end
@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),
- true <- User.visible_for?(user, reading_user) do
+ :visible <- User.visible_for(user, reading_user) do
params =
params
|> Map.delete(:tagged)
@@ -257,7 +261,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
as: :activity
)
else
- _e -> render_error(conn, :not_found, "Can't find user")
+ error -> user_visibility_error(conn, error)
+ end
+ end
+
+ defp user_visibility_error(conn, error) do
+ case error do
+ :restrict_unauthenticated ->
+ render_error(conn, :unauthorized, "This API requires an authenticated user")
+
+ _ ->
+ render_error(conn, :not_found, "Can't find user")
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
index bcd12c73f..e25cef30b 100644
--- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -42,8 +42,20 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
end
end
+ @default_notification_types ~w{
+ mention
+ follow
+ follow_request
+ reblog
+ favourite
+ move
+ pleroma:emoji_reaction
+ }
def index(%{assigns: %{user: user}} = conn, params) do
- params = Map.new(params, fn {k, v} -> {to_string(k), v} end)
+ params =
+ Map.new(params, fn {k, v} -> {to_string(k), v} end)
+ |> Map.put_new("include_types", @default_notification_types)
+
notifications = MastodonAPI.get_notifications(user, params)
conn
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
index 8840fc19c..e50980122 100644
--- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
@@ -107,23 +107,24 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
)
end
- defp resource_search(:v2, "hashtags", query, _options) do
+ defp resource_search(:v2, "hashtags", query, options) do
tags_path = Web.base_url() <> "/tag/"
query
- |> prepare_tags()
+ |> prepare_tags(options)
|> Enum.map(fn tag ->
%{name: tag, url: tags_path <> tag}
end)
end
- defp resource_search(:v1, "hashtags", query, _options) do
- prepare_tags(query)
+ defp resource_search(:v1, "hashtags", query, options) do
+ prepare_tags(query, options)
end
- defp prepare_tags(query, add_joined_tag \\ true) do
+ defp prepare_tags(query, options) do
tags =
query
+ |> preprocess_uri_query()
|> String.split(~r/[^#\w]+/u, trim: true)
|> Enum.uniq_by(&String.downcase/1)
@@ -138,12 +139,33 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
- if Enum.empty?(explicit_tags) && add_joined_tag do
- tags
- |> Kernel.++([joined_tag(tags)])
- |> Enum.uniq_by(&String.downcase/1)
+ tags =
+ if Enum.empty?(explicit_tags) && !options[:skip_joined_tag] do
+ add_joined_tag(tags)
+ else
+ tags
+ end
+
+ Pleroma.Pagination.paginate(tags, options)
+ end
+
+ defp add_joined_tag(tags) do
+ tags
+ |> Kernel.++([joined_tag(tags)])
+ |> Enum.uniq_by(&String.downcase/1)
+ end
+
+ # If `query` is a URI, returns last component of its path, otherwise returns `query`
+ defp preprocess_uri_query(query) do
+ if query =~ ~r/https?:\/\// do
+ query
+ |> String.trim_trailing("/")
+ |> URI.parse()
+ |> Map.get(:path)
+ |> String.split("/")
+ |> Enum.at(-1)
else
- tags
+ query
end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 9270ca267..4bdd46d7e 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -48,6 +48,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.put(:blocking_user, user)
|> Map.put(:muting_user, user)
|> Map.put(:reply_filtering_user, user)
+ |> Map.put(:announce_filtering_user, user)
|> Map.put(:user, user)
activities =
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index 70da64a7a..694bf5ca8 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -6,7 +6,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
import Ecto.Query
import Ecto.Changeset
- alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Pagination
alias Pleroma.ScheduledActivity
@@ -82,15 +81,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
end
defp restrict(query, :include_types, %{include_types: mastodon_types = [_ | _]}) do
- ap_types = convert_and_filter_mastodon_types(mastodon_types)
-
- where(query, [q, a], fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
+ where(query, [n], n.type in ^mastodon_types)
end
defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do
- ap_types = convert_and_filter_mastodon_types(mastodon_types)
-
- where(query, [q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
+ where(query, [n], n.type not in ^mastodon_types)
end
defp restrict(query, :account_ap_id, %{account_ap_id: account_ap_id}) do
@@ -98,10 +93,4 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
end
defp restrict(query, _, _), do: query
-
- defp convert_and_filter_mastodon_types(types) do
- types
- |> Enum.map(&Activity.from_mastodon_notification_type/1)
- |> Enum.filter(& &1)
- end
end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 04c419d2f..a6e64b4ab 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
def render("show.json", %{user: user} = opts) do
- if User.visible_for?(user, opts[:for]) do
+ if User.visible_for(user, opts[:for]) == :visible do
do_render("show.json", opts)
else
%{}
@@ -179,7 +179,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
0
end
- bot = user.actor_type in ["Application", "Service"]
+ bot = user.actor_type == "Service"
emojis =
Enum.map(user.emoji, fn {shortcode, raw_url} ->
@@ -224,7 +224,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
fields: user.fields,
bot: bot,
source: %{
- note: prepare_user_bio(user),
+ note: user.raw_bio || "",
sensitive: false,
fields: user.raw_fields,
pleroma: %{
@@ -235,6 +235,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
# Pleroma extension
pleroma: %{
+ ap_id: user.ap_id,
confirmation_pending: user.confirmation_pending,
tags: user.tags,
hide_followers_count: user.hide_followers_count,
@@ -259,17 +260,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> maybe_put_unread_notification_count(user, opts[:for])
end
- defp prepare_user_bio(%User{bio: ""}), do: ""
-
- defp prepare_user_bio(%User{bio: bio}) when is_binary(bio) do
- bio
- |> String.replace(~r(<br */?>), "\n")
- |> Pleroma.HTML.strip_tags()
- |> HtmlEntities.decode()
- end
-
- defp prepare_user_bio(_), do: ""
-
defp username_from_nickname(string) when is_binary(string) do
hd(String.split(string, "@"))
end
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
index fbe618377..06f0c1728 100644
--- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -23,10 +23,13 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
last_activity_id =
with nil <- participation.last_activity_id do
- ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
- user: user,
- blocking_user: user
- })
+ ActivityPub.fetch_latest_direct_activity_id_for_context(
+ participation.conversation.ap_id,
+ %{
+ user: user,
+ blocking_user: user
+ }
+ )
end
activity = Activity.get_by_id_with_object(last_activity_id)
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 6a630eafa..35c2fc25c 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -23,7 +23,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
streaming_api: Pleroma.Web.Endpoint.websocket_url()
},
stats: Pleroma.Stats.get_stats(),
- thumbnail: instance_thumbnail(),
+ thumbnail: Keyword.get(instance, :instance_thumbnail),
languages: ["en"],
registrations: Keyword.get(instance, :registrations_open),
# Extra (not present in Mastodon):
@@ -69,7 +69,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
if Config.get([:instance, :safe_dm_mentions]) do
"safe_dm_mentions"
end,
- "pleroma_emoji_reactions"
+ "pleroma_emoji_reactions",
+ "pleroma_chat_messages"
]
|> Enum.filter(& &1)
end
@@ -77,7 +78,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
def federation do
quarantined = Config.get([:instance, :quarantined_instances], [])
- if Config.get([:instance, :mrf_transparency]) do
+ if Config.get([:mrf, :transparency]) do
{:ok, data} = MRF.describe()
data
@@ -87,9 +88,4 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
end
|> Map.put(:enabled, Config.get([:instance, :federating]))
end
-
- defp instance_thumbnail do
- Pleroma.Config.get([:instance, :instance_thumbnail]) ||
- "#{Pleroma.Web.base_url()}/instance/thumbnail.jpeg"
- end
end
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
index c46ddcf55..c97e6d32f 100644
--- a/lib/pleroma/web/mastodon_api/views/notification_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex
@@ -6,26 +6,28 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
use Pleroma.Web, :view
alias Pleroma.Activity
+ alias Pleroma.Chat.MessageReference
alias Pleroma.Notification
+ alias Pleroma.Object
alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
+
+ @parent_types ~w{Like Announce EmojiReact}
def render("index.json", %{notifications: notifications, for: reading_user} = opts) do
activities = Enum.map(notifications, & &1.activity)
parent_activities =
activities
- |> Enum.filter(
- &(Activity.mastodon_notification_type(&1) in [
- "favourite",
- "reblog",
- "pleroma:emoji_reaction"
- ])
- )
+ |> Enum.filter(fn
+ %{data: %{"type" => type}} ->
+ type in @parent_types
+ end)
|> Enum.map(& &1.data["object"])
|> Activity.create_by_object_ap_id()
|> Activity.with_preloaded_object(:left)
@@ -42,8 +44,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
true ->
move_activities_targets =
activities
- |> Enum.filter(&(Activity.mastodon_notification_type(&1) == "move"))
+ |> Enum.filter(&(&1.data["type"] == "Move"))
|> Enum.map(&User.get_cached_by_ap_id(&1.data["target"]))
+ |> Enum.filter(& &1)
actors =
activities
@@ -79,52 +82,44 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
end
end
- mastodon_type = Activity.mastodon_notification_type(activity)
-
# Note: :relationships contain user mutes (needed for :muted flag in :status)
status_render_opts = %{relationships: opts[:relationships]}
-
- with %{id: _} = account <-
- AccountView.render(
- "show.json",
- %{user: actor, for: reading_user}
- ) do
- response = %{
- id: to_string(notification.id),
- type: mastodon_type,
- created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
- account: account,
- pleroma: %{
- is_seen: notification.seen
- }
+ account = AccountView.render("show.json", %{user: actor, for: reading_user})
+
+ response = %{
+ id: to_string(notification.id),
+ type: notification.type,
+ created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
+ account: account,
+ pleroma: %{
+ is_muted: User.mutes?(reading_user, actor),
+ is_seen: notification.seen
}
+ }
- case mastodon_type do
- "mention" ->
- put_status(response, activity, reading_user, status_render_opts)
+ case notification.type do
+ "mention" ->
+ put_status(response, activity, reading_user, status_render_opts)
- "favourite" ->
- put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
+ "favourite" ->
+ put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
- "reblog" ->
- put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
+ "reblog" ->
+ put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
- "move" ->
- put_target(response, activity, reading_user, %{})
+ "move" ->
+ put_target(response, activity, reading_user, %{})
- "pleroma:emoji_reaction" ->
- response
- |> put_status(parent_activity_fn.(), reading_user, status_render_opts)
- |> put_emoji(activity)
+ "pleroma:emoji_reaction" ->
+ response
+ |> put_status(parent_activity_fn.(), reading_user, status_render_opts)
+ |> put_emoji(activity)
- type when type in ["follow", "follow_request"] ->
- response
+ "pleroma:chat_mention" ->
+ put_chat_message(response, activity, reading_user, status_render_opts)
- _ ->
- nil
- end
- else
- _ -> nil
+ type when type in ["follow", "follow_request"] ->
+ response
end
end
@@ -132,6 +127,17 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
Map.put(response, :emoji, activity.data["content"])
end
+ defp put_chat_message(response, activity, reading_user, opts) do
+ object = Object.normalize(activity)
+ author = User.get_cached_by_ap_id(object.data["actor"])
+ chat = Pleroma.Chat.get(reading_user.id, author.ap_id)
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+ render_opts = Map.merge(opts, %{for: reading_user, chat_message_reference: cm_ref})
+ chat_message_render = MessageReferenceView.render("show.json", render_opts)
+
+ Map.put(response, :chat_message, chat_message_render)
+ end
+
defp put_status(response, activity, reading_user, opts) do
status_render_opts = Map.merge(opts, %{activity: activity, for: reading_user})
status_render = StatusView.render("show.json", status_render_opts)
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 8e3715093..2c49bedb3 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -377,8 +377,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
page_url_data = URI.parse(page_url)
page_url_data =
- if rich_media[:url] != nil do
- URI.merge(page_url_data, URI.parse(rich_media[:url]))
+ if is_binary(rich_media["url"]) do
+ URI.merge(page_url_data, URI.parse(rich_media["url"]))
else
page_url_data
end
@@ -386,11 +386,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
page_url = page_url_data |> to_string
image_url =
- if rich_media[:image] != nil do
- URI.merge(page_url_data, URI.parse(rich_media[:image]))
+ if is_binary(rich_media["image"]) do
+ URI.merge(page_url_data, URI.parse(rich_media["image"]))
|> to_string
- else
- nil
end
%{
@@ -399,8 +397,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
url: page_url,
image: image_url |> MediaProxy.url(),
- title: rich_media[:title] || "",
- description: rich_media[:description] || "",
+ title: rich_media["title"] || "",
+ description: rich_media["description"] || "",
pleroma: %{
opengraph: rich_media
}
diff --git a/lib/pleroma/web/media_proxy/invalidation.ex b/lib/pleroma/web/media_proxy/invalidation.ex
index c037ff13e..5808861e6 100644
--- a/lib/pleroma/web/media_proxy/invalidation.ex
+++ b/lib/pleroma/web/media_proxy/invalidation.ex
@@ -5,22 +5,34 @@
defmodule Pleroma.Web.MediaProxy.Invalidation do
@moduledoc false
- @callback purge(list(String.t()), map()) :: {:ok, String.t()} | {:error, String.t()}
+ @callback purge(list(String.t()), Keyword.t()) :: {:ok, list(String.t())} | {:error, String.t()}
alias Pleroma.Config
+ alias Pleroma.Web.MediaProxy
- @spec purge(list(String.t())) :: {:ok, String.t()} | {:error, String.t()}
+ @spec enabled?() :: boolean()
+ def enabled?, do: Config.get([:media_proxy, :invalidation, :enabled])
+
+ @spec purge(list(String.t()) | String.t()) :: {:ok, list(String.t())} | {:error, String.t()}
def purge(urls) do
- [:media_proxy, :invalidation, :enabled]
- |> Config.get()
- |> do_purge(urls)
+ prepared_urls = prepare_urls(urls)
+
+ if enabled?() do
+ do_purge(prepared_urls)
+ else
+ {:ok, prepared_urls}
+ end
end
- defp do_purge(true, urls) do
+ defp do_purge(urls) do
provider = Config.get([:media_proxy, :invalidation, :provider])
options = Config.get(provider)
provider.purge(urls, options)
end
- defp do_purge(_, _), do: :ok
+ def prepare_urls(urls) do
+ urls
+ |> List.wrap()
+ |> Enum.map(&MediaProxy.url/1)
+ end
end
diff --git a/lib/pleroma/web/media_proxy/invalidations/http.ex b/lib/pleroma/web/media_proxy/invalidations/http.ex
index 07248df6e..bb81d8888 100644
--- a/lib/pleroma/web/media_proxy/invalidations/http.ex
+++ b/lib/pleroma/web/media_proxy/invalidations/http.ex
@@ -9,10 +9,10 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Http do
require Logger
@impl Pleroma.Web.MediaProxy.Invalidation
- def purge(urls, opts) do
- method = Map.get(opts, :method, :purge)
- headers = Map.get(opts, :headers, [])
- options = Map.get(opts, :options, [])
+ def purge(urls, opts \\ []) do
+ method = Keyword.get(opts, :method, :purge)
+ headers = Keyword.get(opts, :headers, [])
+ options = Keyword.get(opts, :options, [])
Logger.debug("Running cache purge: #{inspect(urls)}")
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Http do
end
end)
- {:ok, "success"}
+ {:ok, urls}
end
defp do_purge(method, url, headers, options) do
diff --git a/lib/pleroma/web/media_proxy/invalidations/script.ex b/lib/pleroma/web/media_proxy/invalidations/script.ex
index 6be782132..d32ffc50b 100644
--- a/lib/pleroma/web/media_proxy/invalidations/script.ex
+++ b/lib/pleroma/web/media_proxy/invalidations/script.ex
@@ -10,32 +10,34 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Script do
require Logger
@impl Pleroma.Web.MediaProxy.Invalidation
- def purge(urls, %{script_path: script_path} = _options) do
+ def purge(urls, opts \\ []) do
args =
urls
|> List.wrap()
|> Enum.uniq()
|> Enum.join(" ")
- path = Path.expand(script_path)
-
- Logger.debug("Running cache purge: #{inspect(urls)}, #{path}")
-
- case do_purge(path, [args]) do
- {result, exit_status} when exit_status > 0 ->
- Logger.error("Error while cache purge: #{inspect(result)}")
- {:error, inspect(result)}
-
- _ ->
- {:ok, "success"}
- end
+ opts
+ |> Keyword.get(:script_path)
+ |> do_purge([args])
+ |> handle_result(urls)
end
- def purge(_, _), do: {:error, "not found script path"}
-
- defp do_purge(path, args) do
+ defp do_purge(script_path, args) when is_binary(script_path) do
+ path = Path.expand(script_path)
+ Logger.debug("Running cache purge: #{inspect(args)}, #{inspect(path)}")
System.cmd(path, args)
rescue
- error -> {inspect(error), 1}
+ error -> error
+ end
+
+ defp do_purge(_, _), do: {:error, "not found script path"}
+
+ defp handle_result({_result, 0}, urls), do: {:ok, urls}
+ defp handle_result({:error, error}, urls), do: handle_result(error, urls)
+
+ defp handle_result(error, _) do
+ Logger.error("Error while cache purge: #{inspect(error)}")
+ {:error, inspect(error)}
end
end
diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex
index b2b524524..077fabe47 100644
--- a/lib/pleroma/web/media_proxy/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy.ex
@@ -6,20 +6,53 @@ defmodule Pleroma.Web.MediaProxy do
alias Pleroma.Config
alias Pleroma.Upload
alias Pleroma.Web
+ alias Pleroma.Web.MediaProxy.Invalidation
@base64_opts [padding: false]
+ @spec in_banned_urls(String.t()) :: boolean()
+ def in_banned_urls(url), do: elem(Cachex.exists?(:banned_urls_cache, url(url)), 1)
+
+ def remove_from_banned_urls(urls) when is_list(urls) do
+ Cachex.execute!(:banned_urls_cache, fn cache ->
+ Enum.each(Invalidation.prepare_urls(urls), &Cachex.del(cache, &1))
+ end)
+ end
+
+ def remove_from_banned_urls(url) when is_binary(url) do
+ Cachex.del(:banned_urls_cache, url(url))
+ end
+
+ def put_in_banned_urls(urls) when is_list(urls) do
+ Cachex.execute!(:banned_urls_cache, fn cache ->
+ Enum.each(Invalidation.prepare_urls(urls), &Cachex.put(cache, &1, true))
+ end)
+ end
+
+ def put_in_banned_urls(url) when is_binary(url) do
+ Cachex.put(:banned_urls_cache, url(url), true)
+ end
+
def url(url) when is_nil(url) or url == "", do: nil
def url("/" <> _ = url), do: url
def url(url) do
- if disabled?() or local?(url) or whitelisted?(url) do
+ if disabled?() or not url_proxiable?(url) do
url
else
encode_url(url)
end
end
+ @spec url_proxiable?(String.t()) :: boolean()
+ def url_proxiable?(url) do
+ if local?(url) or whitelisted?(url) do
+ false
+ else
+ true
+ end
+ end
+
defp disabled?, do: !Config.get([:media_proxy, :enabled], false)
defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
index 4657a4383..9a64b0ef3 100644
--- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
@@ -14,10 +14,11 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
with config <- Pleroma.Config.get([:media_proxy], []),
true <- Keyword.get(config, :enabled, false),
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
+ {_, false} <- {:in_banned_urls, MediaProxy.in_banned_urls(url)},
:ok <- filename_matches(params, conn.request_path, url) do
ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
else
- false ->
+ error when error in [false, {:in_banned_urls, true}] ->
send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404))
{:error, :invalid_signature} ->
diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
new file mode 100644
index 000000000..c8ef3d915
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
@@ -0,0 +1,174 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Web.PleromaAPI.ChatController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Activity
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.Object
+ alias Pleroma.Pagination
+ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
+ alias Pleroma.Web.PleromaAPI.ChatView
+
+ import Ecto.Query
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:chats"]}
+ when action in [
+ :post_chat_message,
+ :create,
+ :mark_as_read,
+ :mark_message_as_read,
+ :delete_message
+ ]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:chats"]} when action in [:messages, :index, :show]
+ )
+
+ plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
+
+ def delete_message(%{assigns: %{user: %{id: user_id} = user}} = conn, %{
+ message_id: message_id,
+ id: chat_id
+ }) do
+ with %MessageReference{} = cm_ref <-
+ MessageReference.get_by_id(message_id),
+ ^chat_id <- cm_ref.chat_id |> to_string(),
+ %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
+ {:ok, _} <- remove_or_delete(cm_ref, user) do
+ conn
+ |> put_view(MessageReferenceView)
+ |> render("show.json", chat_message_reference: cm_ref)
+ else
+ _e ->
+ {:error, :could_not_delete}
+ end
+ end
+
+ defp remove_or_delete(
+ %{object: %{data: %{"actor" => actor, "id" => id}}},
+ %{ap_id: actor} = user
+ ) do
+ with %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
+ CommonAPI.delete(activity.id, user)
+ end
+ end
+
+ defp remove_or_delete(cm_ref, _) do
+ cm_ref
+ |> MessageReference.delete()
+ end
+
+ def post_chat_message(
+ %{body_params: params, assigns: %{user: %{id: user_id} = user}} = conn,
+ %{
+ id: id
+ }
+ ) do
+ with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
+ %User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
+ {:ok, activity} <-
+ CommonAPI.post_chat_message(user, recipient, params[:content],
+ media_id: params[:media_id]
+ ),
+ message <- Object.normalize(activity, false),
+ cm_ref <- MessageReference.for_chat_and_object(chat, message) do
+ conn
+ |> put_view(MessageReferenceView)
+ |> render("show.json", for: user, chat_message_reference: cm_ref)
+ end
+ end
+
+ def mark_message_as_read(%{assigns: %{user: %{id: user_id} = user}} = conn, %{
+ id: chat_id,
+ message_id: message_id
+ }) do
+ with %MessageReference{} = cm_ref <-
+ MessageReference.get_by_id(message_id),
+ ^chat_id <- cm_ref.chat_id |> to_string(),
+ %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
+ {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
+ conn
+ |> put_view(MessageReferenceView)
+ |> render("show.json", for: user, chat_message_reference: cm_ref)
+ end
+ end
+
+ def mark_as_read(
+ %{body_params: %{last_read_id: last_read_id}, assigns: %{user: %{id: user_id}}} = conn,
+ %{id: id}
+ ) do
+ with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
+ {_n, _} <-
+ MessageReference.set_all_seen_for_chat(chat, last_read_id) do
+ conn
+ |> put_view(ChatView)
+ |> render("show.json", chat: chat)
+ end
+ end
+
+ def messages(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: id} = params) do
+ with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do
+ cm_refs =
+ chat
+ |> MessageReference.for_chat_query()
+ |> Pagination.fetch_paginated(params)
+
+ conn
+ |> put_view(MessageReferenceView)
+ |> render("index.json", for: user, chat_message_references: cm_refs)
+ else
+ _ ->
+ conn
+ |> put_status(:not_found)
+ |> json(%{error: "not found"})
+ end
+ end
+
+ def index(%{assigns: %{user: %{id: user_id} = user}} = conn, _params) do
+ blocked_ap_ids = User.blocked_users_ap_ids(user)
+
+ chats =
+ from(c in Chat,
+ where: c.user_id == ^user_id,
+ where: c.recipient not in ^blocked_ap_ids,
+ order_by: [desc: c.updated_at]
+ )
+ |> Repo.all()
+
+ conn
+ |> put_view(ChatView)
+ |> render("index.json", chats: chats)
+ end
+
+ def create(%{assigns: %{user: user}} = conn, params) do
+ with %User{ap_id: recipient} <- User.get_by_id(params[:id]),
+ {:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
+ conn
+ |> put_view(ChatView)
+ |> render("show.json", chat: chat)
+ end
+ end
+
+ def show(%{assigns: %{user: user}} = conn, params) do
+ with %Chat{} = chat <- Repo.get_by(Chat, user_id: user.id, id: params[:id]) do
+ conn
+ |> put_view(ChatView)
+ |> render("show.json", chat: chat)
+ end
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
index d1efdeb5d..33ecd1f70 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
@@ -37,14 +37,14 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
end
end
- def index(conn, _params) do
+ def index(conn, params) do
emoji_path =
[:instance, :static_dir]
|> Pleroma.Config.get!()
|> Path.join("emoji")
- with {:ok, packs} <- Pack.list_local() do
- json(conn, packs)
+ with {:ok, packs, count} <- Pack.list_local(page: params.page, page_size: params.page_size) do
+ json(conn, %{packs: packs, count: count})
else
{:error, :create_dir, e} ->
conn
@@ -60,10 +60,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
end
end
- def show(conn, %{name: name}) do
+ def show(conn, %{name: name, page: page, page_size: page_size}) do
name = String.trim(name)
- with {:ok, pack} <- Pack.show(name) do
+ with {:ok, pack} <- Pack.show(name: name, page: page, page_size: page_size) do
json(conn, pack)
else
{:error, :not_found} ->
diff --git a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
new file mode 100644
index 000000000..f2112a86e
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ def render(
+ "show.json",
+ %{
+ chat_message_reference: %{
+ id: id,
+ object: %{data: chat_message},
+ chat_id: chat_id,
+ unread: unread
+ }
+ }
+ ) do
+ %{
+ id: id |> to_string(),
+ content: chat_message["content"],
+ chat_id: chat_id |> to_string(),
+ account_id: User.get_cached_by_ap_id(chat_message["actor"]).id,
+ created_at: Utils.to_masto_date(chat_message["published"]),
+ emojis: StatusView.build_emojis(chat_message["emoji"]),
+ attachment:
+ chat_message["attachment"] &&
+ StatusView.render("attachment.json", attachment: chat_message["attachment"]),
+ unread: unread
+ }
+ end
+
+ def render("index.json", opts) do
+ render_many(
+ opts[:chat_message_references],
+ __MODULE__,
+ "show.json",
+ Map.put(opts, :as, :chat_message_reference)
+ )
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/views/chat_view.ex b/lib/pleroma/web/pleroma_api/views/chat_view.ex
new file mode 100644
index 000000000..1c996da11
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/views/chat_view.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.ChatView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
+
+ def render("show.json", %{chat: %Chat{} = chat} = opts) do
+ recipient = User.get_cached_by_ap_id(chat.recipient)
+ last_message = opts[:last_message] || MessageReference.last_message_for_chat(chat)
+
+ %{
+ id: chat.id |> to_string(),
+ account: AccountView.render("show.json", Map.put(opts, :user, recipient)),
+ unread: MessageReference.unread_count_for_chat(chat),
+ last_message:
+ last_message &&
+ MessageReferenceView.render("show.json", chat_message_reference: last_message),
+ updated_at: Utils.to_masto_date(chat.updated_at)
+ }
+ end
+
+ def render("index.json", %{chats: chats}) do
+ render_many(chats, __MODULE__, "show.json")
+ end
+end
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index 691725702..cdb827e76 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -16,8 +16,6 @@ defmodule Pleroma.Web.Push.Impl do
require Logger
import Ecto.Query
- defdelegate mastodon_notification_type(activity), to: Activity
-
@types ["Create", "Follow", "Announce", "Like", "Move"]
@doc "Performs sending notifications for user subscriptions"
@@ -31,10 +29,10 @@ defmodule Pleroma.Web.Push.Impl do
when activity_type in @types do
actor = User.get_cached_by_ap_id(notification.activity.data["actor"])
- mastodon_type = mastodon_notification_type(notification.activity)
+ mastodon_type = notification.type
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
avatar_url = User.avatar_url(actor)
- object = Object.normalize(activity)
+ object = Object.normalize(activity, false)
user = User.get_cached_by_id(user_id)
direct_conversation_id = Activity.direct_conversation_id(activity, user)
@@ -116,7 +114,7 @@ defmodule Pleroma.Web.Push.Impl do
end
def build_content(notification, actor, object, mastodon_type) do
- mastodon_type = mastodon_type || mastodon_notification_type(notification.activity)
+ mastodon_type = mastodon_type || notification.type
%{
title: format_title(notification, mastodon_type),
@@ -126,6 +124,13 @@ defmodule Pleroma.Web.Push.Impl do
def format_body(activity, actor, object, mastodon_type \\ nil)
+ def format_body(_activity, actor, %{data: %{"type" => "ChatMessage", "content" => content}}, _) do
+ case content do
+ nil -> "@#{actor.nickname}: (Attachment)"
+ content -> "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
+ end
+ end
+
def format_body(
%{activity: %{data: %{"type" => "Create"}}},
actor,
@@ -151,7 +156,7 @@ defmodule Pleroma.Web.Push.Impl do
mastodon_type
)
when type in ["Follow", "Like"] do
- mastodon_type = mastodon_type || mastodon_notification_type(notification.activity)
+ mastodon_type = mastodon_type || notification.type
case mastodon_type do
"follow" -> "@#{actor.nickname} has followed you"
@@ -166,15 +171,14 @@ defmodule Pleroma.Web.Push.Impl do
"New Direct Message"
end
- def format_title(%{activity: activity}, mastodon_type) do
- mastodon_type = mastodon_type || mastodon_notification_type(activity)
-
- case mastodon_type do
+ def format_title(%{type: type}, mastodon_type) do
+ case mastodon_type || type do
"mention" -> "New Mention"
"follow" -> "New Follower"
"follow_request" -> "New Follow Request"
"reblog" -> "New Repeat"
"favourite" -> "New Favorite"
+ "pleroma:chat_mention" -> "New Chat Message"
type -> "New #{String.capitalize(type || "event")}"
end
end
diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex
index 3e401a490..5b5aa0d59 100644
--- a/lib/pleroma/web/push/subscription.ex
+++ b/lib/pleroma/web/push/subscription.ex
@@ -25,7 +25,7 @@ defmodule Pleroma.Web.Push.Subscription do
timestamps()
end
- @supported_alert_types ~w[follow favourite mention reblog]a
+ @supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention]a
defp alerts(%{data: %{alerts: alerts}}) do
alerts = Map.take(alerts, @supported_alert_types)
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index 9d3d7f978..1729141e9 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
alias Pleroma.Object
alias Pleroma.Web.RichMedia.Parser
- @spec validate_page_url(any()) :: :ok | :error
+ @spec validate_page_url(URI.t() | binary()) :: :ok | :error
defp validate_page_url(page_url) when is_binary(page_url) do
validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld]
@@ -18,8 +18,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|> parse_uri(page_url)
end
- defp validate_page_url(%URI{host: host, scheme: scheme, authority: authority})
- when scheme == "https" and not is_nil(authority) do
+ defp validate_page_url(%URI{host: host, scheme: "https", authority: authority})
+ when is_binary(authority) do
cond do
host in Config.get([:rich_media, :ignore_hosts], []) ->
:error
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 40980def8..ef5ead2da 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -91,7 +91,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
html
|> parse_html()
|> maybe_parse()
- |> Map.put(:url, url)
+ |> Map.put("url", url)
|> clean_parsed_data()
|> check_parsed_data()
rescue
@@ -105,14 +105,14 @@ defmodule Pleroma.Web.RichMedia.Parser do
defp maybe_parse(html) do
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
case parser.parse(html, acc) do
- {:ok, data} -> {:halt, data}
- {:error, _msg} -> {:cont, acc}
+ data when data != %{} -> {:halt, data}
+ _ -> {:cont, acc}
end
end)
end
- defp check_parsed_data(%{title: title} = data)
- when is_binary(title) and byte_size(title) > 0 do
+ defp check_parsed_data(%{"title" => title} = data)
+ when is_binary(title) and title != "" do
{:ok, data}
end
@@ -123,11 +123,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
defp clean_parsed_data(data) do
data
|> Enum.reject(fn {key, val} ->
- with {:ok, _} <- Jason.encode(%{key => val}) do
- false
- else
- _ -> true
- end
+ not match?({:ok, _}, Jason.encode(%{key => val}))
end)
|> Map.new()
end
diff --git a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
index ae0f36702..3d577e254 100644
--- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
+++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
@@ -3,22 +3,15 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
- def parse(html, data, prefix, error_message, key_name, value_name \\ "content") do
- meta_data =
- html
- |> get_elements(key_name, prefix)
- |> Enum.reduce(data, fn el, acc ->
- attributes = normalize_attributes(el, prefix, key_name, value_name)
-
- Map.merge(acc, attributes)
- end)
- |> maybe_put_title(html)
-
- if Enum.empty?(meta_data) do
- {:error, error_message}
- else
- {:ok, meta_data}
- end
+ def parse(data, html, prefix, key_name, value_name \\ "content") do
+ html
+ |> get_elements(key_name, prefix)
+ |> Enum.reduce(data, fn el, acc ->
+ attributes = normalize_attributes(el, prefix, key_name, value_name)
+
+ Map.merge(acc, attributes)
+ end)
+ |> maybe_put_title(html)
end
defp get_elements(html, key_name, prefix) do
@@ -29,19 +22,19 @@ defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
{_tag, attributes, _children} = html_node
data =
- Enum.into(attributes, %{}, fn {name, value} ->
+ Map.new(attributes, fn {name, value} ->
{name, String.trim_leading(value, "#{prefix}:")}
end)
- %{String.to_atom(data[key_name]) => data[value_name]}
+ %{data[key_name] => data[value_name]}
end
- defp maybe_put_title(%{title: _} = meta, _), do: meta
+ defp maybe_put_title(%{"title" => _} = meta, _), do: meta
defp maybe_put_title(meta, html) when meta != %{} do
case get_page_title(html) do
"" -> meta
- title -> Map.put_new(meta, :title, title)
+ title -> Map.put_new(meta, "title", title)
end
end
diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
index 8f32bf91b..6bdeac89c 100644
--- a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
+++ b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
@@ -5,11 +5,11 @@
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
def parse(html, _data) do
with elements = [_ | _] <- get_discovery_data(html),
- {:ok, oembed_url} <- get_oembed_url(elements),
+ oembed_url when is_binary(oembed_url) <- get_oembed_url(elements),
{:ok, oembed_data} <- get_oembed_data(oembed_url) do
- {:ok, oembed_data}
+ oembed_data
else
- _e -> {:error, "No OEmbed data found"}
+ _e -> %{}
end
end
@@ -17,19 +17,13 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
html |> Floki.find("link[type='application/json+oembed']")
end
- defp get_oembed_url(nodes) do
- {"link", attributes, _children} = nodes |> hd()
-
- {:ok, Enum.into(attributes, %{})["href"]}
+ defp get_oembed_url([{"link", attributes, _children} | _]) do
+ Enum.find_value(attributes, fn {k, v} -> if k == "href", do: v end)
end
defp get_oembed_data(url) do
- {:ok, %Tesla.Env{body: json}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media])
-
- {:ok, data} = Jason.decode(json)
-
- data = data |> Map.new(fn {k, v} -> {String.to_atom(k), v} end)
-
- {:ok, data}
+ with {:ok, %Tesla.Env{body: json}} <- Pleroma.HTTP.get(url, [], adapter: [pool: :media]) do
+ Jason.decode(json)
+ end
end
end
diff --git a/lib/pleroma/web/rich_media/parsers/ogp.ex b/lib/pleroma/web/rich_media/parsers/ogp.ex
index 3e9012588..b3b3b059c 100644
--- a/lib/pleroma/web/rich_media/parsers/ogp.ex
+++ b/lib/pleroma/web/rich_media/parsers/ogp.ex
@@ -3,13 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.OGP do
- def parse(html, data) do
- Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(
- html,
- data,
- "og",
- "No OGP metadata found",
- "property"
- )
+ @deprecated "OGP parser is deprecated. Use TwitterCard instead."
+ def parse(_html, _data) do
+ %{}
end
end
diff --git a/lib/pleroma/web/rich_media/parsers/twitter_card.ex b/lib/pleroma/web/rich_media/parsers/twitter_card.ex
index 09d4b526e..4a04865d2 100644
--- a/lib/pleroma/web/rich_media/parsers/twitter_card.ex
+++ b/lib/pleroma/web/rich_media/parsers/twitter_card.ex
@@ -5,18 +5,11 @@
defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do
alias Pleroma.Web.RichMedia.Parsers.MetaTagsParser
- @spec parse(String.t(), map()) :: {:ok, map()} | {:error, String.t()}
+ @spec parse(list(), map()) :: map()
def parse(html, data) do
data
- |> parse_name_attrs(html)
- |> parse_property_attrs(html)
- end
-
- defp parse_name_attrs(data, html) do
- MetaTagsParser.parse(html, data, "twitter", %{}, "name")
- end
-
- defp parse_property_attrs({_, data}, html) do
- MetaTagsParser.parse(html, data, "twitter", "No twitter card metadata found", "property")
+ |> MetaTagsParser.parse(html, "og", "property")
+ |> MetaTagsParser.parse(html, "twitter", "name")
+ |> MetaTagsParser.parse(html, "twitter", "property")
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index aa272540d..419aa55e4 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -209,6 +209,10 @@ defmodule Pleroma.Web.Router do
post("/oauth_app", OAuthAppController, :create)
patch("/oauth_app/:id", OAuthAppController, :update)
delete("/oauth_app/:id", OAuthAppController, :delete)
+
+ get("/media_proxy_caches", MediaProxyCacheController, :index)
+ post("/media_proxy_caches/delete", MediaProxyCacheController, :delete)
+ post("/media_proxy_caches/purge", MediaProxyCacheController, :purge)
end
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
@@ -306,6 +310,15 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:authenticated_api)
+ post("/chats/by-account-id/:id", ChatController, :create)
+ get("/chats", ChatController, :index)
+ get("/chats/:id", ChatController, :show)
+ get("/chats/:id/messages", ChatController, :messages)
+ post("/chats/:id/messages", ChatController, :post_chat_message)
+ delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
+ post("/chats/:id/read", ChatController, :mark_as_read)
+ post("/chats/:id/messages/:message_id/read", ChatController, :mark_message_as_read)
+
get("/conversations/:id/statuses", ConversationController, :statuses)
get("/conversations/:id", ConversationController, :show)
post("/conversations/read", ConversationController, :mark_as_read)
@@ -454,6 +467,7 @@ defmodule Pleroma.Web.Router do
scope "/api/web", Pleroma.Web do
pipe_through(:authenticated_api)
+ # Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere
put("/settings", MastoFEController, :put_settings)
end
diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex
index 0cf41189b..d1d2c9b9c 100644
--- a/lib/pleroma/web/streamer/streamer.ex
+++ b/lib/pleroma/web/streamer/streamer.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.Streamer do
require Logger
alias Pleroma.Activity
+ alias Pleroma.Chat.MessageReference
alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Notification
@@ -22,7 +23,7 @@ defmodule Pleroma.Web.Streamer do
def registry, do: @registry
@public_streams ["public", "public:local", "public:media", "public:local:media"]
- @user_streams ["user", "user:notification", "direct"]
+ @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"]
@doc "Expands and authorizes a stream, and registers the process for streaming."
@spec get_topic_and_add_socket(stream :: String.t(), User.t() | nil, Map.t() | nil) ::
@@ -89,34 +90,20 @@ defmodule Pleroma.Web.Streamer do
if should_env_send?(), do: Registry.unregister(@registry, topic)
end
- def stream(topics, item) when is_list(topics) do
+ def stream(topics, items) do
if should_env_send?() do
- Enum.each(topics, fn t ->
- spawn(fn -> do_stream(t, item) end)
+ List.wrap(topics)
+ |> Enum.each(fn topic ->
+ List.wrap(items)
+ |> Enum.each(fn item ->
+ spawn(fn -> do_stream(topic, item) end)
+ end)
end)
end
:ok
end
- def stream(topic, items) when is_list(items) do
- if should_env_send?() do
- Enum.each(items, fn i ->
- spawn(fn -> do_stream(topic, i) end)
- end)
-
- :ok
- end
- end
-
- def stream(topic, item) do
- if should_env_send?() do
- spawn(fn -> do_stream(topic, item) end)
- end
-
- :ok
- end
-
def filtered_by_user?(%User{} = user, %Activity{} = item) do
%{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute])
@@ -200,6 +187,19 @@ defmodule Pleroma.Web.Streamer do
end)
end
+ defp do_stream(topic, {user, %MessageReference{} = cm_ref})
+ when topic in ["user", "user:pleroma_chat"] do
+ topic = "#{topic}:#{user.id}"
+
+ text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
+
+ Registry.dispatch(@registry, topic, fn list ->
+ Enum.each(list, fn {pid, _auth} ->
+ send(pid, {:text, text})
+ end)
+ end)
+ end
+
defp do_stream("user", item) do
Logger.debug("Trying to push to users")
diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex
index 237b29ded..476a33245 100644
--- a/lib/pleroma/web/views/streamer_view.ex
+++ b/lib/pleroma/web/views/streamer_view.ex
@@ -51,6 +51,29 @@ defmodule Pleroma.Web.StreamerView do
|> Jason.encode!()
end
+ def render("chat_update.json", %{chat_message_reference: cm_ref}) do
+ # Explicitly giving the cmr for the object here, so we don't accidentally
+ # send a later 'last_message' that was inserted between inserting this and
+ # streaming it out
+ #
+ # It also contains the chat with a cache of the correct unread count
+ Logger.debug("Trying to stream out #{inspect(cm_ref)}")
+
+ representation =
+ Pleroma.Web.PleromaAPI.ChatView.render(
+ "show.json",
+ %{last_message: cm_ref, chat: cm_ref.chat}
+ )
+
+ %{
+ event: "pleroma:chat_update",
+ payload:
+ representation
+ |> Jason.encode!()
+ }
+ |> Jason.encode!()
+ end
+
def render("conversation.json", %Participation{} = participation) do
%{
event: "conversation",
diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex
index 49352db2a..8deeabda0 100644
--- a/lib/pleroma/workers/attachments_cleanup_worker.ex
+++ b/lib/pleroma/workers/attachments_cleanup_worker.ex
@@ -18,13 +18,19 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
},
_job
) do
- hrefs =
- Enum.flat_map(attachments, fn attachment ->
- Enum.map(attachment["url"], & &1["href"])
- end)
+ attachments
+ |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
+ |> fetch_objects
+ |> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
+ |> filter_objects
+ |> do_clean
- names = Enum.map(attachments, & &1["name"])
+ {:ok, :success}
+ end
+
+ def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: {:ok, :skip}
+ defp do_clean({object_ids, attachment_urls}) do
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
prefix =
@@ -39,68 +45,70 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
"/"
)
- # find all objects for copies of the attachments, name and actor doesn't matter here
- object_ids_and_hrefs =
- from(o in Object,
- where:
- fragment(
- "to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href' where jsonb_typeof((?)#>'{url}') = 'array'))::jsonb \\?| (?)",
- o.data,
- o.data,
- ^hrefs
- )
- )
- # The query above can be time consumptive on large instances until we
- # refactor how uploads are stored
- |> Repo.all(timeout: :infinity)
- # we should delete 1 object for any given attachment, but don't delete
- # files if there are more than 1 object for it
- |> Enum.reduce(%{}, fn %{
- id: id,
- data: %{
- "url" => [%{"href" => href}],
- "actor" => obj_actor,
- "name" => name
- }
- },
- acc ->
- Map.update(acc, href, %{id: id, count: 1}, fn val ->
- case obj_actor == actor and name in names do
- true ->
- # set id of the actor's object that will be deleted
- %{val | id: id, count: val.count + 1}
-
- false ->
- # another actor's object, just increase count to not delete file
- %{val | count: val.count + 1}
- end
- end)
- end)
- |> Enum.map(fn {href, %{id: id, count: count}} ->
- # only delete files that have single instance
- with 1 <- count do
- href
- |> String.trim_leading("#{base_url}/#{prefix}")
- |> uploader.delete_file()
-
- {id, href}
- else
- _ -> {id, nil}
- end
- end)
+ Enum.each(attachment_urls, fn href ->
+ href
+ |> String.trim_leading("#{base_url}/#{prefix}")
+ |> uploader.delete_file()
+ end)
- object_ids = Enum.map(object_ids_and_hrefs, fn {id, _} -> id end)
+ delete_objects(object_ids)
+ end
- from(o in Object, where: o.id in ^object_ids)
- |> Repo.delete_all()
+ defp delete_objects([_ | _] = object_ids) do
+ Repo.delete_all(from(o in Object, where: o.id in ^object_ids))
+ end
- object_ids_and_hrefs
- |> Enum.filter(fn {_, href} -> not is_nil(href) end)
- |> Enum.map(&elem(&1, 1))
- |> Pleroma.Web.MediaProxy.Invalidation.purge()
+ defp delete_objects(_), do: :ok
- {:ok, :success}
+ # we should delete 1 object for any given attachment, but don't delete
+ # files if there are more than 1 object for it
+ defp filter_objects(objects) do
+ Enum.reduce(objects, {[], []}, fn {href, %{id: id, count: count}}, {ids, hrefs} ->
+ with 1 <- count do
+ {ids ++ [id], hrefs ++ [href]}
+ else
+ _ -> {ids ++ [id], hrefs}
+ end
+ end)
end
- def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: {:ok, :skip}
+ defp prepare_objects(objects, actor, names) do
+ objects
+ |> Enum.reduce(%{}, fn %{
+ id: id,
+ data: %{
+ "url" => [%{"href" => href}],
+ "actor" => obj_actor,
+ "name" => name
+ }
+ },
+ acc ->
+ Map.update(acc, href, %{id: id, count: 1}, fn val ->
+ case obj_actor == actor and name in names do
+ true ->
+ # set id of the actor's object that will be deleted
+ %{val | id: id, count: val.count + 1}
+
+ false ->
+ # another actor's object, just increase count to not delete file
+ %{val | count: val.count + 1}
+ end
+ end)
+ end)
+ end
+
+ defp fetch_objects(hrefs) do
+ from(o in Object,
+ where:
+ fragment(
+ "to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href' where jsonb_typeof((?)#>'{url}') = 'array'))::jsonb \\?| (?)",
+ o.data,
+ o.data,
+ ^hrefs
+ )
+ )
+ # The query above can be time consumptive on large instances until we
+ # refactor how uploads are stored
+ |> Repo.all(timeout: :infinity)
+ end
end
diff --git a/mix.exs b/mix.exs
index 03b060bc0..4d13e95d7 100644
--- a/mix.exs
+++ b/mix.exs
@@ -5,7 +5,7 @@ defmodule Pleroma.Mixfile do
[
app: :pleroma,
version: version("2.0.50"),
- elixir: "~> 1.8",
+ elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())],
@@ -230,32 +230,37 @@ defmodule Pleroma.Mixfile do
defp version(version) do
identifier_filter = ~r/[^0-9a-z\-]+/i
- # Pre-release version, denoted from patch version with a hyphen
- {tag, tag_err} =
- System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
-
- {describe, describe_err} = System.cmd("git", ["describe", "--tags", "--abbrev=8"])
- {commit_hash, commit_hash_err} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
+ {_cmdgit, cmdgit_err} = System.cmd("sh", ["-c", "command -v git"])
git_pre_release =
- cond do
- tag_err == 0 and describe_err == 0 ->
- describe
- |> String.trim()
- |> String.replace(String.trim(tag), "")
- |> String.trim_leading("-")
- |> String.trim()
+ if cmdgit_err == 0 do
+ {tag, tag_err} =
+ System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true)
- commit_hash_err == 0 ->
- "0-g" <> String.trim(commit_hash)
+ {describe, describe_err} = System.cmd("git", ["describe", "--tags", "--abbrev=8"])
+ {commit_hash, commit_hash_err} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
- true ->
- ""
+ # Pre-release version, denoted from patch version with a hyphen
+ cond do
+ tag_err == 0 and describe_err == 0 ->
+ describe
+ |> String.trim()
+ |> String.replace(String.trim(tag), "")
+ |> String.trim_leading("-")
+ |> String.trim()
+
+ commit_hash_err == 0 ->
+ "0-g" <> String.trim(commit_hash)
+
+ true ->
+ nil
+ end
end
# Branch name as pre-release version component, denoted with a dot
branch_name =
- with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
+ with 0 <- cmdgit_err,
+ {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
branch_name <- String.trim(branch_name),
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
true <-
@@ -269,7 +274,7 @@ defmodule Pleroma.Mixfile do
branch_name
else
- _ -> "stable"
+ _ -> ""
end
build_name =
diff --git a/priv/gettext/it/LC_MESSAGES/errors.po b/priv/gettext/it/LC_MESSAGES/errors.po
new file mode 100644
index 000000000..726be628b
--- /dev/null
+++ b/priv/gettext/it/LC_MESSAGES/errors.po
@@ -0,0 +1,580 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-06-19 14:33+0000\n"
+"PO-Revision-Date: 2020-06-19 20:38+0000\n"
+"Last-Translator: Ben Is <srsbzns@cock.li>\n"
+"Language-Team: Italian <https://translate.pleroma.social/projects/pleroma/"
+"pleroma/it/>\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.0.4\n"
+
+## This file is a PO Template file.
+##
+## `msgid`s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run `mix gettext.extract` to bring this file up to
+## date. Leave `msgstr`s empty as changing them here as no
+## effect: edit them in PO (`.po`) files instead.
+## From Ecto.Changeset.cast/4
+msgid "can't be blank"
+msgstr "non può essere nullo"
+
+## From Ecto.Changeset.unique_constraint/3
+msgid "has already been taken"
+msgstr ""
+
+## From Ecto.Changeset.put_change/3
+msgid "is invalid"
+msgstr ""
+
+## From Ecto.Changeset.validate_format/3
+msgid "has invalid format"
+msgstr ""
+
+## From Ecto.Changeset.validate_subset/3
+msgid "has an invalid entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_exclusion/3
+msgid "is reserved"
+msgstr ""
+
+## From Ecto.Changeset.validate_confirmation/3
+msgid "does not match confirmation"
+msgstr ""
+
+## From Ecto.Changeset.no_assoc_constraint/3
+msgid "is still associated with this entry"
+msgstr ""
+
+msgid "are still associated with this entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_length/3
+msgid "should be %{count} character(s)"
+msgid_plural "should be %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have %{count} item(s)"
+msgid_plural "should have %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be at least %{count} character(s)"
+msgid_plural "should be at least %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be at most %{count} character(s)"
+msgid_plural "should be at most %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+## From Ecto.Changeset.validate_number/3
+msgid "must be less than %{number}"
+msgstr ""
+
+msgid "must be greater than %{number}"
+msgstr ""
+
+msgid "must be less than or equal to %{number}"
+msgstr ""
+
+msgid "must be greater than or equal to %{number}"
+msgstr ""
+
+msgid "must be equal to %{number}"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:421
+#, elixir-format
+msgid "Account not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:249
+#, elixir-format
+msgid "Already voted"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:360
+#, elixir-format
+msgid "Bad request"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
+#, elixir-format
+msgid "Can't delete object"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
+#, elixir-format
+msgid "Can't delete this post"
+msgstr ""
+
+#: lib/pleroma/web/controller_helper.ex:95
+#: lib/pleroma/web/controller_helper.ex:101
+#, elixir-format
+msgid "Can't display this activity"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
+#, elixir-format
+msgid "Can't find user"
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114
+#, elixir-format
+msgid "Can't get favorites"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437
+#, elixir-format
+msgid "Can't like object"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:556
+#, elixir-format
+msgid "Cannot post an empty status without attachments"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:504
+#, elixir-format
+msgid "Comment must be up to %{max_size} characters"
+msgstr ""
+
+#: lib/pleroma/config/config_db.ex:222
+#, elixir-format
+msgid "Config with params %{params} not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:95
+#, elixir-format
+msgid "Could not delete"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:141
+#, elixir-format
+msgid "Could not favorite"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:370
+#, elixir-format
+msgid "Could not pin"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:112
+#, elixir-format
+msgid "Could not repeat"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:188
+#, elixir-format
+msgid "Could not unfavorite"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:380
+#, elixir-format
+msgid "Could not unpin"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:126
+#, elixir-format
+msgid "Could not unrepeat"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:428
+#: lib/pleroma/web/common_api/common_api.ex:437
+#, elixir-format
+msgid "Could not update state"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
+#, elixir-format
+msgid "Error."
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:106
+#, elixir-format
+msgid "Invalid CAPTCHA"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
+#: lib/pleroma/web/oauth/oauth_controller.ex:569
+#, elixir-format
+msgid "Invalid credentials"
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
+#, elixir-format
+msgid "Invalid credentials."
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:265
+#, elixir-format
+msgid "Invalid indices"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
+#, elixir-format
+msgid "Invalid parameters"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:411
+#, elixir-format
+msgid "Invalid password."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
+#, elixir-format
+msgid "Invalid request"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:109
+#, elixir-format
+msgid "Kocaptcha service unavailable"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
+#, elixir-format
+msgid "Missing parameters"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:540
+#, elixir-format
+msgid "No such conversation"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:439
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
+#, elixir-format
+msgid "No such permission_group"
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:74
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135
+#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
+#, elixir-format
+msgid "Not found"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:241
+#, elixir-format
+msgid "Poll's author can't vote"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
+#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:290
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
+#, elixir-format
+msgid "Record not found"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153
+#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32
+#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
+#, elixir-format
+msgid "Something went wrong"
+msgstr ""
+
+#: lib/pleroma/web/common_api/activity_draft.ex:107
+#, elixir-format
+msgid "The message visibility must be direct"
+msgstr ""
+
+#: lib/pleroma/web/common_api/utils.ex:566
+#, elixir-format
+msgid "The status is over the character limit"
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
+#, elixir-format
+msgid "This resource requires authentication."
+msgstr ""
+
+#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
+#, elixir-format
+msgid "Throttled"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:266
+#, elixir-format
+msgid "Too many choices"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
+#, elixir-format
+msgid "Unhandled activity type"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:536
+#, elixir-format
+msgid "You can't revoke your own admin status."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:218
+#: lib/pleroma/web/oauth/oauth_controller.ex:309
+#, elixir-format
+msgid "Your account is currently disabled"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:180
+#: lib/pleroma/web/oauth/oauth_controller.ex:332
+#, elixir-format
+msgid "Your login is missing a confirmed e-mail address"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
+#, elixir-format
+msgid "can't read inbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
+#, elixir-format
+msgid "can't update outbox of %{nickname} as %{as_nickname}"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:388
+#, elixir-format
+msgid "conversation is already muted"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
+#, elixir-format
+msgid "error"
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
+#, elixir-format
+msgid "mascots can only be images"
+msgstr ""
+
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
+#, elixir-format
+msgid "not found"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:395
+#, elixir-format
+msgid "Bad OAuth request."
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:115
+#, elixir-format
+msgid "CAPTCHA already used"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:112
+#, elixir-format
+msgid "CAPTCHA expired"
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:55
+#, elixir-format
+msgid "Failed"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:411
+#, elixir-format
+msgid "Failed to authenticate: %{message}."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:442
+#, elixir-format
+msgid "Failed to set up user account."
+msgstr ""
+
+#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
+#, elixir-format
+msgid "Insufficient permissions: %{permissions}."
+msgstr ""
+
+#: lib/pleroma/plugs/uploaded_media.ex:94
+#, elixir-format
+msgid "Internal Error"
+msgstr ""
+
+#: lib/pleroma/web/oauth/fallback_controller.ex:22
+#: lib/pleroma/web/oauth/fallback_controller.ex:29
+#, elixir-format
+msgid "Invalid Username/Password"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:118
+#, elixir-format
+msgid "Invalid answer data"
+msgstr ""
+
+#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
+#, elixir-format
+msgid "Nodeinfo schema version not handled"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:169
+#, elixir-format
+msgid "This action is outside the authorized scopes"
+msgstr ""
+
+#: lib/pleroma/web/oauth/fallback_controller.ex:14
+#, elixir-format
+msgid "Unknown error, please check the details and try again."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:116
+#: lib/pleroma/web/oauth/oauth_controller.ex:155
+#, elixir-format
+msgid "Unlisted redirect_uri."
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:391
+#, elixir-format
+msgid "Unsupported OAuth provider: %{provider}."
+msgstr ""
+
+#: lib/pleroma/uploaders/uploader.ex:72
+#, elixir-format
+msgid "Uploader callback timeout"
+msgstr ""
+
+#: lib/pleroma/web/uploader_controller.ex:23
+#, elixir-format
+msgid "bad request"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:103
+#, elixir-format
+msgid "CAPTCHA Error"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:200
+#, elixir-format
+msgid "Could not add reaction emoji"
+msgstr ""
+
+#: lib/pleroma/web/common_api/common_api.ex:211
+#, elixir-format
+msgid "Could not remove reaction emoji"
+msgstr ""
+
+#: lib/pleroma/web/twitter_api/twitter_api.ex:129
+#, elixir-format
+msgid "Invalid CAPTCHA (Missing parameter: %{name})"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
+#, elixir-format
+msgid "List not found"
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
+#, elixir-format
+msgid "Missing parameter: %{name}"
+msgstr ""
+
+#: lib/pleroma/web/oauth/oauth_controller.ex:207
+#: lib/pleroma/web/oauth/oauth_controller.ex:322
+#, elixir-format
+msgid "Password reset is required"
+msgstr ""
+
+#: lib/pleroma/tests/auth_test_controller.ex:9
+#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
+#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/fallback_redirect_controller.ex:6
+#: lib/pleroma/web/feed/tag_controller.ex:6 lib/pleroma/web/feed/user_controller.ex:6
+#: lib/pleroma/web/mailer/subscription_controller.ex:2 lib/pleroma/web/masto_fe_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 lib/pleroma/web/media_proxy/media_proxy_controller.ex:6
+#: lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6
+#: lib/pleroma/web/oauth/fallback_controller.ex:6 lib/pleroma/web/oauth/mfa_controller.ex:10
+#: lib/pleroma/web/oauth/oauth_controller.ex:6 lib/pleroma/web/ostatus/ostatus_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:2
+#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6
+#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6
+#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6
+#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6
+#, elixir-format
+msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+msgstr ""
+
+#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
+#, elixir-format
+msgid "Two-factor authentication enabled, you must use a access token."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
+#, elixir-format
+msgid "Unexpected error occurred while adding file to pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
+#, elixir-format
+msgid "Unexpected error occurred while creating pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
+#, elixir-format
+msgid "Unexpected error occurred while removing file from pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
+#, elixir-format
+msgid "Unexpected error occurred while updating file in pack."
+msgstr ""
+
+#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
+#, elixir-format
+msgid "Unexpected error occurred while updating pack metadata."
+msgstr ""
+
+#: lib/pleroma/plugs/user_is_admin_plug.ex:40
+#, elixir-format
+msgid "User is not an admin or OAuth admin scope is not granted."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
+#, elixir-format
+msgid "Web push subscription is disabled on this Pleroma instance"
+msgstr ""
+
+#: lib/pleroma/web/admin_api/admin_api_controller.ex:502
+#, elixir-format
+msgid "You can't revoke your own admin/moderator status."
+msgstr ""
+
+#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105
+#, elixir-format
+msgid "authorization required for timeline view"
+msgstr ""
diff --git a/priv/repo/migrations/20200309123730_create_chats.exs b/priv/repo/migrations/20200309123730_create_chats.exs
new file mode 100644
index 000000000..715d798ea
--- /dev/null
+++ b/priv/repo/migrations/20200309123730_create_chats.exs
@@ -0,0 +1,16 @@
+defmodule Pleroma.Repo.Migrations.CreateChats do
+ use Ecto.Migration
+
+ def change do
+ create table(:chats) do
+ add(:user_id, references(:users, type: :uuid))
+ # Recipient is an ActivityPub id, to future-proof for group support.
+ add(:recipient, :string)
+ add(:unread, :integer, default: 0)
+ timestamps()
+ end
+
+ # There's only one chat between a user and a recipient.
+ create(index(:chats, [:user_id, :recipient], unique: true))
+ end
+end
diff --git a/priv/repo/migrations/20200322174133_user_raw_bio.exs b/priv/repo/migrations/20200322174133_user_raw_bio.exs
new file mode 100644
index 000000000..ddf9be4f5
--- /dev/null
+++ b/priv/repo/migrations/20200322174133_user_raw_bio.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.UserRawBio do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add_if_not_exists(:raw_bio, :text)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20200323122421_mrf_config_move_from_instance_namespace.exs b/priv/repo/migrations/20200323122421_mrf_config_move_from_instance_namespace.exs
new file mode 100644
index 000000000..6f6094613
--- /dev/null
+++ b/priv/repo/migrations/20200323122421_mrf_config_move_from_instance_namespace.exs
@@ -0,0 +1,39 @@
+defmodule Pleroma.Repo.Migrations.MrfConfigMoveFromInstanceNamespace do
+ use Ecto.Migration
+
+ alias Pleroma.ConfigDB
+
+ @old_keys [:rewrite_policy, :mrf_transparency, :mrf_transparency_exclusions]
+ def change do
+ config = ConfigDB.get_by_params(%{group: ":pleroma", key: ":instance"})
+
+ if config do
+ old_instance = ConfigDB.from_binary(config.value)
+
+ mrf =
+ old_instance
+ |> Keyword.take(@old_keys)
+ |> Keyword.new(fn
+ {:rewrite_policy, policies} -> {:policies, policies}
+ {:mrf_transparency, transparency} -> {:transparency, transparency}
+ {:mrf_transparency_exclusions, exclusions} -> {:transparency_exclusions, exclusions}
+ end)
+
+ if mrf != [] do
+ {:ok, _} =
+ ConfigDB.create(
+ %{group: ":pleroma", key: ":mrf", value: ConfigDB.to_binary(mrf)},
+ false
+ )
+
+ new_instance = Keyword.drop(old_instance, @old_keys)
+
+ if new_instance != [] do
+ {:ok, _} = ConfigDB.update(config, %{value: ConfigDB.to_binary(new_instance)}, false)
+ else
+ {:ok, _} = ConfigDB.delete(config)
+ end
+ end
+ end
+ end
+end
diff --git a/priv/repo/migrations/20200328193433_populate_user_raw_bio.exs b/priv/repo/migrations/20200328193433_populate_user_raw_bio.exs
new file mode 100644
index 000000000..cb35db3f5
--- /dev/null
+++ b/priv/repo/migrations/20200328193433_populate_user_raw_bio.exs
@@ -0,0 +1,25 @@
+defmodule Pleroma.Repo.Migrations.PopulateUserRawBio do
+ use Ecto.Migration
+ import Ecto.Query
+ alias Pleroma.User
+ alias Pleroma.Repo
+
+ def change do
+ {:ok, _} = Application.ensure_all_started(:fast_sanitize)
+
+ User.Query.build(%{local: true})
+ |> select([u], struct(u, [:id, :ap_id, :bio]))
+ |> Repo.stream()
+ |> Enum.each(fn %{bio: bio} = user ->
+ if bio do
+ raw_bio =
+ bio
+ |> String.replace(~r(<br */?>), "\n")
+ |> Pleroma.HTML.strip_tags()
+
+ Ecto.Changeset.cast(user, %{raw_bio: raw_bio}, [:raw_bio])
+ |> Repo.update()
+ end
+ end)
+ end
+end
diff --git a/priv/repo/migrations/20200527163635_delete_notifications_from_invisible_users.exs b/priv/repo/migrations/20200527163635_delete_notifications_from_invisible_users.exs
new file mode 100644
index 000000000..9e95a8111
--- /dev/null
+++ b/priv/repo/migrations/20200527163635_delete_notifications_from_invisible_users.exs
@@ -0,0 +1,18 @@
+defmodule Pleroma.Repo.Migrations.DeleteNotificationsFromInvisibleUsers do
+ use Ecto.Migration
+
+ import Ecto.Query
+ alias Pleroma.Repo
+
+ def up do
+ Pleroma.Notification
+ |> join(:inner, [n], activity in assoc(n, :activity))
+ |> where(
+ [n, a],
+ fragment("? in (SELECT ap_id FROM users WHERE invisible = true)", a.actor)
+ )
+ |> Repo.delete_all()
+ end
+
+ def down, do: :ok
+end
diff --git a/priv/repo/migrations/20200602094828_add_type_to_notifications.exs b/priv/repo/migrations/20200602094828_add_type_to_notifications.exs
new file mode 100644
index 000000000..19c733628
--- /dev/null
+++ b/priv/repo/migrations/20200602094828_add_type_to_notifications.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddTypeToNotifications do
+ use Ecto.Migration
+
+ def change do
+ alter table(:notifications) do
+ add(:type, :string)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20200602125218_backfill_notification_types.exs b/priv/repo/migrations/20200602125218_backfill_notification_types.exs
new file mode 100644
index 000000000..996d721ee
--- /dev/null
+++ b/priv/repo/migrations/20200602125218_backfill_notification_types.exs
@@ -0,0 +1,10 @@
+defmodule Pleroma.Repo.Migrations.BackfillNotificationTypes do
+ use Ecto.Migration
+
+ def up do
+ Pleroma.MigrationHelper.NotificationBackfill.fill_in_notification_types()
+ end
+
+ def down do
+ end
+end
diff --git a/priv/repo/migrations/20200602150528_create_chat_message_reference.exs b/priv/repo/migrations/20200602150528_create_chat_message_reference.exs
new file mode 100644
index 000000000..6f9148b7c
--- /dev/null
+++ b/priv/repo/migrations/20200602150528_create_chat_message_reference.exs
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Repo.Migrations.CreateChatMessageReference do
+ use Ecto.Migration
+
+ def change do
+ create table(:chat_message_references, primary_key: false) do
+ add(:id, :uuid, primary_key: true)
+ add(:chat_id, references(:chats, on_delete: :delete_all), null: false)
+ add(:object_id, references(:objects, on_delete: :delete_all), null: false)
+ add(:seen, :boolean, default: false, null: false)
+
+ timestamps()
+ end
+
+ create(index(:chat_message_references, [:chat_id, "id desc"]))
+ end
+end
diff --git a/priv/repo/migrations/20200603105113_add_unique_index_to_chat_message_references.exs b/priv/repo/migrations/20200603105113_add_unique_index_to_chat_message_references.exs
new file mode 100644
index 000000000..fdf85132e
--- /dev/null
+++ b/priv/repo/migrations/20200603105113_add_unique_index_to_chat_message_references.exs
@@ -0,0 +1,7 @@
+defmodule Pleroma.Repo.Migrations.AddUniqueIndexToChatMessageReferences do
+ use Ecto.Migration
+
+ def change do
+ create(unique_index(:chat_message_references, [:object_id, :chat_id]))
+ end
+end
diff --git a/priv/repo/migrations/20200603120448_remove_unread_from_chats.exs b/priv/repo/migrations/20200603120448_remove_unread_from_chats.exs
new file mode 100644
index 000000000..6322137d5
--- /dev/null
+++ b/priv/repo/migrations/20200603120448_remove_unread_from_chats.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.RemoveUnreadFromChats do
+ use Ecto.Migration
+
+ def change do
+ alter table(:chats) do
+ remove(:unread, :integer, default: 0)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20200603122732_add_seen_index_to_chat_message_references.exs b/priv/repo/migrations/20200603122732_add_seen_index_to_chat_message_references.exs
new file mode 100644
index 000000000..a5065d612
--- /dev/null
+++ b/priv/repo/migrations/20200603122732_add_seen_index_to_chat_message_references.exs
@@ -0,0 +1,12 @@
+defmodule Pleroma.Repo.Migrations.AddSeenIndexToChatMessageReferences do
+ use Ecto.Migration
+
+ def change do
+ create(
+ index(:chat_message_references, [:chat_id],
+ where: "seen = false",
+ name: "unseen_messages_count_index"
+ )
+ )
+ end
+end
diff --git a/priv/repo/migrations/20200604150318_migrate_seen_to_unread_in_chat_message_references.exs b/priv/repo/migrations/20200604150318_migrate_seen_to_unread_in_chat_message_references.exs
new file mode 100644
index 000000000..fd6bc7bc7
--- /dev/null
+++ b/priv/repo/migrations/20200604150318_migrate_seen_to_unread_in_chat_message_references.exs
@@ -0,0 +1,30 @@
+defmodule Pleroma.Repo.Migrations.MigrateSeenToUnreadInChatMessageReferences do
+ use Ecto.Migration
+
+ def change do
+ drop(
+ index(:chat_message_references, [:chat_id],
+ where: "seen = false",
+ name: "unseen_messages_count_index"
+ )
+ )
+
+ alter table(:chat_message_references) do
+ add(:unread, :boolean, default: true)
+ end
+
+ execute("update chat_message_references set unread = not seen")
+
+ alter table(:chat_message_references) do
+ modify(:unread, :boolean, default: true, null: false)
+ remove(:seen, :boolean, default: false, null: false)
+ end
+
+ create(
+ index(:chat_message_references, [:chat_id],
+ where: "unread = true",
+ name: "unread_messages_count_index"
+ )
+ )
+ end
+end
diff --git a/priv/repo/migrations/20200606105430_change_type_to_enum_for_notifications.exs b/priv/repo/migrations/20200606105430_change_type_to_enum_for_notifications.exs
new file mode 100644
index 000000000..9ea34436b
--- /dev/null
+++ b/priv/repo/migrations/20200606105430_change_type_to_enum_for_notifications.exs
@@ -0,0 +1,36 @@
+defmodule Pleroma.Repo.Migrations.ChangeTypeToEnumForNotifications do
+ use Ecto.Migration
+
+ def up do
+ """
+ create type notification_type as enum (
+ 'follow',
+ 'follow_request',
+ 'mention',
+ 'move',
+ 'pleroma:emoji_reaction',
+ 'pleroma:chat_mention',
+ 'reblog',
+ 'favourite'
+ )
+ """
+ |> execute()
+
+ """
+ alter table notifications
+ alter column type type notification_type using (type::notification_type)
+ """
+ |> execute()
+ end
+
+ def down do
+ alter table(:notifications) do
+ modify(:type, :string)
+ end
+
+ """
+ drop type notification_type
+ """
+ |> execute()
+ end
+end
diff --git a/priv/repo/migrations/20200607112923_change_chat_id_to_flake.exs b/priv/repo/migrations/20200607112923_change_chat_id_to_flake.exs
new file mode 100644
index 000000000..f14e269ca
--- /dev/null
+++ b/priv/repo/migrations/20200607112923_change_chat_id_to_flake.exs
@@ -0,0 +1,23 @@
+defmodule Pleroma.Repo.Migrations.ChangeChatIdToFlake do
+ use Ecto.Migration
+
+ def up do
+ execute("""
+ alter table chats
+ drop constraint chats_pkey cascade,
+ alter column id drop default,
+ alter column id set data type uuid using cast( lpad( to_hex(id), 32, '0') as uuid),
+ add primary key (id)
+ """)
+
+ execute("""
+ alter table chat_message_references
+ alter column chat_id set data type uuid using cast( lpad( to_hex(chat_id), 32, '0') as uuid),
+ add constraint chat_message_references_chat_id_fkey foreign key (chat_id) references chats(id) on delete cascade
+ """)
+ end
+
+ def down do
+ :ok
+ end
+end
diff --git a/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs b/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs
index 6227769dc..757afa129 100644
--- a/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs
+++ b/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs
@@ -10,8 +10,8 @@ defmodule Pleroma.Repo.Migrations.AddFtsIndexToObjectsTwo do
execute("CREATE FUNCTION objects_fts_update() RETURNS trigger AS $$
begin
- new.fts_content := to_tsvector('english', new.data->>'content');
- return new;
+ new.fts_content := to_tsvector('english', new.data->>'content');
+ return new;
end
$$ LANGUAGE plpgsql")
execute("create index if not exists objects_fts on objects using RUM (fts_content rum_tsvector_addon_ops, inserted_at) with (attach = 'inserted_at', to = 'fts_content');")
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index 278ad2f96..7cc3fee40 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -30,6 +30,7 @@
"@type": "@id"
},
"EmojiReact": "litepub:EmojiReact",
+ "ChatMessage": "litepub:ChatMessage",
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
diff --git a/test/application_requirements_test.exs b/test/application_requirements_test.exs
new file mode 100644
index 000000000..481cdfd73
--- /dev/null
+++ b/test/application_requirements_test.exs
@@ -0,0 +1,96 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ApplicationRequirementsTest do
+ use Pleroma.DataCase
+ import ExUnit.CaptureLog
+ import Mock
+
+ alias Pleroma.Repo
+
+ describe "check_rum!" do
+ setup_with_mocks([
+ {Pleroma.ApplicationRequirements, [:passthrough],
+ [check_migrations_applied!: fn _ -> :ok end]}
+ ]) do
+ :ok
+ end
+
+ setup do: clear_config([:database, :rum_enabled])
+
+ test "raises if rum is enabled and detects unapplied rum migrations" do
+ Pleroma.Config.put([:database, :rum_enabled], true)
+
+ with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
+ assert_raise Pleroma.ApplicationRequirements.VerifyError,
+ "Unapplied RUM Migrations detected",
+ fn ->
+ capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ end
+ end
+ end
+
+ test "raises if rum is disabled and detects rum migrations" do
+ Pleroma.Config.put([:database, :rum_enabled], false)
+
+ with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
+ assert_raise Pleroma.ApplicationRequirements.VerifyError,
+ "RUM Migrations detected",
+ fn ->
+ capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ end
+ end
+ end
+
+ test "doesn't do anything if rum enabled and applied migrations" do
+ Pleroma.Config.put([:database, :rum_enabled], true)
+
+ with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
+ assert Pleroma.ApplicationRequirements.verify!() == :ok
+ end
+ end
+
+ test "doesn't do anything if rum disabled" do
+ Pleroma.Config.put([:database, :rum_enabled], false)
+
+ with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
+ assert Pleroma.ApplicationRequirements.verify!() == :ok
+ end
+ end
+ end
+
+ describe "check_migrations_applied!" do
+ setup_with_mocks([
+ {Ecto.Migrator, [],
+ [
+ with_repo: fn repo, fun -> passthrough([repo, fun]) end,
+ migrations: fn Repo ->
+ [
+ {:up, 20_191_128_153_944, "fix_missing_following_count"},
+ {:up, 20_191_203_043_610, "create_report_notes"},
+ {:down, 20_191_220_174_645, "add_scopes_to_pleroma_feo_auth_records"}
+ ]
+ end
+ ]}
+ ]) do
+ :ok
+ end
+
+ setup do: clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
+
+ test "raises if it detects unapplied migrations" do
+ assert_raise Pleroma.ApplicationRequirements.VerifyError,
+ "Unapplied Migrations detected",
+ fn ->
+ capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ end
+ end
+
+ test "doesn't do anything if disabled" do
+ Pleroma.Config.put([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true)
+
+ assert :ok == Pleroma.ApplicationRequirements.verify!()
+ end
+ end
+end
diff --git a/test/chat/message_reference_test.exs b/test/chat/message_reference_test.exs
new file mode 100644
index 000000000..aaa7c1ad4
--- /dev/null
+++ b/test/chat/message_reference_test.exs
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Chat.MessageReferenceTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "messages" do
+ test "it returns the last message in a chat" do
+ user = insert(:user)
+ recipient = insert(:user)
+
+ {:ok, _message_1} = CommonAPI.post_chat_message(user, recipient, "hey")
+ {:ok, _message_2} = CommonAPI.post_chat_message(recipient, user, "ho")
+
+ {:ok, chat} = Chat.get_or_create(user.id, recipient.ap_id)
+
+ message = MessageReference.last_message_for_chat(chat)
+
+ assert message.object.data["content"] == "ho"
+ end
+ end
+end
diff --git a/test/chat_test.exs b/test/chat_test.exs
new file mode 100644
index 000000000..332f2180a
--- /dev/null
+++ b/test/chat_test.exs
@@ -0,0 +1,61 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ChatTest do
+ use Pleroma.DataCase, async: true
+
+ alias Pleroma.Chat
+
+ import Pleroma.Factory
+
+ describe "creation and getting" do
+ test "it only works if the recipient is a valid user (for now)" do
+ user = insert(:user)
+
+ assert {:error, _chat} = Chat.bump_or_create(user.id, "http://some/nonexisting/account")
+ assert {:error, _chat} = Chat.get_or_create(user.id, "http://some/nonexisting/account")
+ end
+
+ test "it creates a chat for a user and recipient" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
+
+ assert chat.id
+ end
+
+ test "it returns and bumps a chat for a user and recipient if it already exists" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
+ {:ok, chat_two} = Chat.bump_or_create(user.id, other_user.ap_id)
+
+ assert chat.id == chat_two.id
+ end
+
+ test "it returns a chat for a user and recipient if it already exists" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
+ {:ok, chat_two} = Chat.get_or_create(user.id, other_user.ap_id)
+
+ assert chat.id == chat_two.id
+ end
+
+ test "a returning chat will have an updated `update_at` field" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
+ :timer.sleep(1500)
+ {:ok, chat_two} = Chat.bump_or_create(user.id, other_user.ap_id)
+
+ assert chat.id == chat_two.id
+ assert chat.updated_at != chat_two.updated_at
+ end
+ end
+end
diff --git a/test/config/config_db_test.exs b/test/config/config_db_test.exs
index 336de7359..3895e2cda 100644
--- a/test/config/config_db_test.exs
+++ b/test/config/config_db_test.exs
@@ -7,40 +7,28 @@ defmodule Pleroma.ConfigDBTest do
import Pleroma.Factory
alias Pleroma.ConfigDB
- test "get_by_key/1" do
+ test "get_by_params/1" do
config = insert(:config)
insert(:config)
assert config == ConfigDB.get_by_params(%{group: config.group, key: config.key})
end
- test "create/1" do
- {:ok, config} = ConfigDB.create(%{group: ":pleroma", key: ":some_key", value: "some_value"})
- assert config == ConfigDB.get_by_params(%{group: ":pleroma", key: ":some_key"})
- end
-
- test "update/1" do
- config = insert(:config)
- {:ok, updated} = ConfigDB.update(config, %{value: "some_value"})
- loaded = ConfigDB.get_by_params(%{group: config.group, key: config.key})
- assert loaded == updated
- end
-
test "get_all_as_keyword/0" do
saved = insert(:config)
- insert(:config, group: ":quack", key: ":level", value: ConfigDB.to_binary(:info))
- insert(:config, group: ":quack", key: ":meta", value: ConfigDB.to_binary([:none]))
+ insert(:config, group: ":quack", key: ":level", value: :info)
+ insert(:config, group: ":quack", key: ":meta", value: [:none])
insert(:config,
group: ":quack",
key: ":webhook_url",
- value: ConfigDB.to_binary("https://hooks.slack.com/services/KEY/some_val")
+ value: "https://hooks.slack.com/services/KEY/some_val"
)
config = ConfigDB.get_all_as_keyword()
assert config[:pleroma] == [
- {ConfigDB.from_string(saved.key), ConfigDB.from_binary(saved.value)}
+ {saved.key, saved.value}
]
assert config[:quack][:level] == :info
@@ -51,11 +39,11 @@ defmodule Pleroma.ConfigDBTest do
describe "update_or_create/1" do
test "common" do
config = insert(:config)
- key2 = "another_key"
+ key2 = :another_key
params = [
- %{group: "pleroma", key: key2, value: "another_value"},
- %{group: config.group, key: config.key, value: "new_value"}
+ %{group: :pleroma, key: key2, value: "another_value"},
+ %{group: :pleroma, key: config.key, value: [a: 1, b: 2, c: "new_value"]}
]
assert Repo.all(ConfigDB) |> length() == 1
@@ -65,16 +53,16 @@ defmodule Pleroma.ConfigDBTest do
assert Repo.all(ConfigDB) |> length() == 2
config1 = ConfigDB.get_by_params(%{group: config.group, key: config.key})
- config2 = ConfigDB.get_by_params(%{group: "pleroma", key: key2})
+ config2 = ConfigDB.get_by_params(%{group: :pleroma, key: key2})
- assert config1.value == ConfigDB.transform("new_value")
- assert config2.value == ConfigDB.transform("another_value")
+ assert config1.value == [a: 1, b: 2, c: "new_value"]
+ assert config2.value == "another_value"
end
test "partial update" do
- config = insert(:config, value: ConfigDB.to_binary(key1: "val1", key2: :val2))
+ config = insert(:config, value: [key1: "val1", key2: :val2])
- {:ok, _config} =
+ {:ok, config} =
ConfigDB.update_or_create(%{
group: config.group,
key: config.key,
@@ -83,15 +71,14 @@ defmodule Pleroma.ConfigDBTest do
updated = ConfigDB.get_by_params(%{group: config.group, key: config.key})
- value = ConfigDB.from_binary(updated.value)
- assert length(value) == 3
- assert value[:key1] == :val1
- assert value[:key2] == :val2
- assert value[:key3] == :val3
+ assert config.value == updated.value
+ assert updated.value[:key1] == :val1
+ assert updated.value[:key2] == :val2
+ assert updated.value[:key3] == :val3
end
test "deep merge" do
- config = insert(:config, value: ConfigDB.to_binary(key1: "val1", key2: [k1: :v1, k2: "v2"]))
+ config = insert(:config, value: [key1: "val1", key2: [k1: :v1, k2: "v2"]])
{:ok, config} =
ConfigDB.update_or_create(%{
@@ -103,18 +90,15 @@ defmodule Pleroma.ConfigDBTest do
updated = ConfigDB.get_by_params(%{group: config.group, key: config.key})
assert config.value == updated.value
-
- value = ConfigDB.from_binary(updated.value)
- assert value[:key1] == :val1
- assert value[:key2] == [k1: :v1, k2: :v2, k3: :v3]
- assert value[:key3] == :val3
+ assert updated.value[:key1] == :val1
+ assert updated.value[:key2] == [k1: :v1, k2: :v2, k3: :v3]
+ assert updated.value[:key3] == :val3
end
test "only full update for some keys" do
- config1 = insert(:config, key: ":ecto_repos", value: ConfigDB.to_binary(repo: Pleroma.Repo))
+ config1 = insert(:config, key: :ecto_repos, value: [repo: Pleroma.Repo])
- config2 =
- insert(:config, group: ":cors_plug", key: ":max_age", value: ConfigDB.to_binary(18))
+ config2 = insert(:config, group: :cors_plug, key: :max_age, value: 18)
{:ok, _config} =
ConfigDB.update_or_create(%{
@@ -133,8 +117,8 @@ defmodule Pleroma.ConfigDBTest do
updated1 = ConfigDB.get_by_params(%{group: config1.group, key: config1.key})
updated2 = ConfigDB.get_by_params(%{group: config2.group, key: config2.key})
- assert ConfigDB.from_binary(updated1.value) == [another_repo: [Pleroma.Repo]]
- assert ConfigDB.from_binary(updated2.value) == 777
+ assert updated1.value == [another_repo: [Pleroma.Repo]]
+ assert updated2.value == 777
end
test "full update if value is not keyword" do
@@ -142,7 +126,7 @@ defmodule Pleroma.ConfigDBTest do
insert(:config,
group: ":tesla",
key: ":adapter",
- value: ConfigDB.to_binary(Tesla.Adapter.Hackney)
+ value: Tesla.Adapter.Hackney
)
{:ok, _config} =
@@ -154,20 +138,20 @@ defmodule Pleroma.ConfigDBTest do
updated = ConfigDB.get_by_params(%{group: config.group, key: config.key})
- assert ConfigDB.from_binary(updated.value) == Tesla.Adapter.Httpc
+ assert updated.value == Tesla.Adapter.Httpc
end
test "only full update for some subkeys" do
config1 =
insert(:config,
key: ":emoji",
- value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
+ value: [groups: [a: 1, b: 2], key: [a: 1]]
)
config2 =
insert(:config,
key: ":assets",
- value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
+ value: [mascots: [a: 1, b: 2], key: [a: 1]]
)
{:ok, _config} =
@@ -187,8 +171,8 @@ defmodule Pleroma.ConfigDBTest do
updated1 = ConfigDB.get_by_params(%{group: config1.group, key: config1.key})
updated2 = ConfigDB.get_by_params(%{group: config2.group, key: config2.key})
- assert ConfigDB.from_binary(updated1.value) == [groups: [c: 3, d: 4], key: [a: 1, b: 2]]
- assert ConfigDB.from_binary(updated2.value) == [mascots: [c: 3, d: 4], key: [a: 1, b: 2]]
+ assert updated1.value == [groups: [c: 3, d: 4], key: [a: 1, b: 2]]
+ assert updated2.value == [mascots: [c: 3, d: 4], key: [a: 1, b: 2]]
end
end
@@ -206,14 +190,14 @@ defmodule Pleroma.ConfigDBTest do
end
test "partial subkeys delete" do
- config = insert(:config, value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1]))
+ config = insert(:config, value: [groups: [a: 1, b: 2], key: [a: 1]])
{:ok, deleted} =
ConfigDB.delete(%{group: config.group, key: config.key, subkeys: [":groups"]})
assert Ecto.get_meta(deleted, :state) == :loaded
- assert deleted.value == ConfigDB.to_binary(key: [a: 1])
+ assert deleted.value == [key: [a: 1]]
updated = ConfigDB.get_by_params(%{group: config.group, key: config.key})
@@ -221,7 +205,7 @@ defmodule Pleroma.ConfigDBTest do
end
test "full delete if remaining value after subkeys deletion is empty list" do
- config = insert(:config, value: ConfigDB.to_binary(groups: [a: 1, b: 2]))
+ config = insert(:config, value: [groups: [a: 1, b: 2]])
{:ok, deleted} =
ConfigDB.delete(%{group: config.group, key: config.key, subkeys: [":groups"]})
@@ -232,234 +216,159 @@ defmodule Pleroma.ConfigDBTest do
end
end
- describe "transform/1" do
+ describe "to_elixir_types/1" do
test "string" do
- binary = ConfigDB.transform("value as string")
- assert binary == :erlang.term_to_binary("value as string")
- assert ConfigDB.from_binary(binary) == "value as string"
+ assert ConfigDB.to_elixir_types("value as string") == "value as string"
end
test "boolean" do
- binary = ConfigDB.transform(false)
- assert binary == :erlang.term_to_binary(false)
- assert ConfigDB.from_binary(binary) == false
+ assert ConfigDB.to_elixir_types(false) == false
end
test "nil" do
- binary = ConfigDB.transform(nil)
- assert binary == :erlang.term_to_binary(nil)
- assert ConfigDB.from_binary(binary) == nil
+ assert ConfigDB.to_elixir_types(nil) == nil
end
test "integer" do
- binary = ConfigDB.transform(150)
- assert binary == :erlang.term_to_binary(150)
- assert ConfigDB.from_binary(binary) == 150
+ assert ConfigDB.to_elixir_types(150) == 150
end
test "atom" do
- binary = ConfigDB.transform(":atom")
- assert binary == :erlang.term_to_binary(:atom)
- assert ConfigDB.from_binary(binary) == :atom
+ assert ConfigDB.to_elixir_types(":atom") == :atom
end
test "ssl options" do
- binary = ConfigDB.transform([":tlsv1", ":tlsv1.1", ":tlsv1.2"])
- assert binary == :erlang.term_to_binary([:tlsv1, :"tlsv1.1", :"tlsv1.2"])
- assert ConfigDB.from_binary(binary) == [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
+ assert ConfigDB.to_elixir_types([":tlsv1", ":tlsv1.1", ":tlsv1.2"]) == [
+ :tlsv1,
+ :"tlsv1.1",
+ :"tlsv1.2"
+ ]
end
test "pleroma module" do
- binary = ConfigDB.transform("Pleroma.Bookmark")
- assert binary == :erlang.term_to_binary(Pleroma.Bookmark)
- assert ConfigDB.from_binary(binary) == Pleroma.Bookmark
+ assert ConfigDB.to_elixir_types("Pleroma.Bookmark") == Pleroma.Bookmark
end
test "pleroma string" do
- binary = ConfigDB.transform("Pleroma")
- assert binary == :erlang.term_to_binary("Pleroma")
- assert ConfigDB.from_binary(binary) == "Pleroma"
+ assert ConfigDB.to_elixir_types("Pleroma") == "Pleroma"
end
test "phoenix module" do
- binary = ConfigDB.transform("Phoenix.Socket.V1.JSONSerializer")
- assert binary == :erlang.term_to_binary(Phoenix.Socket.V1.JSONSerializer)
- assert ConfigDB.from_binary(binary) == Phoenix.Socket.V1.JSONSerializer
+ assert ConfigDB.to_elixir_types("Phoenix.Socket.V1.JSONSerializer") ==
+ Phoenix.Socket.V1.JSONSerializer
end
test "tesla module" do
- binary = ConfigDB.transform("Tesla.Adapter.Hackney")
- assert binary == :erlang.term_to_binary(Tesla.Adapter.Hackney)
- assert ConfigDB.from_binary(binary) == Tesla.Adapter.Hackney
+ assert ConfigDB.to_elixir_types("Tesla.Adapter.Hackney") == Tesla.Adapter.Hackney
end
test "ExSyslogger module" do
- binary = ConfigDB.transform("ExSyslogger")
- assert binary == :erlang.term_to_binary(ExSyslogger)
- assert ConfigDB.from_binary(binary) == ExSyslogger
+ assert ConfigDB.to_elixir_types("ExSyslogger") == ExSyslogger
end
test "Quack.Logger module" do
- binary = ConfigDB.transform("Quack.Logger")
- assert binary == :erlang.term_to_binary(Quack.Logger)
- assert ConfigDB.from_binary(binary) == Quack.Logger
+ assert ConfigDB.to_elixir_types("Quack.Logger") == Quack.Logger
end
test "Swoosh.Adapters modules" do
- binary = ConfigDB.transform("Swoosh.Adapters.SMTP")
- assert binary == :erlang.term_to_binary(Swoosh.Adapters.SMTP)
- assert ConfigDB.from_binary(binary) == Swoosh.Adapters.SMTP
- binary = ConfigDB.transform("Swoosh.Adapters.AmazonSES")
- assert binary == :erlang.term_to_binary(Swoosh.Adapters.AmazonSES)
- assert ConfigDB.from_binary(binary) == Swoosh.Adapters.AmazonSES
+ assert ConfigDB.to_elixir_types("Swoosh.Adapters.SMTP") == Swoosh.Adapters.SMTP
+ assert ConfigDB.to_elixir_types("Swoosh.Adapters.AmazonSES") == Swoosh.Adapters.AmazonSES
end
test "sigil" do
- binary = ConfigDB.transform("~r[comp[lL][aA][iI][nN]er]")
- assert binary == :erlang.term_to_binary(~r/comp[lL][aA][iI][nN]er/)
- assert ConfigDB.from_binary(binary) == ~r/comp[lL][aA][iI][nN]er/
+ assert ConfigDB.to_elixir_types("~r[comp[lL][aA][iI][nN]er]") == ~r/comp[lL][aA][iI][nN]er/
end
test "link sigil" do
- binary = ConfigDB.transform("~r/https:\/\/example.com/")
- assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/)
- assert ConfigDB.from_binary(binary) == ~r/https:\/\/example.com/
+ assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/") == ~r/https:\/\/example.com/
end
test "link sigil with um modifiers" do
- binary = ConfigDB.transform("~r/https:\/\/example.com/um")
- assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/um)
- assert ConfigDB.from_binary(binary) == ~r/https:\/\/example.com/um
+ assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/um") ==
+ ~r/https:\/\/example.com/um
end
test "link sigil with i modifier" do
- binary = ConfigDB.transform("~r/https:\/\/example.com/i")
- assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/i)
- assert ConfigDB.from_binary(binary) == ~r/https:\/\/example.com/i
+ assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/i") == ~r/https:\/\/example.com/i
end
test "link sigil with s modifier" do
- binary = ConfigDB.transform("~r/https:\/\/example.com/s")
- assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/s)
- assert ConfigDB.from_binary(binary) == ~r/https:\/\/example.com/s
+ assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/s") == ~r/https:\/\/example.com/s
end
test "raise if valid delimiter not found" do
assert_raise ArgumentError, "valid delimiter for Regex expression not found", fn ->
- ConfigDB.transform("~r/https://[]{}<>\"'()|example.com/s")
+ ConfigDB.to_elixir_types("~r/https://[]{}<>\"'()|example.com/s")
end
end
test "2 child tuple" do
- binary = ConfigDB.transform(%{"tuple" => ["v1", ":v2"]})
- assert binary == :erlang.term_to_binary({"v1", :v2})
- assert ConfigDB.from_binary(binary) == {"v1", :v2}
+ assert ConfigDB.to_elixir_types(%{"tuple" => ["v1", ":v2"]}) == {"v1", :v2}
end
test "proxy tuple with localhost" do
- binary =
- ConfigDB.transform(%{
- "tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]
- })
-
- assert binary == :erlang.term_to_binary({:proxy_url, {:socks5, :localhost, 1234}})
- assert ConfigDB.from_binary(binary) == {:proxy_url, {:socks5, :localhost, 1234}}
+ assert ConfigDB.to_elixir_types(%{
+ "tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]
+ }) == {:proxy_url, {:socks5, :localhost, 1234}}
end
test "proxy tuple with domain" do
- binary =
- ConfigDB.transform(%{
- "tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]
- })
-
- assert binary == :erlang.term_to_binary({:proxy_url, {:socks5, 'domain.com', 1234}})
- assert ConfigDB.from_binary(binary) == {:proxy_url, {:socks5, 'domain.com', 1234}}
+ assert ConfigDB.to_elixir_types(%{
+ "tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]
+ }) == {:proxy_url, {:socks5, 'domain.com', 1234}}
end
test "proxy tuple with ip" do
- binary =
- ConfigDB.transform(%{
- "tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]
- })
-
- assert binary == :erlang.term_to_binary({:proxy_url, {:socks5, {127, 0, 0, 1}, 1234}})
- assert ConfigDB.from_binary(binary) == {:proxy_url, {:socks5, {127, 0, 0, 1}, 1234}}
+ assert ConfigDB.to_elixir_types(%{
+ "tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]
+ }) == {:proxy_url, {:socks5, {127, 0, 0, 1}, 1234}}
end
test "tuple with n childs" do
- binary =
- ConfigDB.transform(%{
- "tuple" => [
- "v1",
- ":v2",
- "Pleroma.Bookmark",
- 150,
- false,
- "Phoenix.Socket.V1.JSONSerializer"
- ]
- })
-
- assert binary ==
- :erlang.term_to_binary(
- {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
- )
-
- assert ConfigDB.from_binary(binary) ==
- {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
+ assert ConfigDB.to_elixir_types(%{
+ "tuple" => [
+ "v1",
+ ":v2",
+ "Pleroma.Bookmark",
+ 150,
+ false,
+ "Phoenix.Socket.V1.JSONSerializer"
+ ]
+ }) == {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
end
test "map with string key" do
- binary = ConfigDB.transform(%{"key" => "value"})
- assert binary == :erlang.term_to_binary(%{"key" => "value"})
- assert ConfigDB.from_binary(binary) == %{"key" => "value"}
+ assert ConfigDB.to_elixir_types(%{"key" => "value"}) == %{"key" => "value"}
end
test "map with atom key" do
- binary = ConfigDB.transform(%{":key" => "value"})
- assert binary == :erlang.term_to_binary(%{key: "value"})
- assert ConfigDB.from_binary(binary) == %{key: "value"}
+ assert ConfigDB.to_elixir_types(%{":key" => "value"}) == %{key: "value"}
end
test "list of strings" do
- binary = ConfigDB.transform(["v1", "v2", "v3"])
- assert binary == :erlang.term_to_binary(["v1", "v2", "v3"])
- assert ConfigDB.from_binary(binary) == ["v1", "v2", "v3"]
+ assert ConfigDB.to_elixir_types(["v1", "v2", "v3"]) == ["v1", "v2", "v3"]
end
test "list of modules" do
- binary = ConfigDB.transform(["Pleroma.Repo", "Pleroma.Activity"])
- assert binary == :erlang.term_to_binary([Pleroma.Repo, Pleroma.Activity])
- assert ConfigDB.from_binary(binary) == [Pleroma.Repo, Pleroma.Activity]
+ assert ConfigDB.to_elixir_types(["Pleroma.Repo", "Pleroma.Activity"]) == [
+ Pleroma.Repo,
+ Pleroma.Activity
+ ]
end
test "list of atoms" do
- binary = ConfigDB.transform([":v1", ":v2", ":v3"])
- assert binary == :erlang.term_to_binary([:v1, :v2, :v3])
- assert ConfigDB.from_binary(binary) == [:v1, :v2, :v3]
+ assert ConfigDB.to_elixir_types([":v1", ":v2", ":v3"]) == [:v1, :v2, :v3]
end
test "list of mixed values" do
- binary =
- ConfigDB.transform([
- "v1",
- ":v2",
- "Pleroma.Repo",
- "Phoenix.Socket.V1.JSONSerializer",
- 15,
- false
- ])
-
- assert binary ==
- :erlang.term_to_binary([
- "v1",
- :v2,
- Pleroma.Repo,
- Phoenix.Socket.V1.JSONSerializer,
- 15,
- false
- ])
-
- assert ConfigDB.from_binary(binary) == [
+ assert ConfigDB.to_elixir_types([
+ "v1",
+ ":v2",
+ "Pleroma.Repo",
+ "Phoenix.Socket.V1.JSONSerializer",
+ 15,
+ false
+ ]) == [
"v1",
:v2,
Pleroma.Repo,
@@ -470,40 +379,17 @@ defmodule Pleroma.ConfigDBTest do
end
test "simple keyword" do
- binary = ConfigDB.transform([%{"tuple" => [":key", "value"]}])
- assert binary == :erlang.term_to_binary([{:key, "value"}])
- assert ConfigDB.from_binary(binary) == [{:key, "value"}]
- assert ConfigDB.from_binary(binary) == [key: "value"]
- end
-
- test "keyword with partial_chain key" do
- binary =
- ConfigDB.transform([%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}])
-
- assert binary == :erlang.term_to_binary(partial_chain: &:hackney_connect.partial_chain/1)
- assert ConfigDB.from_binary(binary) == [partial_chain: &:hackney_connect.partial_chain/1]
+ assert ConfigDB.to_elixir_types([%{"tuple" => [":key", "value"]}]) == [key: "value"]
end
test "keyword" do
- binary =
- ConfigDB.transform([
- %{"tuple" => [":types", "Pleroma.PostgresTypes"]},
- %{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]},
- %{"tuple" => [":migration_lock", nil]},
- %{"tuple" => [":key1", 150]},
- %{"tuple" => [":key2", "string"]}
- ])
-
- assert binary ==
- :erlang.term_to_binary(
- types: Pleroma.PostgresTypes,
- telemetry_event: [Pleroma.Repo.Instrumenter],
- migration_lock: nil,
- key1: 150,
- key2: "string"
- )
-
- assert ConfigDB.from_binary(binary) == [
+ assert ConfigDB.to_elixir_types([
+ %{"tuple" => [":types", "Pleroma.PostgresTypes"]},
+ %{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]},
+ %{"tuple" => [":migration_lock", nil]},
+ %{"tuple" => [":key1", 150]},
+ %{"tuple" => [":key2", "string"]}
+ ]) == [
types: Pleroma.PostgresTypes,
telemetry_event: [Pleroma.Repo.Instrumenter],
migration_lock: nil,
@@ -512,86 +398,60 @@ defmodule Pleroma.ConfigDBTest do
]
end
+ test "trandformed keyword" do
+ assert ConfigDB.to_elixir_types(a: 1, b: 2, c: "string") == [a: 1, b: 2, c: "string"]
+ end
+
test "complex keyword with nested mixed childs" do
- binary =
- ConfigDB.transform([
- %{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]},
- %{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
- %{"tuple" => [":link_name", true]},
- %{"tuple" => [":proxy_remote", false]},
- %{"tuple" => [":common_map", %{":key" => "value"}]},
- %{
- "tuple" => [
- ":proxy_opts",
- [
- %{"tuple" => [":redirect_on_failure", false]},
- %{"tuple" => [":max_body_length", 1_048_576]},
- %{
- "tuple" => [
- ":http",
- [%{"tuple" => [":follow_redirect", true]}, %{"tuple" => [":pool", ":upload"]}]
- ]
- }
- ]
- ]
- }
- ])
-
- assert binary ==
- :erlang.term_to_binary(
- uploader: Pleroma.Uploaders.Local,
- filters: [Pleroma.Upload.Filter.Dedupe],
- link_name: true,
- proxy_remote: false,
- common_map: %{key: "value"},
- proxy_opts: [
- redirect_on_failure: false,
- max_body_length: 1_048_576,
- http: [
- follow_redirect: true,
- pool: :upload
+ assert ConfigDB.to_elixir_types([
+ %{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]},
+ %{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
+ %{"tuple" => [":link_name", true]},
+ %{"tuple" => [":proxy_remote", false]},
+ %{"tuple" => [":common_map", %{":key" => "value"}]},
+ %{
+ "tuple" => [
+ ":proxy_opts",
+ [
+ %{"tuple" => [":redirect_on_failure", false]},
+ %{"tuple" => [":max_body_length", 1_048_576]},
+ %{
+ "tuple" => [
+ ":http",
+ [
+ %{"tuple" => [":follow_redirect", true]},
+ %{"tuple" => [":pool", ":upload"]}
+ ]
+ ]
+ }
]
]
- )
-
- assert ConfigDB.from_binary(binary) ==
- [
- uploader: Pleroma.Uploaders.Local,
- filters: [Pleroma.Upload.Filter.Dedupe],
- link_name: true,
- proxy_remote: false,
- common_map: %{key: "value"},
- proxy_opts: [
- redirect_on_failure: false,
- max_body_length: 1_048_576,
- http: [
- follow_redirect: true,
- pool: :upload
- ]
+ }
+ ]) == [
+ uploader: Pleroma.Uploaders.Local,
+ filters: [Pleroma.Upload.Filter.Dedupe],
+ link_name: true,
+ proxy_remote: false,
+ common_map: %{key: "value"},
+ proxy_opts: [
+ redirect_on_failure: false,
+ max_body_length: 1_048_576,
+ http: [
+ follow_redirect: true,
+ pool: :upload
]
]
+ ]
end
test "common keyword" do
- binary =
- ConfigDB.transform([
- %{"tuple" => [":level", ":warn"]},
- %{"tuple" => [":meta", [":all"]]},
- %{"tuple" => [":path", ""]},
- %{"tuple" => [":val", nil]},
- %{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]}
- ])
-
- assert binary ==
- :erlang.term_to_binary(
- level: :warn,
- meta: [:all],
- path: "",
- val: nil,
- webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
- )
-
- assert ConfigDB.from_binary(binary) == [
+ assert ConfigDB.to_elixir_types([
+ %{"tuple" => [":level", ":warn"]},
+ %{"tuple" => [":meta", [":all"]]},
+ %{"tuple" => [":path", ""]},
+ %{"tuple" => [":val", nil]},
+ %{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]}
+ ]) == [
level: :warn,
meta: [:all],
path: "",
@@ -601,98 +461,73 @@ defmodule Pleroma.ConfigDBTest do
end
test "complex keyword with sigil" do
- binary =
- ConfigDB.transform([
- %{"tuple" => [":federated_timeline_removal", []]},
- %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]},
- %{"tuple" => [":replace", []]}
- ])
-
- assert binary ==
- :erlang.term_to_binary(
- federated_timeline_removal: [],
- reject: [~r/comp[lL][aA][iI][nN]er/],
- replace: []
- )
-
- assert ConfigDB.from_binary(binary) ==
- [federated_timeline_removal: [], reject: [~r/comp[lL][aA][iI][nN]er/], replace: []]
+ assert ConfigDB.to_elixir_types([
+ %{"tuple" => [":federated_timeline_removal", []]},
+ %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]},
+ %{"tuple" => [":replace", []]}
+ ]) == [
+ federated_timeline_removal: [],
+ reject: [~r/comp[lL][aA][iI][nN]er/],
+ replace: []
+ ]
end
test "complex keyword with tuples with more than 2 values" do
- binary =
- ConfigDB.transform([
- %{
- "tuple" => [
- ":http",
- [
- %{
- "tuple" => [
- ":key1",
- [
- %{
- "tuple" => [
- ":_",
- [
- %{
- "tuple" => [
- "/api/v1/streaming",
- "Pleroma.Web.MastodonAPI.WebsocketHandler",
- []
- ]
- },
- %{
- "tuple" => [
- "/websocket",
- "Phoenix.Endpoint.CowboyWebSocket",
- %{
- "tuple" => [
- "Phoenix.Transports.WebSocket",
- %{
- "tuple" => [
- "Pleroma.Web.Endpoint",
- "Pleroma.Web.UserSocket",
- []
- ]
- }
- ]
- }
- ]
- },
- %{
- "tuple" => [
- ":_",
- "Phoenix.Endpoint.Cowboy2Handler",
- %{"tuple" => ["Pleroma.Web.Endpoint", []]}
- ]
- }
- ]
- ]
- }
- ]
- ]
- }
- ]
- ]
- }
- ])
-
- assert binary ==
- :erlang.term_to_binary(
- http: [
- key1: [
- _: [
- {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
- {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
- {Phoenix.Transports.WebSocket,
- {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}},
- {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
- ]
+ assert ConfigDB.to_elixir_types([
+ %{
+ "tuple" => [
+ ":http",
+ [
+ %{
+ "tuple" => [
+ ":key1",
+ [
+ %{
+ "tuple" => [
+ ":_",
+ [
+ %{
+ "tuple" => [
+ "/api/v1/streaming",
+ "Pleroma.Web.MastodonAPI.WebsocketHandler",
+ []
+ ]
+ },
+ %{
+ "tuple" => [
+ "/websocket",
+ "Phoenix.Endpoint.CowboyWebSocket",
+ %{
+ "tuple" => [
+ "Phoenix.Transports.WebSocket",
+ %{
+ "tuple" => [
+ "Pleroma.Web.Endpoint",
+ "Pleroma.Web.UserSocket",
+ []
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ %{
+ "tuple" => [
+ ":_",
+ "Phoenix.Endpoint.Cowboy2Handler",
+ %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+ ]
+ }
+ ]
+ ]
+ }
+ ]
+ ]
+ }
]
]
- )
-
- assert ConfigDB.from_binary(binary) == [
+ }
+ ]) == [
http: [
key1: [
{:_,
diff --git a/test/config/deprecation_warnings_test.exs b/test/config/deprecation_warnings_test.exs
new file mode 100644
index 000000000..548ee87b0
--- /dev/null
+++ b/test/config/deprecation_warnings_test.exs
@@ -0,0 +1,57 @@
+defmodule Pleroma.Config.DeprecationWarningsTest do
+ use ExUnit.Case, async: true
+ use Pleroma.Tests.Helpers
+
+ import ExUnit.CaptureLog
+
+ test "check_old_mrf_config/0" do
+ clear_config([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.NoOpPolicy)
+ clear_config([:instance, :mrf_transparency], true)
+ clear_config([:instance, :mrf_transparency_exclusions], [])
+
+ assert capture_log(fn -> Pleroma.Config.DeprecationWarnings.check_old_mrf_config() end) =~
+ """
+ !!!DEPRECATION WARNING!!!
+ Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later:
+
+ * `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies`
+ * `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency`
+ * `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`
+ """
+ end
+
+ test "move_namespace_and_warn/2" do
+ old_group1 = [:group, :key]
+ old_group2 = [:group, :key2]
+ old_group3 = [:group, :key3]
+
+ new_group1 = [:another_group, :key4]
+ new_group2 = [:another_group, :key5]
+ new_group3 = [:another_group, :key6]
+
+ clear_config(old_group1, 1)
+ clear_config(old_group2, 2)
+ clear_config(old_group3, 3)
+
+ clear_config(new_group1)
+ clear_config(new_group2)
+ clear_config(new_group3)
+
+ config_map = [
+ {old_group1, new_group1, "\n error :key"},
+ {old_group2, new_group2, "\n error :key2"},
+ {old_group3, new_group3, "\n error :key3"}
+ ]
+
+ assert capture_log(fn ->
+ Pleroma.Config.DeprecationWarnings.move_namespace_and_warn(
+ config_map,
+ "Warning preface"
+ )
+ end) =~ "Warning preface\n error :key\n error :key2\n error :key3"
+
+ assert Pleroma.Config.get(new_group1) == 1
+ assert Pleroma.Config.get(new_group2) == 2
+ assert Pleroma.Config.get(new_group3) == 3
+ end
+end
diff --git a/test/config/transfer_task_test.exs b/test/config/transfer_task_test.exs
index 473899d1d..f53829e09 100644
--- a/test/config/transfer_task_test.exs
+++ b/test/config/transfer_task_test.exs
@@ -6,9 +6,9 @@ defmodule Pleroma.Config.TransferTaskTest do
use Pleroma.DataCase
import ExUnit.CaptureLog
+ import Pleroma.Factory
alias Pleroma.Config.TransferTask
- alias Pleroma.ConfigDB
setup do: clear_config(:configurable_from_database, true)
@@ -19,31 +19,11 @@ defmodule Pleroma.Config.TransferTaskTest do
refute Application.get_env(:postgrex, :test_key)
initial = Application.get_env(:logger, :level)
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":test_key",
- value: [live: 2, com: 3]
- })
-
- ConfigDB.create(%{
- group: ":idna",
- key: ":test_key",
- value: [live: 15, com: 35]
- })
-
- ConfigDB.create(%{
- group: ":quack",
- key: ":test_key",
- value: [:test_value1, :test_value2]
- })
-
- ConfigDB.create(%{
- group: ":postgrex",
- key: ":test_key",
- value: :value
- })
-
- ConfigDB.create(%{group: ":logger", key: ":level", value: :debug})
+ insert(:config, key: :test_key, value: [live: 2, com: 3])
+ insert(:config, group: :idna, key: :test_key, value: [live: 15, com: 35])
+ insert(:config, group: :quack, key: :test_key, value: [:test_value1, :test_value2])
+ insert(:config, group: :postgrex, key: :test_key, value: :value)
+ insert(:config, group: :logger, key: :level, value: :debug)
TransferTask.start_link([])
@@ -66,17 +46,8 @@ defmodule Pleroma.Config.TransferTaskTest do
level = Application.get_env(:quack, :level)
meta = Application.get_env(:quack, :meta)
- ConfigDB.create(%{
- group: ":quack",
- key: ":level",
- value: :info
- })
-
- ConfigDB.create(%{
- group: ":quack",
- key: ":meta",
- value: [:none]
- })
+ insert(:config, group: :quack, key: :level, value: :info)
+ insert(:config, group: :quack, key: :meta, value: [:none])
TransferTask.start_link([])
@@ -95,17 +66,8 @@ defmodule Pleroma.Config.TransferTaskTest do
clear_config(:emoji)
clear_config(:assets)
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":emoji",
- value: [groups: [a: 1, b: 2]]
- })
-
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":assets",
- value: [mascots: [a: 1, b: 2]]
- })
+ insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]])
+ insert(:config, key: :assets, value: [mascots: [a: 1, b: 2]])
TransferTask.start_link([])
@@ -122,12 +84,7 @@ defmodule Pleroma.Config.TransferTaskTest do
test "don't restart if no reboot time settings were changed" do
clear_config(:emoji)
-
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":emoji",
- value: [groups: [a: 1, b: 2]]
- })
+ insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]])
refute String.contains?(
capture_log(fn -> TransferTask.start_link([]) end),
@@ -137,25 +94,13 @@ defmodule Pleroma.Config.TransferTaskTest do
test "on reboot time key" do
clear_config(:chat)
-
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":chat",
- value: [enabled: false]
- })
-
+ insert(:config, key: :chat, value: [enabled: false])
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
end
test "on reboot time subkey" do
clear_config(Pleroma.Captcha)
-
- ConfigDB.create(%{
- group: ":pleroma",
- key: "Pleroma.Captcha",
- value: [seconds_valid: 60]
- })
-
+ insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
end
@@ -163,17 +108,8 @@ defmodule Pleroma.Config.TransferTaskTest do
clear_config(:chat)
clear_config(Pleroma.Captcha)
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":chat",
- value: [enabled: false]
- })
-
- ConfigDB.create(%{
- group: ":pleroma",
- key: "Pleroma.Captcha",
- value: [seconds_valid: 60]
- })
+ insert(:config, key: :chat, value: [enabled: false])
+ insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
refute String.contains?(
capture_log(fn -> TransferTask.load_and_update_env([], false) end),
diff --git a/test/fixtures/config/temp.secret.exs b/test/fixtures/config/temp.secret.exs
index dc950ca30..fa8c7c7e8 100644
--- a/test/fixtures/config/temp.secret.exs
+++ b/test/fixtures/config/temp.secret.exs
@@ -9,3 +9,5 @@ config :quack, level: :info
config :pleroma, Pleroma.Repo, pool: Ecto.Adapters.SQL.Sandbox
config :postgrex, :json_library, Poison
+
+config :pleroma, :database, rum_enabled: true
diff --git a/test/fixtures/create-chat-message.json b/test/fixtures/create-chat-message.json
new file mode 100644
index 000000000..9c23a1c9b
--- /dev/null
+++ b/test/fixtures/create-chat-message.json
@@ -0,0 +1,31 @@
+{
+ "actor": "http://2hu.gensokyo/users/raymoo",
+ "id": "http://2hu.gensokyo/objects/1",
+ "object": {
+ "attributedTo": "http://2hu.gensokyo/users/raymoo",
+ "content": "You expected a cute girl? Too bad. <script>alert('XSS')</script>",
+ "id": "http://2hu.gensokyo/objects/2",
+ "published": "2020-02-12T14:08:20Z",
+ "to": [
+ "http://2hu.gensokyo/users/marisa"
+ ],
+ "tag": [
+ {
+ "icon": {
+ "type": "Image",
+ "url": "http://2hu.gensokyo/emoji/Firefox.gif"
+ },
+ "id": "http://2hu.gensokyo/emoji/Firefox.gif",
+ "name": ":firefox:",
+ "type": "Emoji",
+ "updated": "1970-01-01T00:00:00Z"
+ }
+ ],
+ "type": "ChatMessage"
+ },
+ "published": "2018-02-12T14:08:20Z",
+ "to": [
+ "http://2hu.gensokyo/users/marisa"
+ ],
+ "type": "Create"
+}
diff --git a/test/instance_static/emoji/test_pack/blank2.png b/test/instance_static/emoji/test_pack/blank2.png
new file mode 100644
index 000000000..8f50fa023
--- /dev/null
+++ b/test/instance_static/emoji/test_pack/blank2.png
Binary files differ
diff --git a/test/instance_static/emoji/test_pack/pack.json b/test/instance_static/emoji/test_pack/pack.json
index 481891b08..5b33fbb32 100644
--- a/test/instance_static/emoji/test_pack/pack.json
+++ b/test/instance_static/emoji/test_pack/pack.json
@@ -1,6 +1,7 @@
{
"files": {
- "blank": "blank.png"
+ "blank": "blank.png",
+ "blank2": "blank2.png"
},
"pack": {
"description": "Test description",
diff --git a/test/instance_static/emoji/test_pack_nonshared/nonshared.zip b/test/instance_static/emoji/test_pack_nonshared/nonshared.zip
index 148446c64..59bff37f0 100644
--- a/test/instance_static/emoji/test_pack_nonshared/nonshared.zip
+++ b/test/instance_static/emoji/test_pack_nonshared/nonshared.zip
Binary files differ
diff --git a/test/instance_static/emoji/test_pack_nonshared/pack.json b/test/instance_static/emoji/test_pack_nonshared/pack.json
index 93d643a5f..09f6274d1 100644
--- a/test/instance_static/emoji/test_pack_nonshared/pack.json
+++ b/test/instance_static/emoji/test_pack_nonshared/pack.json
@@ -4,7 +4,7 @@
"homepage": "https://pleroma.social",
"description": "Test description",
"fallback-src": "https://nonshared-pack",
- "fallback-src-sha256": "74409E2674DAA06C072729C6C8426C4CB3B7E0B85ED77792DB7A436E11D76DAF",
+ "fallback-src-sha256": "1967BB4E42BCC34BCC12D57BE7811D3B7BE52F965BCE45C87BD377B9499CE11D",
"share-files": false
},
"files": {
diff --git a/test/migration_helper/notification_backfill_test.exs b/test/migration_helper/notification_backfill_test.exs
new file mode 100644
index 000000000..2a62a2b00
--- /dev/null
+++ b/test/migration_helper/notification_backfill_test.exs
@@ -0,0 +1,56 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.MigrationHelper.NotificationBackfillTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Activity
+ alias Pleroma.MigrationHelper.NotificationBackfill
+ alias Pleroma.Notification
+ alias Pleroma.Repo
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "fill_in_notification_types" do
+ test "it fills in missing notification types" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, post} = CommonAPI.post(user, %{status: "yeah, @#{other_user.nickname}"})
+ {:ok, chat} = CommonAPI.post_chat_message(user, other_user, "yo")
+ {:ok, react} = CommonAPI.react_with_emoji(post.id, other_user, "☕")
+ {:ok, like} = CommonAPI.favorite(other_user, post.id)
+ {:ok, react_2} = CommonAPI.react_with_emoji(post.id, other_user, "☕")
+
+ data =
+ react_2.data
+ |> Map.put("type", "EmojiReaction")
+
+ {:ok, react_2} =
+ react_2
+ |> Activity.change(%{data: data})
+ |> Repo.update()
+
+ assert {5, nil} = Repo.update_all(Notification, set: [type: nil])
+
+ NotificationBackfill.fill_in_notification_types()
+
+ assert %{type: "mention"} =
+ Repo.get_by(Notification, user_id: other_user.id, activity_id: post.id)
+
+ assert %{type: "favourite"} =
+ Repo.get_by(Notification, user_id: user.id, activity_id: like.id)
+
+ assert %{type: "pleroma:emoji_reaction"} =
+ Repo.get_by(Notification, user_id: user.id, activity_id: react.id)
+
+ assert %{type: "pleroma:emoji_reaction"} =
+ Repo.get_by(Notification, user_id: user.id, activity_id: react_2.id)
+
+ assert %{type: "pleroma:chat_mention"} =
+ Repo.get_by(Notification, user_id: other_user.id, activity_id: chat.id)
+ end
+ end
+end
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 37c255fee..526f43fab 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -10,6 +10,7 @@ defmodule Pleroma.NotificationTest do
alias Pleroma.FollowingRelationship
alias Pleroma.Notification
+ alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -31,6 +32,7 @@ defmodule Pleroma.NotificationTest do
{:ok, [notification]} = Notification.create_notifications(activity)
assert notification.user_id == user.id
+ assert notification.type == "pleroma:emoji_reaction"
end
test "notifies someone when they are directly addressed" do
@@ -48,6 +50,7 @@ defmodule Pleroma.NotificationTest do
notified_ids = Enum.sort([notification.user_id, other_notification.user_id])
assert notified_ids == [other_user.id, third_user.id]
assert notification.activity_id == activity.id
+ assert notification.type == "mention"
assert other_notification.activity_id == activity.id
assert [%Pleroma.Marker{unread_count: 2}] =
@@ -303,6 +306,14 @@ defmodule Pleroma.NotificationTest do
assert {:ok, []} == Notification.create_notifications(status)
end
+
+ test "it disables notifications from people who are invisible" do
+ author = insert(:user, invisible: true)
+ user = insert(:user)
+
+ {:ok, status} = CommonAPI.post(author, %{status: "hey @#{user.nickname}"})
+ refute Notification.create_notification(status, user)
+ end
end
describe "follow / follow_request notifications" do
@@ -335,9 +346,12 @@ defmodule Pleroma.NotificationTest do
# After request is accepted, the same notification is rendered with type "follow":
assert {:ok, _} = CommonAPI.accept_follow_request(user, followed_user)
- notification_id = notification.id
- assert [%{id: ^notification_id}] = Notification.for_user(followed_user)
- assert %{type: "follow"} = NotificationView.render("show.json", render_opts)
+ notification =
+ Repo.get(Notification, notification.id)
+ |> Repo.preload(:activity)
+
+ assert %{type: "follow"} =
+ NotificationView.render("show.json", notification: notification, for: followed_user)
end
test "it doesn't create a notification for follow-unfollow-follow chains" do
diff --git a/test/repo_test.exs b/test/repo_test.exs
index daffc6542..92e827c95 100644
--- a/test/repo_test.exs
+++ b/test/repo_test.exs
@@ -4,9 +4,7 @@
defmodule Pleroma.RepoTest do
use Pleroma.DataCase
- import ExUnit.CaptureLog
import Pleroma.Factory
- import Mock
alias Pleroma.User
@@ -49,36 +47,4 @@ defmodule Pleroma.RepoTest do
assert Repo.get_assoc(token, :user) == {:error, :not_found}
end
end
-
- describe "check_migrations_applied!" do
- setup_with_mocks([
- {Ecto.Migrator, [],
- [
- with_repo: fn repo, fun -> passthrough([repo, fun]) end,
- migrations: fn Pleroma.Repo ->
- [
- {:up, 20_191_128_153_944, "fix_missing_following_count"},
- {:up, 20_191_203_043_610, "create_report_notes"},
- {:down, 20_191_220_174_645, "add_scopes_to_pleroma_feo_auth_records"}
- ]
- end
- ]}
- ]) do
- :ok
- end
-
- setup do: clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
-
- test "raises if it detects unapplied migrations" do
- assert_raise Pleroma.Repo.UnappliedMigrationsError, fn ->
- capture_log(&Repo.check_migrations_applied!/0)
- end
- end
-
- test "doesn't do anything if disabled" do
- Pleroma.Config.put([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true)
-
- assert :ok == Repo.check_migrations_applied!()
- end
- end
end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 6e3676aca..6e22b66a4 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -42,7 +42,8 @@ defmodule Pleroma.Factory do
user
| ap_id: User.ap_id(user),
follower_address: User.ap_followers(user),
- following_address: User.ap_following(user)
+ following_address: User.ap_following(user),
+ raw_bio: user.bio
}
end
@@ -396,24 +397,17 @@ defmodule Pleroma.Factory do
}
end
- def config_factory do
+ def config_factory(attrs \\ %{}) do
%Pleroma.ConfigDB{
- key:
- sequence(:key, fn key ->
- # Atom dynamic registration hack in tests
- "some_key_#{key}"
- |> String.to_atom()
- |> inspect()
- end),
- group: ":pleroma",
+ key: sequence(:key, &String.to_atom("some_key_#{&1}")),
+ group: :pleroma,
value:
sequence(
:value,
- fn key ->
- :erlang.term_to_binary(%{another_key: "#{key}somevalue", another: "#{key}somevalue"})
- end
+ &%{another_key: "#{&1}somevalue", another: "#{&1}somevalue"}
)
}
+ |> merge_attributes(attrs)
end
def marker_factory do
diff --git a/test/tasks/config_test.exs b/test/tasks/config_test.exs
index 04bc947a9..71f36c0e3 100644
--- a/test/tasks/config_test.exs
+++ b/test/tasks/config_test.exs
@@ -5,6 +5,8 @@
defmodule Mix.Tasks.Pleroma.ConfigTest do
use Pleroma.DataCase
+ import Pleroma.Factory
+
alias Pleroma.ConfigDB
alias Pleroma.Repo
@@ -48,25 +50,21 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
config3 = ConfigDB.get_by_params(%{group: ":quack", key: ":level"})
refute ConfigDB.get_by_params(%{group: ":pleroma", key: "Pleroma.Repo"})
refute ConfigDB.get_by_params(%{group: ":postgrex", key: ":json_library"})
+ refute ConfigDB.get_by_params(%{group: ":pleroma", key: ":database"})
- assert ConfigDB.from_binary(config1.value) == [key: "value", key2: [Repo]]
- assert ConfigDB.from_binary(config2.value) == [key: "value2", key2: ["Activity"]]
- assert ConfigDB.from_binary(config3.value) == :info
+ assert config1.value == [key: "value", key2: [Repo]]
+ assert config2.value == [key: "value2", key2: ["Activity"]]
+ assert config3.value == :info
end
test "config table is truncated before migration" do
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":first_setting",
- value: [key: "value", key2: ["Activity"]]
- })
-
+ insert(:config, key: :first_setting, value: [key: "value", key2: ["Activity"]])
assert Repo.aggregate(ConfigDB, :count, :id) == 1
Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs")
config = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"})
- assert ConfigDB.from_binary(config.value) == [key: "value", key2: [Repo]]
+ assert config.value == [key: "value", key2: [Repo]]
end
end
@@ -82,19 +80,9 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
end
test "settings are migrated to file and deleted from db", %{temp_file: temp_file} do
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":setting_first",
- value: [key: "value", key2: ["Activity"]]
- })
-
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":setting_second",
- value: [key: "value2", key2: [Repo]]
- })
-
- ConfigDB.create(%{group: ":quack", key: ":level", value: :info})
+ insert(:config, key: :setting_first, value: [key: "value", key2: ["Activity"]])
+ insert(:config, key: :setting_second, value: [key: "value2", key2: [Repo]])
+ insert(:config, group: :quack, key: :level, value: :info)
Mix.Tasks.Pleroma.Config.run(["migrate_from_db", "--env", "temp", "-d"])
@@ -107,9 +95,8 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
end
test "load a settings with large values and pass to file", %{temp_file: temp_file} do
- ConfigDB.create(%{
- group: ":pleroma",
- key: ":instance",
+ insert(:config,
+ key: :instance,
value: [
name: "Pleroma",
email: "example@example.com",
@@ -134,14 +121,11 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
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,
attachment_links: false,
@@ -163,7 +147,6 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
extended_nickname_format: true,
multi_factor_authentication: [
totp: [
- # digits 6 or 8
digits: 6,
period: 30
],
@@ -173,7 +156,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
]
]
]
- })
+ )
Mix.Tasks.Pleroma.Config.run(["migrate_from_db", "--env", "temp", "-d"])
@@ -189,7 +172,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
end
assert file ==
- "#{header}\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 attachment_links: false,\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 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"
+ "#{header}\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 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 autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\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 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
end
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index b55aa1cdb..9220d23fc 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -4,6 +4,7 @@
defmodule Mix.Tasks.Pleroma.UserTest do
alias Pleroma.Activity
+ alias Pleroma.MFA
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
@@ -278,6 +279,35 @@ defmodule Mix.Tasks.Pleroma.UserTest do
end
end
+ describe "running reset_mfa" do
+ test "disables MFA" do
+ user =
+ insert(:user,
+ multi_factor_authentication_settings: %MFA.Settings{
+ enabled: true,
+ totp: %MFA.Settings.TOTP{secret: "xx", confirmed: true}
+ }
+ )
+
+ Mix.Tasks.Pleroma.User.run(["reset_mfa", user.nickname])
+
+ assert_received {:mix_shell, :info, [message]}
+ assert message == "Multi-Factor Authentication disabled for #{user.nickname}"
+
+ assert %{enabled: false, totp: false} ==
+ user.nickname
+ |> User.get_cached_by_nickname()
+ |> MFA.mfa_settings()
+ end
+
+ test "no user to reset MFA" do
+ Mix.Tasks.Pleroma.User.run(["reset_password", "nonexistent"])
+
+ assert_received {:mix_shell, :error, [message]}
+ assert message =~ "No local user"
+ end
+ end
+
describe "running invite" do
test "invite token is generated" do
assert capture_io(fn ->
diff --git a/test/upload/filter/mogrify_test.exs b/test/upload/filter/mogrify_test.exs
index b6a463e8c..62ca30487 100644
--- a/test/upload/filter/mogrify_test.exs
+++ b/test/upload/filter/mogrify_test.exs
@@ -6,21 +6,17 @@ defmodule Pleroma.Upload.Filter.MogrifyTest do
use Pleroma.DataCase
import Mock
- alias Pleroma.Config
- alias Pleroma.Upload
alias Pleroma.Upload.Filter
- setup do: clear_config([Filter.Mogrify, :args])
-
test "apply mogrify filter" do
- Config.put([Filter.Mogrify, :args], [{"tint", "40"}])
+ clear_config(Filter.Mogrify, args: [{"tint", "40"}])
File.cp!(
"test/fixtures/image.jpg",
"test/fixtures/image_tmp.jpg"
)
- upload = %Upload{
+ upload = %Pleroma.Upload{
name: "an… image.jpg",
content_type: "image/jpg",
path: Path.absname("test/fixtures/image_tmp.jpg"),
diff --git a/test/upload_test.exs b/test/upload_test.exs
index 060a940bb..2abf0edec 100644
--- a/test/upload_test.exs
+++ b/test/upload_test.exs
@@ -54,6 +54,7 @@ defmodule Pleroma.UploadTest do
%{
"name" => "image.jpg",
"type" => "Document",
+ "mediaType" => "image/jpeg",
"url" => [
%{
"href" => "http://localhost:4001/media/post-process-file.jpg",
diff --git a/test/user_test.exs b/test/user_test.exs
index 98c79da4f..311b6c683 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -1342,11 +1342,11 @@ defmodule Pleroma.UserTest do
end
end
- describe "visible_for?/2" do
+ describe "visible_for/2" do
test "returns true when the account is itself" do
user = insert(:user, local: true)
- assert User.visible_for?(user, user)
+ assert User.visible_for(user, user) == :visible
end
test "returns false when the account is unauthenticated and auth is required" do
@@ -1355,14 +1355,14 @@ defmodule Pleroma.UserTest do
user = insert(:user, local: true, confirmation_pending: true)
other_user = insert(:user, local: true)
- refute User.visible_for?(user, other_user)
+ refute User.visible_for(user, other_user) == :visible
end
test "returns true when the account is unauthenticated and auth is not required" do
user = insert(:user, local: true, confirmation_pending: true)
other_user = insert(:user, local: true)
- assert User.visible_for?(user, other_user)
+ assert User.visible_for(user, other_user) == :visible
end
test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do
@@ -1371,7 +1371,7 @@ defmodule Pleroma.UserTest do
user = insert(:user, local: true, confirmation_pending: true)
other_user = insert(:user, local: true, is_admin: true)
- assert User.visible_for?(user, other_user)
+ assert User.visible_for(user, other_user) == :visible
end
end
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 2f65dfc8e..1c684df1a 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -574,7 +574,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
refute Enum.member?(activities, activity_four)
end
- test "doesn't return announce activities concerning blocked users" do
+ test "doesn't return announce activities with blocked users in 'to'" do
blocker = insert(:user)
blockee = insert(:user)
friend = insert(:user)
@@ -596,6 +596,39 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
refute Enum.member?(activities, activity_three.id)
end
+ test "doesn't return announce activities with blocked users in 'cc'" do
+ blocker = insert(:user)
+ blockee = insert(:user)
+ friend = insert(:user)
+
+ {:ok, _user_relationship} = User.block(blocker, blockee)
+
+ {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
+
+ {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
+
+ assert object = Pleroma.Object.normalize(activity_two)
+
+ data = %{
+ "actor" => friend.ap_id,
+ "object" => object.data["id"],
+ "context" => object.data["context"],
+ "type" => "Announce",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [blockee.ap_id]
+ }
+
+ assert {:ok, activity_three} = ActivityPub.insert(data)
+
+ activities =
+ ActivityPub.fetch_activities([], %{blocking_user: blocker})
+ |> Enum.map(fn act -> act.id end)
+
+ assert Enum.member?(activities, activity_one.id)
+ refute Enum.member?(activities, activity_two.id)
+ refute Enum.member?(activities, activity_three.id)
+ end
+
test "doesn't return activities from blocked domains" do
domain = "dogwhistle.zone"
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
@@ -1643,6 +1676,40 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert Enum.all?(visible_ids, &(&1 in activities_ids))
end
+
+ test "filtering out announces where the user is the actor of the announced message" do
+ user = insert(:user)
+ other_user = insert(:user)
+ third_user = insert(:user)
+ User.follow(user, other_user)
+
+ {:ok, post} = CommonAPI.post(user, %{status: "yo"})
+ {:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
+ {:ok, _announce} = CommonAPI.repeat(post.id, other_user)
+ {:ok, _announce} = CommonAPI.repeat(post.id, third_user)
+ {:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
+
+ params = %{
+ type: ["Announce"]
+ }
+
+ results =
+ [user.ap_id | User.following(user)]
+ |> ActivityPub.fetch_activities(params)
+
+ assert length(results) == 3
+
+ params = %{
+ type: ["Announce"],
+ announce_filtering_user: user
+ }
+
+ [result] =
+ [user.ap_id | User.following(user)]
+ |> ActivityPub.fetch_activities(params)
+
+ assert result.id == announce.id
+ end
end
describe "replies filtering with private messages" do
@@ -1986,4 +2053,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
end) =~ "Follower/Following counter update for #{user.ap_id} failed"
end
end
+
+ describe "global activity expiration" do
+ setup do: clear_config([:mrf, :policies])
+
+ test "creates an activity expiration for local Create activities" do
+ Pleroma.Config.put(
+ [:mrf, :policies],
+ Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy
+ )
+
+ {:ok, %{id: id_create}} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
+ {:ok, _follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
+
+ assert [%{activity_id: ^id_create}] = Pleroma.ActivityExpiration |> Repo.all()
+ end
+ end
end
diff --git a/test/web/activity_pub/mrf/activity_expiration_policy_test.exs b/test/web/activity_pub/mrf/activity_expiration_policy_test.exs
new file mode 100644
index 000000000..8babf49e7
--- /dev/null
+++ b/test/web/activity_pub/mrf/activity_expiration_policy_test.exs
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do
+ use ExUnit.Case, async: true
+ alias Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy
+
+ @id Pleroma.Web.Endpoint.url() <> "/activities/cofe"
+
+ test "adds `expires_at` property" do
+ assert {:ok, %{"type" => "Create", "expires_at" => expires_at}} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => @id,
+ "type" => "Create",
+ "object" => %{"type" => "Note"}
+ })
+
+ assert Timex.diff(expires_at, NaiveDateTime.utc_now(), :days) == 364
+ end
+
+ test "keeps existing `expires_at` if it less than the config setting" do
+ expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: 1)
+
+ assert {:ok, %{"type" => "Create", "expires_at" => ^expires_at}} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => @id,
+ "type" => "Create",
+ "expires_at" => expires_at,
+ "object" => %{"type" => "Note"}
+ })
+ end
+
+ test "overwrites existing `expires_at` if it greater than the config setting" do
+ too_distant_future = NaiveDateTime.utc_now() |> Timex.shift(years: 2)
+
+ assert {:ok, %{"type" => "Create", "expires_at" => expires_at}} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => @id,
+ "type" => "Create",
+ "expires_at" => too_distant_future,
+ "object" => %{"type" => "Note"}
+ })
+
+ assert Timex.diff(expires_at, NaiveDateTime.utc_now(), :days) == 364
+ end
+
+ test "ignores remote activities" do
+ assert {:ok, activity} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => "https://example.com/123",
+ "type" => "Create",
+ "object" => %{"type" => "Note"}
+ })
+
+ refute Map.has_key?(activity, "expires_at")
+ end
+
+ test "ignores non-Create/Note activities" do
+ assert {:ok, activity} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => "https://example.com/123",
+ "type" => "Follow"
+ })
+
+ refute Map.has_key?(activity, "expires_at")
+
+ assert {:ok, activity} =
+ ActivityExpirationPolicy.filter(%{
+ "id" => "https://example.com/123",
+ "type" => "Create",
+ "object" => %{"type" => "Cofe"}
+ })
+
+ refute Map.has_key?(activity, "expires_at")
+ end
+end
diff --git a/test/web/activity_pub/mrf/hellthread_policy_test.exs b/test/web/activity_pub/mrf/hellthread_policy_test.exs
index 95ef0b168..6e9daa7f9 100644
--- a/test/web/activity_pub/mrf/hellthread_policy_test.exs
+++ b/test/web/activity_pub/mrf/hellthread_policy_test.exs
@@ -8,6 +8,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy
+ alias Pleroma.Web.CommonAPI
+
setup do
user = insert(:user)
@@ -20,7 +22,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
"https://instance.tld/users/user1",
"https://instance.tld/users/user2",
"https://instance.tld/users/user3"
- ]
+ ],
+ "object" => %{
+ "type" => "Note"
+ }
}
[user: user, message: message]
@@ -28,6 +33,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do
setup do: clear_config(:mrf_hellthread)
+ test "doesn't die on chat messages" do
+ Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 2, reject_threshold: 0})
+
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post_chat_message(user, other_user, "moin")
+
+ assert {:ok, _} = filter(activity.data)
+ end
+
describe "reject" do
test "rejects the message if the recipient count is above reject_threshold", %{
message: message
diff --git a/test/web/activity_pub/mrf/mrf_test.exs b/test/web/activity_pub/mrf/mrf_test.exs
index c941066f2..a63b25423 100644
--- a/test/web/activity_pub/mrf/mrf_test.exs
+++ b/test/web/activity_pub/mrf/mrf_test.exs
@@ -60,8 +60,6 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
end
describe "describe/0" do
- setup do: clear_config([:instance, :rewrite_policy])
-
test "it works as expected with noop policy" do
expected = %{
mrf_policies: ["NoOpPolicy"],
@@ -72,7 +70,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
end
test "it works as expected with mock policy" do
- Pleroma.Config.put([:instance, :rewrite_policy], [MRFModuleMock])
+ clear_config([:mrf, :policies], [MRFModuleMock])
expected = %{
mrf_policies: ["MRFModuleMock"],
diff --git a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs
index 724bae058..ba1b69658 100644
--- a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs
+++ b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do
alias Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy
- setup do: clear_config([:mrf_user_allowlist, :localhost])
+ setup do: clear_config(:mrf_user_allowlist)
test "pass filter if allow list is empty" do
actor = insert(:user)
@@ -17,14 +17,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do
test "pass filter if allow list isn't empty and user in allow list" do
actor = insert(:user)
- Pleroma.Config.put([:mrf_user_allowlist, :localhost], [actor.ap_id, "test-ap-id"])
+ Pleroma.Config.put([:mrf_user_allowlist], %{"localhost" => [actor.ap_id, "test-ap-id"]})
message = %{"actor" => actor.ap_id}
assert UserAllowListPolicy.filter(message) == {:ok, message}
end
test "rejected if allow list isn't empty and user not in allow list" do
actor = insert(:user)
- Pleroma.Config.put([:mrf_user_allowlist, :localhost], ["test-ap-id"])
+ Pleroma.Config.put([:mrf_user_allowlist], %{"localhost" => ["test-ap-id"]})
message = %{"actor" => actor.ap_id}
assert UserAllowListPolicy.filter(message) == {:reject, nil}
end
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index 7953eecf2..31224abe0 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -2,14 +2,264 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
use Pleroma.DataCase
alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
+ describe "attachments" do
+ test "works with honkerific attachments" do
+ attachment = %{
+ "mediaType" => "",
+ "name" => "",
+ "summary" => "298p3RG7j27tfsZ9RQ.jpg",
+ "type" => "Document",
+ "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
+ }
+
+ assert {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert attachment.mediaType == "application/octet-stream"
+ end
+
+ test "it turns mastodon attachments into our attachments" do
+ attachment = %{
+ "url" =>
+ "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
+ "type" => "Document",
+ "name" => nil,
+ "mediaType" => "image/jpeg"
+ }
+
+ {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert [
+ %{
+ href:
+ "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
+ type: "Link",
+ mediaType: "image/jpeg"
+ }
+ ] = attachment.url
+
+ assert attachment.mediaType == "image/jpeg"
+ end
+
+ test "it handles our own uploads" do
+ user = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ {:ok, attachment} =
+ attachment.data
+ |> AttachmentValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert attachment.mediaType == "image/jpeg"
+ end
+ end
+
+ describe "chat message create activities" do
+ test "it is invalid if the object already exists" do
+ user = insert(:user)
+ recipient = insert(:user)
+ {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey")
+ object = Object.normalize(activity, false)
+
+ {:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id])
+
+ {:error, cng} = ObjectValidator.validate(create_data, [])
+
+ assert {:object, {"The object to create already exists", []}} in cng.errors
+ end
+
+ test "it is invalid if the object data has a different `to` or `actor` field" do
+ user = insert(:user)
+ recipient = insert(:user)
+ {:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey")
+
+ {:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id])
+
+ {:error, cng} = ObjectValidator.validate(create_data, [])
+
+ assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors
+ assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors
+ end
+ end
+
+ describe "chat messages" do
+ setup do
+ clear_config([:instance, :remote_limit])
+ user = insert(:user)
+ recipient = insert(:user, local: false)
+
+ {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:")
+
+ %{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
+ end
+
+ test "let's through some basic html", %{user: user, recipient: recipient} do
+ {:ok, valid_chat_message, _} =
+ Builder.chat_message(
+ user,
+ recipient.ap_id,
+ "hey <a href='https://example.org'>example</a> <script>alert('uguu')</script>"
+ )
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["content"] ==
+ "hey <a href=\"https://example.org\">example</a> alert(&#39;uguu&#39;)"
+ end
+
+ test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert Map.put(valid_chat_message, "attachment", nil) == object
+ end
+
+ test "validates for a basic object with an attachment", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", attachment.data)
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
+ end
+
+ test "validates for a basic object with an attachment in an array", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", [attachment.data])
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
+ end
+
+ test "validates for a basic object with an attachment but without content", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", attachment.data)
+ |> Map.delete("content")
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
+ end
+
+ test "does not validate if the message has no content", %{
+ valid_chat_message: valid_chat_message
+ } do
+ contentless =
+ valid_chat_message
+ |> Map.delete("content")
+
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, []))
+ end
+
+ test "does not validate if the message is longer than the remote_limit", %{
+ valid_chat_message: valid_chat_message
+ } do
+ Pleroma.Config.put([:instance, :remote_limit], 2)
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
+ end
+
+ test "does not validate if the recipient is blocking the actor", %{
+ valid_chat_message: valid_chat_message,
+ user: user,
+ recipient: recipient
+ } do
+ Pleroma.User.block(recipient, user)
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
+ end
+
+ test "does not validate if the actor or the recipient is not in our system", %{
+ valid_chat_message: valid_chat_message
+ } do
+ chat_message =
+ valid_chat_message
+ |> Map.put("actor", "https://raymoo.com/raymoo")
+
+ {:error, _} = ObjectValidator.validate(chat_message, [])
+
+ chat_message =
+ valid_chat_message
+ |> Map.put("to", ["https://raymoo.com/raymoo"])
+
+ {:error, _} = ObjectValidator.validate(chat_message, [])
+ end
+
+ test "does not validate for a message with multiple recipients", %{
+ valid_chat_message: valid_chat_message,
+ user: user,
+ recipient: recipient
+ } do
+ chat_message =
+ valid_chat_message
+ |> Map.put("to", [user.ap_id, recipient.ap_id])
+
+ assert {:error, _} = ObjectValidator.validate(chat_message, [])
+ end
+
+ test "does not validate if it doesn't concern local users" do
+ user = insert(:user, local: false)
+ recipient = insert(:user, local: false)
+
+ {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
+ assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
+ end
+ end
+
describe "EmojiReacts" do
setup do
user = insert(:user)
diff --git a/test/web/activity_pub/object_validators/types/date_time_test.exs b/test/web/activity_pub/object_validators/types/date_time_test.exs
index 3e17a9497..43be8e936 100644
--- a/test/web/activity_pub/object_validators/types/date_time_test.exs
+++ b/test/web/activity_pub/object_validators/types/date_time_test.exs
@@ -1,5 +1,5 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTimeTest do
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime
use Pleroma.DataCase
test "it validates an xsd:Datetime" do
diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/web/activity_pub/object_validators/types/object_id_test.exs
index 834213182..e0ab76379 100644
--- a/test/web/activity_pub/object_validators/types/object_id_test.exs
+++ b/test/web/activity_pub/object_validators/types/object_id_test.exs
@@ -1,5 +1,9 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID
use Pleroma.DataCase
@uris [
diff --git a/test/web/activity_pub/object_validators/types/recipients_test.exs b/test/web/activity_pub/object_validators/types/recipients_test.exs
index f278f039b..053916bdd 100644
--- a/test/web/activity_pub/object_validators/types/recipients_test.exs
+++ b/test/web/activity_pub/object_validators/types/recipients_test.exs
@@ -1,5 +1,5 @@
defmodule Pleroma.Web.ObjectValidators.Types.RecipientsTest do
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients
use Pleroma.DataCase
test "it asserts that all elements of the list are object ids" do
diff --git a/test/web/activity_pub/object_validators/types/safe_text_test.exs b/test/web/activity_pub/object_validators/types/safe_text_test.exs
new file mode 100644
index 000000000..9c08606f6
--- /dev/null
+++ b/test/web/activity_pub/object_validators/types/safe_text_test.exs
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.SafeTextTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.SafeText
+
+ test "it lets normal text go through" do
+ text = "hey how are you"
+ assert {:ok, text} == SafeText.cast(text)
+ end
+
+ test "it removes html tags from text" do
+ text = "hey look xss <script>alert('foo')</script>"
+ assert {:ok, "hey look xss alert(&#39;foo&#39;)"} == SafeText.cast(text)
+ end
+
+ test "it keeps basic html tags" do
+ text = "hey <a href='http://gensokyo.2hu'>look</a> xss <script>alert('foo')</script>"
+
+ assert {:ok, "hey <a href=\"http://gensokyo.2hu\">look</a> xss alert(&#39;foo&#39;)"} ==
+ SafeText.cast(text)
+ end
+
+ test "errors for non-text" do
+ assert :error == SafeText.cast(1)
+ end
+end
diff --git a/test/web/activity_pub/pipeline_test.exs b/test/web/activity_pub/pipeline_test.exs
index 26557720b..8deb64501 100644
--- a/test/web/activity_pub/pipeline_test.exs
+++ b/test/web/activity_pub/pipeline_test.exs
@@ -33,7 +33,10 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
{
Pleroma.Web.ActivityPub.SideEffects,
[],
- [handle: fn o, m -> {:ok, o, m} end]
+ [
+ handle: fn o, m -> {:ok, o, m} end,
+ handle_after_transaction: fn m -> m end
+ ]
},
{
Pleroma.Web.Federator,
@@ -71,7 +74,7 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
{
Pleroma.Web.ActivityPub.SideEffects,
[],
- [handle: fn o, m -> {:ok, o, m} end]
+ [handle: fn o, m -> {:ok, o, m} end, handle_after_transaction: fn m -> m end]
},
{
Pleroma.Web.Federator,
@@ -110,7 +113,7 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do
{
Pleroma.Web.ActivityPub.SideEffects,
[],
- [handle: fn o, m -> {:ok, o, m} end]
+ [handle: fn o, m -> {:ok, o, m} end, handle_after_transaction: fn m -> m end]
},
{
Pleroma.Web.Federator,
diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs
index a80104ea7..6bbbaae87 100644
--- a/test/web/activity_pub/side_effects_test.exs
+++ b/test/web/activity_pub/side_effects_test.exs
@@ -7,6 +7,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
use Pleroma.DataCase
alias Pleroma.Activity
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
@@ -20,6 +22,48 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
import Pleroma.Factory
import Mock
+ describe "handle_after_transaction" do
+ test "it streams out notifications and streams" do
+ author = insert(:user, local: true)
+ recipient = insert(:user, local: true)
+
+ {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
+
+ {:ok, create_activity_data, _meta} =
+ Builder.create(author, chat_message_data["id"], [recipient.ap_id])
+
+ {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
+
+ {:ok, _create_activity, meta} =
+ SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
+
+ assert [notification] = meta[:notifications]
+
+ with_mocks([
+ {
+ Pleroma.Web.Streamer,
+ [],
+ [
+ stream: fn _, _ -> nil end
+ ]
+ },
+ {
+ Pleroma.Web.Push,
+ [],
+ [
+ send: fn _ -> nil end
+ ]
+ }
+ ]) do
+ SideEffects.handle_after_transaction(meta)
+
+ assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
+ assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
+ assert called(Pleroma.Web.Push.send(notification))
+ end
+ end
+ end
+
describe "delete objects" do
setup do
user = insert(:user)
@@ -290,6 +334,147 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
end
end
+ describe "creation of ChatMessages" do
+ test "notifies the recipient" do
+ author = insert(:user, local: false)
+ recipient = insert(:user, local: true)
+
+ {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
+
+ {:ok, create_activity_data, _meta} =
+ Builder.create(author, chat_message_data["id"], [recipient.ap_id])
+
+ {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
+
+ {:ok, _create_activity, _meta} =
+ SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
+
+ assert Repo.get_by(Notification, user_id: recipient.id, activity_id: create_activity.id)
+ end
+
+ test "it streams the created ChatMessage" do
+ author = insert(:user, local: true)
+ recipient = insert(:user, local: true)
+
+ {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
+
+ {:ok, create_activity_data, _meta} =
+ Builder.create(author, chat_message_data["id"], [recipient.ap_id])
+
+ {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
+
+ {:ok, _create_activity, meta} =
+ SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
+
+ assert [_, _] = meta[:streamables]
+ end
+
+ test "it creates a Chat and MessageReferences for the local users and bumps the unread count, except for the author" do
+ author = insert(:user, local: true)
+ recipient = insert(:user, local: true)
+
+ {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
+
+ {:ok, create_activity_data, _meta} =
+ Builder.create(author, chat_message_data["id"], [recipient.ap_id])
+
+ {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
+
+ with_mocks([
+ {
+ Pleroma.Web.Streamer,
+ [],
+ [
+ stream: fn _, _ -> nil end
+ ]
+ },
+ {
+ Pleroma.Web.Push,
+ [],
+ [
+ send: fn _ -> nil end
+ ]
+ }
+ ]) do
+ {:ok, _create_activity, meta} =
+ SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
+
+ # The notification gets created
+ assert [notification] = meta[:notifications]
+ assert notification.activity_id == create_activity.id
+
+ # But it is not sent out
+ refute called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
+ refute called(Pleroma.Web.Push.send(notification))
+
+ # Same for the user chat stream
+ assert [{topics, _}, _] = meta[:streamables]
+ assert topics == ["user", "user:pleroma_chat"]
+ refute called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
+
+ chat = Chat.get(author.id, recipient.ap_id)
+
+ [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
+
+ assert cm_ref.object.data["content"] == "hey"
+ assert cm_ref.unread == false
+
+ chat = Chat.get(recipient.id, author.ap_id)
+
+ [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
+
+ assert cm_ref.object.data["content"] == "hey"
+ assert cm_ref.unread == true
+ end
+ end
+
+ test "it creates a Chat for the local users and bumps the unread count" do
+ author = insert(:user, local: false)
+ recipient = insert(:user, local: true)
+
+ {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
+
+ {:ok, create_activity_data, _meta} =
+ Builder.create(author, chat_message_data["id"], [recipient.ap_id])
+
+ {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
+
+ {:ok, _create_activity, _meta} =
+ SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
+
+ # An object is created
+ assert Object.get_by_ap_id(chat_message_data["id"])
+
+ # The remote user won't get a chat
+ chat = Chat.get(author.id, recipient.ap_id)
+ refute chat
+
+ # The local user will get a chat
+ chat = Chat.get(recipient.id, author.ap_id)
+ assert chat
+
+ author = insert(:user, local: true)
+ recipient = insert(:user, local: true)
+
+ {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
+
+ {:ok, create_activity_data, _meta} =
+ Builder.create(author, chat_message_data["id"], [recipient.ap_id])
+
+ {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
+
+ {:ok, _create_activity, _meta} =
+ SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
+
+ # Both users are local and get the chat
+ chat = Chat.get(author.id, recipient.ap_id)
+ assert chat
+
+ chat = Chat.get(recipient.id, author.ap_id)
+ assert chat
+ end
+ end
+
describe "announce objects" do
setup do
poster = insert(:user)
diff --git a/test/web/activity_pub/transmogrifier/chat_message_test.exs b/test/web/activity_pub/transmogrifier/chat_message_test.exs
new file mode 100644
index 000000000..d6736dc3e
--- /dev/null
+++ b/test/web/activity_pub/transmogrifier/chat_message_test.exs
@@ -0,0 +1,153 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Transmogrifier.ChatMessageTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Activity
+ alias Pleroma.Chat
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ describe "handle_incoming" do
+ test "handles chonks with attachment" do
+ data = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "actor" => "https://honk.tedunangst.com/u/tedu",
+ "id" => "https://honk.tedunangst.com/u/tedu/honk/x6gt8X8PcyGkQcXxzg1T",
+ "object" => %{
+ "attachment" => [
+ %{
+ "mediaType" => "image/jpeg",
+ "name" => "298p3RG7j27tfsZ9RQ.jpg",
+ "summary" => "298p3RG7j27tfsZ9RQ.jpg",
+ "type" => "Document",
+ "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
+ }
+ ],
+ "attributedTo" => "https://honk.tedunangst.com/u/tedu",
+ "content" => "",
+ "id" => "https://honk.tedunangst.com/u/tedu/chonk/26L4wl5yCbn4dr4y1b",
+ "published" => "2020-05-18T01:13:03Z",
+ "to" => [
+ "https://dontbulling.me/users/lain"
+ ],
+ "type" => "ChatMessage"
+ },
+ "published" => "2020-05-18T01:13:03Z",
+ "to" => [
+ "https://dontbulling.me/users/lain"
+ ],
+ "type" => "Create"
+ }
+
+ _user = insert(:user, ap_id: data["actor"])
+ _user = insert(:user, ap_id: hd(data["to"]))
+
+ assert {:ok, _activity} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it rejects messages that don't contain content" do
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Poison.decode!()
+
+ object =
+ data["object"]
+ |> Map.delete("content")
+
+ data =
+ data
+ |> Map.put("object", object)
+
+ _author =
+ insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now())
+
+ _recipient =
+ insert(:user,
+ ap_id: List.first(data["to"]),
+ local: true,
+ last_refreshed_at: DateTime.utc_now()
+ )
+
+ {:error, _} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it rejects messages that don't concern local users" do
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Poison.decode!()
+
+ _author =
+ insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now())
+
+ _recipient =
+ insert(:user,
+ ap_id: List.first(data["to"]),
+ local: false,
+ last_refreshed_at: DateTime.utc_now()
+ )
+
+ {:error, _} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it rejects messages where the `to` field of activity and object don't match" do
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Poison.decode!()
+
+ author = insert(:user, ap_id: data["actor"])
+ _recipient = insert(:user, ap_id: List.first(data["to"]))
+
+ data =
+ data
+ |> Map.put("to", author.ap_id)
+
+ assert match?({:error, _}, Transmogrifier.handle_incoming(data))
+ refute Object.get_by_ap_id(data["object"]["id"])
+ end
+
+ test "it fetches the actor if they aren't in our system" do
+ Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Poison.decode!()
+ |> Map.put("actor", "http://mastodon.example.org/users/admin")
+ |> put_in(["object", "actor"], "http://mastodon.example.org/users/admin")
+
+ _recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
+
+ {:ok, %Activity{} = _activity} = Transmogrifier.handle_incoming(data)
+ end
+
+ test "it inserts it and creates a chat" do
+ data =
+ File.read!("test/fixtures/create-chat-message.json")
+ |> Poison.decode!()
+
+ author =
+ insert(:user, ap_id: data["actor"], local: false, last_refreshed_at: DateTime.utc_now())
+
+ recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
+
+ {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)
+ assert activity.local == false
+
+ assert activity.actor == author.ap_id
+ assert activity.recipients == [recipient.ap_id, author.ap_id]
+
+ %Object{} = object = Object.get_by_ap_id(activity.data["object"])
+
+ assert object
+ assert object.data["content"] == "You expected a cute girl? Too bad. alert(&#39;XSS&#39;)"
+ assert match?(%{"firefox" => _}, object.data["emoji"])
+
+ refute Chat.get(author.id, recipient.ap_id)
+ assert Chat.get(recipient.id, author.ap_id)
+ end
+ end
+end
diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/web/activity_pub/transmogrifier/follow_handling_test.exs
index 967389fae..06c39eed6 100644
--- a/test/web/activity_pub/transmogrifier/follow_handling_test.exs
+++ b/test/web/activity_pub/transmogrifier/follow_handling_test.exs
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
use Pleroma.DataCase
alias Pleroma.Activity
+ alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
@@ -12,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
import Pleroma.Factory
import Ecto.Query
+ import Mock
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -57,9 +59,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
activity = Repo.get(Activity, activity.id)
assert activity.data["state"] == "accept"
assert User.following?(User.get_cached_by_ap_id(data["actor"]), user)
+
+ [notification] = Notification.for_user(user)
+ assert notification.type == "follow"
end
- test "with locked accounts, it does not create a follow or an accept" do
+ test "with locked accounts, it does create a Follow, but not an Accept" do
user = insert(:user, locked: true)
data =
@@ -81,6 +86,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
|> Repo.all()
assert Enum.empty?(accepts)
+
+ [notification] = Notification.for_user(user)
+ assert notification.type == "follow_request"
end
test "it works for follow requests when you are already followed, creating a new accept activity" do
@@ -144,6 +152,23 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
assert activity.data["state"] == "reject"
end
+ test "it rejects incoming follow requests if the following errors for some reason" do
+ user = insert(:user)
+
+ data =
+ File.read!("test/fixtures/mastodon-follow-activity.json")
+ |> Poison.decode!()
+ |> Map.put("object", user.ap_id)
+
+ with_mock Pleroma.User, [:passthrough], follow: fn _, _ -> {:error, :testing} end do
+ {:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data)
+
+ %Activity{} = activity = Activity.get_by_ap_id(id)
+
+ assert activity.data["state"] == "reject"
+ end
+ end
+
test "it works for incoming follow requests from hubzilla" do
user = insert(:user)
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index 94d8552e8..47d6e843a 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -1571,9 +1571,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
- assert modified_object["conversation"] ==
- "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
-
assert modified_object["context"] ==
"tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
end
diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs
index c3bcbd823..48fb108ec 100644
--- a/test/web/admin_api/controllers/admin_api_controller_test.exs
+++ b/test/web/admin_api/controllers/admin_api_controller_test.exs
@@ -337,7 +337,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
assert expected == json_response(conn, 200)
@@ -614,7 +615,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => admin.ap_id
},
%{
"deactivated" => user.deactivated,
@@ -625,7 +627,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => ["foo", "bar"],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
]
|> Enum.sort_by(& &1["nickname"])
@@ -697,7 +700,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
]
}
@@ -722,7 +726,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
]
}
@@ -747,7 +752,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
]
}
@@ -772,7 +778,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
]
}
@@ -797,7 +804,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
]
}
@@ -822,7 +830,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
]
}
@@ -842,7 +851,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user2) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user2.name || user2.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user2.ap_id
}
]
}
@@ -874,7 +884,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
]
}
@@ -899,7 +910,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
},
%{
"deactivated" => admin.deactivated,
@@ -910,7 +922,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => admin.ap_id
},
%{
"deactivated" => false,
@@ -921,7 +934,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => old_admin.ap_id
}
]
|> Enum.sort_by(& &1["nickname"])
@@ -951,7 +965,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => admin.ap_id
},
%{
"deactivated" => false,
@@ -962,7 +977,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => second_admin.ap_id
}
]
|> Enum.sort_by(& &1["nickname"])
@@ -994,7 +1010,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(moderator.name || moderator.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => moderator.ap_id
}
]
}
@@ -1019,7 +1036,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => ["first"],
"avatar" => User.avatar_url(user1) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user1.name || user1.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user1.ap_id
},
%{
"deactivated" => false,
@@ -1030,7 +1048,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => ["second"],
"avatar" => User.avatar_url(user2) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user2.name || user2.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user2.ap_id
}
]
|> Enum.sort_by(& &1["nickname"])
@@ -1069,7 +1088,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
]
}
@@ -1093,7 +1113,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => admin.ap_id
}
]
}
@@ -1155,7 +1176,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
+ "confirmation_pending" => false,
+ "url" => user.ap_id
}
log_entry = Repo.one(ModerationLog)
@@ -1577,14 +1599,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
"actor_type" => "Application"
})
- |> json_response(200) == %{"errors" => %{"actor_type" => "is invalid"}}
+ |> json_response(400) == %{"errors" => %{"actor_type" => "is invalid"}}
end
test "update non existing user", %{conn: conn} do
assert patch(conn, "/api/pleroma/admin/users/non-existing/credentials", %{
"password" => "new_password"
})
- |> json_response(200) == %{"error" => "Unable to update user."}
+ |> json_response(404) == %{"error" => "Not found"}
end
end
diff --git a/test/web/admin_api/controllers/config_controller_test.exs b/test/web/admin_api/controllers/config_controller_test.exs
index 780de8d18..064ef9bc7 100644
--- a/test/web/admin_api/controllers/config_controller_test.exs
+++ b/test/web/admin_api/controllers/config_controller_test.exs
@@ -57,12 +57,12 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
]
} = json_response_and_validate_schema(conn, 200)
- assert key1 == config1.key
- assert key2 == config2.key
+ assert key1 == inspect(config1.key)
+ assert key2 == inspect(config2.key)
end
test "db is added to settings that are in db", %{conn: conn} do
- _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name"))
+ _config = insert(:config, key: ":instance", value: [name: "Some name"])
%{"configs" => configs} =
conn
@@ -83,7 +83,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
config3 =
insert(:config,
- value: ConfigDB.to_binary(k1: :v1, k2: :v2)
+ value: [k1: :v1, k2: :v2]
)
%{"configs" => configs} =
@@ -93,42 +93,45 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
assert length(configs) > 3
+ saved_configs = [config1, config2, config3]
+ keys = Enum.map(saved_configs, &inspect(&1.key))
+
received_configs =
Enum.filter(configs, fn %{"group" => group, "key" => key} ->
- group == ":pleroma" and key in [config1.key, config2.key, config3.key]
+ group == ":pleroma" and key in keys
end)
assert length(received_configs) == 3
db_keys =
config3.value
- |> ConfigDB.from_binary()
|> Keyword.keys()
- |> ConfigDB.convert()
+ |> ConfigDB.to_json_types()
+
+ keys = Enum.map(saved_configs -- [config3], &inspect(&1.key))
+
+ values = Enum.map(saved_configs, &ConfigDB.to_json_types(&1.value))
+
+ mapset_keys = MapSet.new(keys ++ db_keys)
Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
- assert db in [[config1.key], [config2.key], db_keys]
+ db = MapSet.new(db)
+ assert MapSet.subset?(db, mapset_keys)
- assert value in [
- ConfigDB.from_binary_with_convert(config1.value),
- ConfigDB.from_binary_with_convert(config2.value),
- ConfigDB.from_binary_with_convert(config3.value)
- ]
+ assert value in values
end)
end
test "subkeys with full update right merge", %{conn: conn} do
- config1 =
- insert(:config,
- key: ":emoji",
- value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
- )
+ insert(:config,
+ key: ":emoji",
+ value: [groups: [a: 1, b: 2], key: [a: 1]]
+ )
- config2 =
- insert(:config,
- key: ":assets",
- value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
- )
+ insert(:config,
+ key: ":assets",
+ value: [mascots: [a: 1, b: 2], key: [a: 1]]
+ )
%{"configs" => configs} =
conn
@@ -137,14 +140,14 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
vals =
Enum.filter(configs, fn %{"group" => group, "key" => key} ->
- group == ":pleroma" and key in [config1.key, config2.key]
+ group == ":pleroma" and key in [":emoji", ":assets"]
end)
emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
- emoji_val = ConfigDB.transform_with_out_binary(emoji["value"])
- assets_val = ConfigDB.transform_with_out_binary(assets["value"])
+ emoji_val = ConfigDB.to_elixir_types(emoji["value"])
+ assets_val = ConfigDB.to_elixir_types(assets["value"])
assert emoji_val[:groups] == [a: 1, b: 2]
assert assets_val[:mascots] == [a: 1, b: 2]
@@ -277,7 +280,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
"value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
"db" => [":key5"]
}
- ]
+ ],
+ "need_reboot" => false
}
assert Application.get_env(:pleroma, :key1) == "value1"
@@ -357,7 +361,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
"value" => "https://hooks.slack.com/services/KEY",
"db" => [":webhook_url"]
}
- ]
+ ],
+ "need_reboot" => false
}
assert Application.get_env(:quack, :level) == :info
@@ -366,14 +371,14 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
end
test "saving config with partial update", %{conn: conn} do
- config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
+ insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
- %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]}
+ %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
]
})
@@ -389,7 +394,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
],
"db" => [":key1", ":key2", ":key3"]
}
- ]
+ ],
+ "need_reboot" => false
}
end
@@ -500,8 +506,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
end
test "saving config with nested merge", %{conn: conn} do
- config =
- insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
+ insert(:config, key: :key1, value: [key1: 1, key2: [k1: 1, k2: 2]])
conn =
conn
@@ -509,8 +514,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|> post("/api/pleroma/admin/config", %{
configs: [
%{
- group: config.group,
- key: config.key,
+ group: ":pleroma",
+ key: ":key1",
value: [
%{"tuple" => [":key3", 3]},
%{
@@ -548,7 +553,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
],
"db" => [":key1", ":key3", ":key2"]
}
- ]
+ ],
+ "need_reboot" => false
}
end
@@ -588,7 +594,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
],
"db" => [":ssl_options"]
}
- ]
+ ],
+ "need_reboot" => false
}
assert Application.get_env(:pleroma, :key1) == [
@@ -600,12 +607,11 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
backends = Application.get_env(:logger, :backends)
on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
- config =
- insert(:config,
- group: ":logger",
- key: ":backends",
- value: :erlang.term_to_binary([])
- )
+ insert(:config,
+ group: :logger,
+ key: :backends,
+ value: []
+ )
Pleroma.Config.TransferTask.load_and_update_env([], false)
@@ -617,8 +623,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|> post("/api/pleroma/admin/config", %{
configs: [
%{
- group: config.group,
- key: config.key,
+ group: ":logger",
+ key: ":backends",
value: [":console"]
}
]
@@ -634,7 +640,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
],
"db" => [":backends"]
}
- ]
+ ],
+ "need_reboot" => false
}
assert Application.get_env(:logger, :backends) == [
@@ -643,19 +650,18 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
end
test "saving full setting if value is not keyword", %{conn: conn} do
- config =
- insert(:config,
- group: ":tesla",
- key: ":adapter",
- value: :erlang.term_to_binary(Tesla.Adapter.Hackey)
- )
+ insert(:config,
+ group: :tesla,
+ key: :adapter,
+ value: Tesla.Adapter.Hackey
+ )
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
- %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"}
+ %{group: ":tesla", key: ":adapter", value: "Tesla.Adapter.Httpc"}
]
})
@@ -667,7 +673,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
"value" => "Tesla.Adapter.Httpc",
"db" => [":adapter"]
}
- ]
+ ],
+ "need_reboot" => false
}
end
@@ -677,13 +684,13 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
token: token
} do
ueberauth = Application.get_env(:ueberauth, Ueberauth)
- config1 = insert(:config, key: ":keyaa1")
- config2 = insert(:config, key: ":keyaa2")
+ insert(:config, key: :keyaa1)
+ insert(:config, key: :keyaa2)
config3 =
insert(:config,
- group: ":ueberauth",
- key: "Ueberauth"
+ group: :ueberauth,
+ key: Ueberauth
)
conn =
@@ -691,8 +698,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
- %{group: config1.group, key: config1.key, value: "another_value"},
- %{group: config2.group, key: config2.key, value: "another_value"}
+ %{group: ":pleroma", key: ":keyaa1", value: "another_value"},
+ %{group: ":pleroma", key: ":keyaa2", value: "another_value"}
]
})
@@ -700,22 +707,23 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
"configs" => [
%{
"group" => ":pleroma",
- "key" => config1.key,
+ "key" => ":keyaa1",
"value" => "another_value",
"db" => [":keyaa1"]
},
%{
"group" => ":pleroma",
- "key" => config2.key,
+ "key" => ":keyaa2",
"value" => "another_value",
"db" => [":keyaa2"]
}
- ]
+ ],
+ "need_reboot" => false
}
assert Application.get_env(:pleroma, :keyaa1) == "another_value"
assert Application.get_env(:pleroma, :keyaa2) == "another_value"
- assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value)
+ assert Application.get_env(:ueberauth, Ueberauth) == config3.value
conn =
build_conn()
@@ -724,7 +732,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/config", %{
configs: [
- %{group: config2.group, key: config2.key, delete: true},
+ %{group: ":pleroma", key: ":keyaa2", delete: true},
%{
group: ":ueberauth",
key: "Ueberauth",
@@ -734,7 +742,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
})
assert json_response_and_validate_schema(conn, 200) == %{
- "configs" => []
+ "configs" => [],
+ "need_reboot" => false
}
assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
@@ -801,7 +810,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
":name"
]
}
- ]
+ ],
+ "need_reboot" => false
}
end
@@ -935,7 +945,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
],
"db" => [":http"]
}
- ]
+ ],
+ "need_reboot" => false
}
end
@@ -1000,7 +1011,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
],
"db" => [":key2", ":key3"]
}
- ]
+ ],
+ "need_reboot" => false
}
end
@@ -1027,7 +1039,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
"value" => %{"key" => "some_val"},
"db" => [":key1"]
}
- ]
+ ],
+ "need_reboot" => false
}
end
@@ -1077,16 +1090,16 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
":background"
]
}
- ]
+ ],
+ "need_reboot" => false
}
end
test "delete part of settings by atom subkeys", %{conn: conn} do
- config =
- insert(:config,
- key: ":keyaa1",
- value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
- )
+ insert(:config,
+ key: :keyaa1,
+ value: [subkey1: "val1", subkey2: "val2", subkey3: "val3"]
+ )
conn =
conn
@@ -1094,8 +1107,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|> post("/api/pleroma/admin/config", %{
configs: [
%{
- group: config.group,
- key: config.key,
+ group: ":pleroma",
+ key: ":keyaa1",
subkeys: [":subkey1", ":subkey3"],
delete: true
}
@@ -1110,7 +1123,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
"value" => [%{"tuple" => [":subkey2", "val2"]}],
"db" => [":subkey2"]
}
- ]
+ ],
+ "need_reboot" => false
}
end
@@ -1236,6 +1250,90 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
assert Application.get_env(:not_real, :anything) == "value6"
end
+
+ test "args for Pleroma.Upload.Filter.Mogrify with custom tuples", %{conn: conn} do
+ clear_config(Pleroma.Upload.Filter.Mogrify)
+
+ assert conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: ":pleroma",
+ key: "Pleroma.Upload.Filter.Mogrify",
+ value: [
+ %{"tuple" => [":args", ["auto-orient", "strip"]]}
+ ]
+ }
+ ]
+ })
+ |> json_response_and_validate_schema(200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => "Pleroma.Upload.Filter.Mogrify",
+ "value" => [
+ %{"tuple" => [":args", ["auto-orient", "strip"]]}
+ ],
+ "db" => [":args"]
+ }
+ ],
+ "need_reboot" => false
+ }
+
+ assert Config.get(Pleroma.Upload.Filter.Mogrify) == [args: ["auto-orient", "strip"]]
+
+ assert conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: ":pleroma",
+ key: "Pleroma.Upload.Filter.Mogrify",
+ value: [
+ %{
+ "tuple" => [
+ ":args",
+ [
+ "auto-orient",
+ "strip",
+ "{\"implode\", \"1\"}",
+ "{\"resize\", \"3840x1080>\"}"
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ })
+ |> json_response(200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => "Pleroma.Upload.Filter.Mogrify",
+ "value" => [
+ %{
+ "tuple" => [
+ ":args",
+ [
+ "auto-orient",
+ "strip",
+ "{\"implode\", \"1\"}",
+ "{\"resize\", \"3840x1080>\"}"
+ ]
+ ]
+ }
+ ],
+ "db" => [":args"]
+ }
+ ],
+ "need_reboot" => false
+ }
+
+ assert Config.get(Pleroma.Upload.Filter.Mogrify) == [
+ args: ["auto-orient", "strip", {"implode", "1"}, {"resize", "3840x1080>"}]
+ ]
+ end
end
describe "GET /api/pleroma/admin/config/descriptions" do
diff --git a/test/web/admin_api/controllers/media_proxy_cache_controller_test.exs b/test/web/admin_api/controllers/media_proxy_cache_controller_test.exs
new file mode 100644
index 000000000..5ab6cb78a
--- /dev/null
+++ b/test/web/admin_api/controllers/media_proxy_cache_controller_test.exs
@@ -0,0 +1,145 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.MediaProxyCacheControllerTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+ import Mock
+
+ alias Pleroma.Web.MediaProxy
+
+ setup do: clear_config([:media_proxy])
+
+ setup do
+ on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
+ end
+
+ setup do
+ admin = insert(:user, is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+
+ Config.put([:media_proxy, :enabled], true)
+ Config.put([:media_proxy, :invalidation, :enabled], true)
+ Config.put([:media_proxy, :invalidation, :provider], MediaProxy.Invalidation.Script)
+
+ {:ok, %{admin: admin, token: token, conn: conn}}
+ end
+
+ describe "GET /api/pleroma/admin/media_proxy_caches" do
+ test "shows banned MediaProxy URLs", %{conn: conn} do
+ MediaProxy.put_in_banned_urls([
+ "http://localhost:4001/media/a688346.jpg",
+ "http://localhost:4001/media/fb1f4d.jpg"
+ ])
+
+ MediaProxy.put_in_banned_urls("http://localhost:4001/media/gb1f44.jpg")
+ MediaProxy.put_in_banned_urls("http://localhost:4001/media/tb13f47.jpg")
+ MediaProxy.put_in_banned_urls("http://localhost:4001/media/wb1f46.jpg")
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/media_proxy_caches?page_size=2")
+ |> json_response_and_validate_schema(200)
+
+ assert response["urls"] == [
+ "http://localhost:4001/media/fb1f4d.jpg",
+ "http://localhost:4001/media/a688346.jpg"
+ ]
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/media_proxy_caches?page_size=2&page=2")
+ |> json_response_and_validate_schema(200)
+
+ assert response["urls"] == [
+ "http://localhost:4001/media/gb1f44.jpg",
+ "http://localhost:4001/media/tb13f47.jpg"
+ ]
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/media_proxy_caches?page_size=2&page=3")
+ |> json_response_and_validate_schema(200)
+
+ assert response["urls"] == ["http://localhost:4001/media/wb1f46.jpg"]
+ end
+ end
+
+ describe "POST /api/pleroma/admin/media_proxy_caches/delete" do
+ test "deleted MediaProxy URLs from banned", %{conn: conn} do
+ MediaProxy.put_in_banned_urls([
+ "http://localhost:4001/media/a688346.jpg",
+ "http://localhost:4001/media/fb1f4d.jpg"
+ ])
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/media_proxy_caches/delete", %{
+ urls: ["http://localhost:4001/media/a688346.jpg"]
+ })
+ |> json_response_and_validate_schema(200)
+
+ assert response["urls"] == ["http://localhost:4001/media/a688346.jpg"]
+ refute MediaProxy.in_banned_urls("http://localhost:4001/media/a688346.jpg")
+ assert MediaProxy.in_banned_urls("http://localhost:4001/media/fb1f4d.jpg")
+ end
+ end
+
+ describe "POST /api/pleroma/admin/media_proxy_caches/purge" do
+ test "perform invalidates cache of MediaProxy", %{conn: conn} do
+ urls = [
+ "http://example.com/media/a688346.jpg",
+ "http://example.com/media/fb1f4d.jpg"
+ ]
+
+ with_mocks [
+ {MediaProxy.Invalidation.Script, [],
+ [
+ purge: fn _, _ -> {"ok", 0} end
+ ]}
+ ] do
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/media_proxy_caches/purge", %{urls: urls, ban: false})
+ |> json_response_and_validate_schema(200)
+
+ assert response["urls"] == urls
+
+ refute MediaProxy.in_banned_urls("http://example.com/media/a688346.jpg")
+ refute MediaProxy.in_banned_urls("http://example.com/media/fb1f4d.jpg")
+ end
+ end
+
+ test "perform invalidates cache of MediaProxy and adds url to banned", %{conn: conn} do
+ urls = [
+ "http://example.com/media/a688346.jpg",
+ "http://example.com/media/fb1f4d.jpg"
+ ]
+
+ with_mocks [{MediaProxy.Invalidation.Script, [], [purge: fn _, _ -> {"ok", 0} end]}] do
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/media_proxy_caches/purge", %{
+ urls: urls,
+ ban: true
+ })
+ |> json_response_and_validate_schema(200)
+
+ assert response["urls"] == urls
+
+ assert MediaProxy.in_banned_urls("http://example.com/media/a688346.jpg")
+ assert MediaProxy.in_banned_urls("http://example.com/media/fb1f4d.jpg")
+ end
+ end
+ end
+end
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 2291f76dd..6bd26050e 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -5,7 +5,9 @@
defmodule Pleroma.Web.CommonAPITest do
use Pleroma.DataCase
alias Pleroma.Activity
+ alias Pleroma.Chat
alias Pleroma.Conversation.Participation
+ alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -23,6 +25,150 @@ defmodule Pleroma.Web.CommonAPITest do
setup do: clear_config([:instance, :limit])
setup do: clear_config([:instance, :max_pinned_statuses])
+ describe "posting chat messages" do
+ setup do: clear_config([:instance, :chat_limit])
+
+ test "it posts a chat message without content but with an attachment" do
+ author = insert(:user)
+ recipient = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
+
+ with_mocks([
+ {
+ Pleroma.Web.Streamer,
+ [],
+ [
+ stream: fn _, _ ->
+ nil
+ end
+ ]
+ },
+ {
+ Pleroma.Web.Push,
+ [],
+ [
+ send: fn _ -> nil end
+ ]
+ }
+ ]) do
+ {:ok, activity} =
+ CommonAPI.post_chat_message(
+ author,
+ recipient,
+ nil,
+ media_id: upload.id
+ )
+
+ notification =
+ Notification.for_user_and_activity(recipient, activity)
+ |> Repo.preload(:activity)
+
+ assert called(Pleroma.Web.Push.send(notification))
+ assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
+ assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
+
+ assert activity
+ end
+ end
+
+ test "it adds html newlines" do
+ author = insert(:user)
+ recipient = insert(:user)
+
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post_chat_message(
+ author,
+ recipient,
+ "uguu\nuguuu"
+ )
+
+ assert other_user.ap_id not in activity.recipients
+
+ object = Object.normalize(activity, false)
+
+ assert object.data["content"] == "uguu<br/>uguuu"
+ end
+
+ test "it linkifies" do
+ author = insert(:user)
+ recipient = insert(:user)
+
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post_chat_message(
+ author,
+ recipient,
+ "https://example.org is the site of @#{other_user.nickname} #2hu"
+ )
+
+ assert other_user.ap_id not in activity.recipients
+
+ object = Object.normalize(activity, false)
+
+ assert object.data["content"] ==
+ "<a href=\"https://example.org\" rel=\"ugc\">https://example.org</a> is the site of <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{
+ other_user.id
+ }\" href=\"#{other_user.ap_id}\" rel=\"ugc\">@<span>#{other_user.nickname}</span></a></span> <a class=\"hashtag\" data-tag=\"2hu\" href=\"http://localhost:4001/tag/2hu\">#2hu</a>"
+ end
+
+ test "it posts a chat message" do
+ author = insert(:user)
+ recipient = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post_chat_message(
+ author,
+ recipient,
+ "a test message <script>alert('uuu')</script> :firefox:"
+ )
+
+ assert activity.data["type"] == "Create"
+ assert activity.local
+ object = Object.normalize(activity)
+
+ assert object.data["type"] == "ChatMessage"
+ assert object.data["to"] == [recipient.ap_id]
+
+ assert object.data["content"] ==
+ "a test message &lt;script&gt;alert(&#39;uuu&#39;)&lt;/script&gt; :firefox:"
+
+ assert object.data["emoji"] == %{
+ "firefox" => "http://localhost:4001/emoji/Firefox.gif"
+ }
+
+ assert Chat.get(author.id, recipient.ap_id)
+ assert Chat.get(recipient.id, author.ap_id)
+
+ assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
+ end
+
+ test "it reject messages over the local limit" do
+ Pleroma.Config.put([:instance, :chat_limit], 2)
+
+ author = insert(:user)
+ recipient = insert(:user)
+
+ {:error, message} =
+ CommonAPI.post_chat_message(
+ author,
+ recipient,
+ "123"
+ )
+
+ assert message == :content_too_long
+ end
+ end
+
describe "unblocking" do
test "it works even without an existing block activity" do
blocked = insert(:user)
diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs
index de90aa6e0..592fdccd1 100644
--- a/test/web/federator_test.exs
+++ b/test/web/federator_test.exs
@@ -23,7 +23,7 @@ defmodule Pleroma.Web.FederatorTest do
setup_all do: clear_config([:instance, :federating], true)
setup do: clear_config([:instance, :allow_relay])
- setup do: clear_config([:instance, :rewrite_policy])
+ setup do: clear_config([:mrf, :policies])
setup do: clear_config([:mrf_keyword])
describe "Publish an activity" do
@@ -158,7 +158,7 @@ defmodule Pleroma.Web.FederatorTest do
Pleroma.Config.put([:mrf_keyword, :reject], ["lain"])
Pleroma.Config.put(
- [:instance, :rewrite_policy],
+ [:mrf, :policies],
Pleroma.Web.ActivityPub.MRF.KeywordPolicy
)
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index 7c420985d..f67d294ba 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -83,10 +83,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
test "updates the user's bio", %{conn: conn} do
user2 = insert(:user)
- conn =
- patch(conn, "/api/v1/accounts/update_credentials", %{
- "note" => "I drink #cofe with @#{user2.nickname}\n\nsuya.."
- })
+ raw_bio = "I drink #cofe with @#{user2.nickname}\n\nsuya.."
+
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{"note" => raw_bio})
assert user_data = json_response_and_validate_schema(conn, 200)
@@ -94,6 +93,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
user2.id
}" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
+
+ assert user_data["source"]["note"] == raw_bio
+
+ user = Repo.get(User, user_data["id"])
+
+ assert user.raw_bio == raw_bio
end
test "updates the user's locking status", %{conn: conn} do
@@ -395,4 +400,71 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
|> json_response_and_validate_schema(403)
end
end
+
+ describe "Mark account as bot" do
+ setup do: oauth_access(["write:accounts"])
+ setup :request_content_type
+
+ test "changing actor_type to Service makes account a bot", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Service"})
+ |> json_response_and_validate_schema(200)
+
+ assert account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Service"
+ end
+
+ test "changing actor_type to Person makes account a human", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Person"})
+ |> json_response_and_validate_schema(200)
+
+ refute account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Person"
+ end
+
+ test "changing actor_type to Application causes error", %{conn: conn} do
+ response =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Application"})
+ |> json_response_and_validate_schema(403)
+
+ assert %{"error" => "Invalid request"} == response
+ end
+
+ test "changing bot field to true changes actor_type to Service", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{bot: "true"})
+ |> json_response_and_validate_schema(200)
+
+ assert account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Service"
+ end
+
+ test "changing bot field to false changes actor_type to Person", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{bot: "false"})
+ |> json_response_and_validate_schema(200)
+
+ refute account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Person"
+ end
+
+ test "actor_type field has a higher priority than bot", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{
+ actor_type: "Person",
+ bot: "true"
+ })
+ |> json_response_and_validate_schema(200)
+
+ refute account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Person"
+ end
+ end
end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 1ce97378d..ebfcedd01 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -127,6 +127,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|> get("/api/v1/accounts/internal.fetch")
|> json_response_and_validate_schema(404)
end
+
+ test "returns 404 for deactivated user", %{conn: conn} do
+ user = insert(:user, deactivated: true)
+
+ assert %{"error" => "Can't find user"} =
+ conn
+ |> get("/api/v1/accounts/#{user.id}")
+ |> json_response_and_validate_schema(:not_found)
+ end
end
defp local_and_remote_users do
@@ -143,15 +152,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
- assert %{"error" => "Can't find user"} ==
+ assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{local.id}")
- |> json_response_and_validate_schema(:not_found)
+ |> json_response_and_validate_schema(:unauthorized)
- assert %{"error" => "Can't find user"} ==
+ assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{remote.id}")
- |> json_response_and_validate_schema(:not_found)
+ |> json_response_and_validate_schema(:unauthorized)
end
test "if user is authenticated", %{local: local, remote: remote} do
@@ -173,8 +182,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
res_conn = get(conn, "/api/v1/accounts/#{local.id}")
- assert json_response_and_validate_schema(res_conn, :not_found) == %{
- "error" => "Can't find user"
+ assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
+ "error" => "This API requires an authenticated user"
}
res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
@@ -203,8 +212,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
- assert json_response_and_validate_schema(res_conn, :not_found) == %{
- "error" => "Can't find user"
+ assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
+ "error" => "This API requires an authenticated user"
}
end
@@ -249,6 +258,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert id == announce.id
end
+ test "deactivated user", %{conn: conn} do
+ user = insert(:user, deactivated: true)
+
+ assert %{"error" => "Can't find user"} ==
+ conn
+ |> get("/api/v1/accounts/#{user.id}/statuses")
+ |> json_response_and_validate_schema(:not_found)
+ end
+
+ test "returns 404 when user is invisible", %{conn: conn} do
+ user = insert(:user, %{invisible: true})
+
+ assert %{"error" => "Can't find user"} =
+ conn
+ |> get("/api/v1/accounts/#{user.id}")
+ |> json_response_and_validate_schema(404)
+ end
+
test "respects blocks", %{user: user_one, conn: conn} do
user_two = insert(:user)
user_three = insert(:user)
@@ -350,9 +377,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert json_response_and_validate_schema(conn, 200) == []
end
- test "gets an users media", %{conn: conn} do
+ test "gets an users media, excludes reblogs", %{conn: conn} do
note = insert(:note_activity)
user = User.get_cached_by_ap_id(note.data["actor"])
+ other_user = insert(:user)
file = %Plug.Upload{
content_type: "image/jpg",
@@ -364,6 +392,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
{:ok, %{id: image_post_id}} = CommonAPI.post(user, %{status: "cofe", media_ids: [media_id]})
+ {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: other_user.ap_id)
+
+ {:ok, %{id: other_image_post_id}} =
+ CommonAPI.post(other_user, %{status: "cofe2", media_ids: [media_id]})
+
+ {:ok, _announce} = CommonAPI.repeat(other_image_post_id, user)
+
conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_media=true")
assert [%{"id" => ^image_post_id}] = json_response_and_validate_schema(conn, 200)
@@ -422,15 +457,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
- assert %{"error" => "Can't find user"} ==
+ assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{local.id}/statuses")
- |> json_response_and_validate_schema(:not_found)
+ |> json_response_and_validate_schema(:unauthorized)
- assert %{"error" => "Can't find user"} ==
+ assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{remote.id}/statuses")
- |> json_response_and_validate_schema(:not_found)
+ |> json_response_and_validate_schema(:unauthorized)
end
test "if user is authenticated", %{local: local, remote: remote} do
@@ -451,10 +486,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
- assert %{"error" => "Can't find user"} ==
+ assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{local.id}/statuses")
- |> json_response_and_validate_schema(:not_found)
+ |> json_response_and_validate_schema(:unauthorized)
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
@@ -481,10 +516,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
- assert %{"error" => "Can't find user"} ==
+ assert %{"error" => "This API requires an authenticated user"} ==
conn
|> get("/api/v1/accounts/#{remote.id}/statuses")
- |> json_response_and_validate_schema(:not_found)
+ |> json_response_and_validate_schema(:unauthorized)
end
test "if user is authenticated", %{local: local, remote: remote} do
diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs
index e278d61f5..70ef0e8b5 100644
--- a/test/web/mastodon_api/controllers/notification_controller_test.exs
+++ b/test/web/mastodon_api/controllers/notification_controller_test.exs
@@ -54,6 +54,27 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
assert response == expected_response
end
+ test "by default, does not contain pleroma:chat_mention" do
+ %{user: user, conn: conn} = oauth_access(["read:notifications"])
+ other_user = insert(:user)
+
+ {:ok, _activity} = CommonAPI.post_chat_message(other_user, user, "hey")
+
+ result =
+ conn
+ |> get("/api/v1/notifications")
+ |> json_response_and_validate_schema(200)
+
+ assert [] == result
+
+ result =
+ conn
+ |> get("/api/v1/notifications?include_types[]=pleroma:chat_mention")
+ |> json_response_and_validate_schema(200)
+
+ assert [_] = result
+ end
+
test "getting a single notification" do
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
@@ -292,6 +313,33 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
assert public_activity.id in activity_ids
refute unlisted_activity.id in activity_ids
end
+
+ test "doesn't return less than the requested amount of records when the user's reply is liked" do
+ user = insert(:user)
+ %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
+
+ {:ok, mention} =
+ CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "public"})
+
+ {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
+
+ {:ok, reply} =
+ CommonAPI.post(other_user, %{
+ status: ".",
+ visibility: "public",
+ in_reply_to_status_id: activity.id
+ })
+
+ {:ok, _favorite} = CommonAPI.favorite(user, reply.id)
+
+ activity_ids =
+ conn
+ |> get("/api/v1/notifications?exclude_visibilities[]=direct&limit=2")
+ |> json_response_and_validate_schema(200)
+ |> Enum.map(& &1["status"]["id"])
+
+ assert [reply.id, mention.id] == activity_ids
+ end
end
test "filters notifications using exclude_types" do
diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs
index 84d46895e..826f37fbc 100644
--- a/test/web/mastodon_api/controllers/search_controller_test.exs
+++ b/test/web/mastodon_api/controllers/search_controller_test.exs
@@ -111,6 +111,60 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
%{"name" => "prone", "url" => "#{Web.base_url()}/tag/prone"},
%{"name" => "AccidentProne", "url" => "#{Web.base_url()}/tag/AccidentProne"}
]
+
+ results =
+ conn
+ |> get("/api/v2/search?#{URI.encode_query(%{q: "https://shpposter.club/users/shpuld"})}")
+ |> json_response_and_validate_schema(200)
+
+ assert results["hashtags"] == [
+ %{"name" => "shpuld", "url" => "#{Web.base_url()}/tag/shpuld"}
+ ]
+
+ results =
+ conn
+ |> get(
+ "/api/v2/search?#{
+ URI.encode_query(%{
+ q:
+ "https://www.washingtonpost.com/sports/2020/06/10/" <>
+ "nascar-ban-display-confederate-flag-all-events-properties/"
+ })
+ }"
+ )
+ |> json_response_and_validate_schema(200)
+
+ assert results["hashtags"] == [
+ %{"name" => "nascar", "url" => "#{Web.base_url()}/tag/nascar"},
+ %{"name" => "ban", "url" => "#{Web.base_url()}/tag/ban"},
+ %{"name" => "display", "url" => "#{Web.base_url()}/tag/display"},
+ %{"name" => "confederate", "url" => "#{Web.base_url()}/tag/confederate"},
+ %{"name" => "flag", "url" => "#{Web.base_url()}/tag/flag"},
+ %{"name" => "all", "url" => "#{Web.base_url()}/tag/all"},
+ %{"name" => "events", "url" => "#{Web.base_url()}/tag/events"},
+ %{"name" => "properties", "url" => "#{Web.base_url()}/tag/properties"},
+ %{
+ "name" => "NascarBanDisplayConfederateFlagAllEventsProperties",
+ "url" =>
+ "#{Web.base_url()}/tag/NascarBanDisplayConfederateFlagAllEventsProperties"
+ }
+ ]
+ end
+
+ test "supports pagination of hashtags search results", %{conn: conn} do
+ results =
+ conn
+ |> get(
+ "/api/v2/search?#{
+ URI.encode_query(%{q: "#some #text #with #hashtags", limit: 2, offset: 1})
+ }"
+ )
+ |> json_response_and_validate_schema(200)
+
+ assert results["hashtags"] == [
+ %{"name" => "text", "url" => "#{Web.base_url()}/tag/text"},
+ %{"name" => "with", "url" => "#{Web.base_url()}/tag/with"}
+ ]
end
test "excludes a blocked users from search results", %{conn: conn} do
diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs
index 700c82e4f..a98e939e8 100644
--- a/test/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/web/mastodon_api/controllers/status_controller_test.exs
@@ -1541,14 +1541,49 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
} = response
end
+ test "favorites paginate correctly" do
+ %{user: user, conn: conn} = oauth_access(["read:favourites"])
+ other_user = insert(:user)
+ {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
+ {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
+ {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
+
+ {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
+ {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
+ {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
+
+ result =
+ conn
+ |> get("/api/v1/favourites?limit=1")
+
+ assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
+ assert post_id == second_post.id
+
+ # Using the header for pagination works correctly
+ [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
+ [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
+
+ assert max_id == third_favorite.id
+
+ result =
+ conn
+ |> get("/api/v1/favourites?max_id=#{max_id}")
+
+ assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
+ json_response_and_validate_schema(result, 200)
+
+ assert first_post_id == first_post.id
+ assert third_post_id == third_post.id
+ end
+
test "returns the favorites of a user" do
%{user: user, conn: conn} = oauth_access(["read:favourites"])
other_user = insert(:user)
{:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
- {:ok, activity} = CommonAPI.post(other_user, %{status: "traps are happy"})
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
- {:ok, _} = CommonAPI.favorite(user, activity.id)
+ {:ok, last_like} = CommonAPI.favorite(user, activity.id)
first_conn = get(conn, "/api/v1/favourites")
@@ -1566,9 +1601,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
{:ok, _} = CommonAPI.favorite(user, second_activity.id)
- last_like = status["id"]
-
- second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like}")
+ second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
assert [second_status] = json_response_and_validate_schema(second_conn, 200)
assert second_status["id"] == to_string(second_activity.id)
diff --git a/test/web/mastodon_api/controllers/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs
index 4aa260663..d36bb1ae8 100644
--- a/test/web/mastodon_api/controllers/subscription_controller_test.exs
+++ b/test/web/mastodon_api/controllers/subscription_controller_test.exs
@@ -58,7 +58,9 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
result =
conn
|> post("/api/v1/push/subscription", %{
- "data" => %{"alerts" => %{"mention" => true, "test" => true}},
+ "data" => %{
+ "alerts" => %{"mention" => true, "test" => true, "pleroma:chat_mention" => true}
+ },
"subscription" => @sub
})
|> json_response_and_validate_schema(200)
@@ -66,7 +68,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do
[subscription] = Pleroma.Repo.all(Subscription)
assert %{
- "alerts" => %{"mention" => true},
+ "alerts" => %{"mention" => true, "pleroma:chat_mention" => true},
"endpoint" => subscription.endpoint,
"id" => to_string(subscription.id),
"server_key" => @server_key
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index f91333e5c..80b1f734c 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -33,7 +33,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
bio:
"<script src=\"invalid-html\"></script><span>valid html</span>. a<br>b<br/>c<br >d<br />f '&<>\"",
inserted_at: ~N[2017-08-15 15:47:06.597036],
- emoji: %{"karjalanpiirakka" => "/file.png"}
+ emoji: %{"karjalanpiirakka" => "/file.png"},
+ raw_bio: "valid html. a\nb\nc\nd\nf '&<>\""
})
expected = %{
@@ -72,6 +73,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
fields: []
},
pleroma: %{
+ ap_id: user.ap_id,
background_image: "https://example.com/images/asuka_hospital.png",
confirmation_pending: false,
tags: [],
@@ -148,6 +150,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
fields: []
},
pleroma: %{
+ ap_id: user.ap_id,
background_image: nil,
confirmation_pending: false,
tags: [],
diff --git a/test/web/mastodon_api/views/conversation_view_test.exs b/test/web/mastodon_api/views/conversation_view_test.exs
index 6f84366f8..2e8203c9b 100644
--- a/test/web/mastodon_api/views/conversation_view_test.exs
+++ b/test/web/mastodon_api/views/conversation_view_test.exs
@@ -15,8 +15,17 @@ defmodule Pleroma.Web.MastodonAPI.ConversationViewTest do
user = insert(:user)
other_user = insert(:user)
+ {:ok, parent} = CommonAPI.post(user, %{status: "parent"})
+
{:ok, activity} =
- CommonAPI.post(user, %{status: "hey @#{other_user.nickname}", visibility: "direct"})
+ CommonAPI.post(user, %{
+ status: "hey @#{other_user.nickname}",
+ visibility: "direct",
+ in_reply_to_id: parent.id
+ })
+
+ {:ok, _reply_activity} =
+ CommonAPI.post(user, %{status: "hu", visibility: "public", in_reply_to_id: parent.id})
[participation] = Participation.for_user_with_last_activity_id(user)
diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs
index f15be1df1..8e0e58538 100644
--- a/test/web/mastodon_api/views/notification_view_test.exs
+++ b/test/web/mastodon_api/views/notification_view_test.exs
@@ -6,7 +6,10 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
use Pleroma.DataCase
alias Pleroma.Activity
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
alias Pleroma.Notification
+ alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
@@ -14,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
import Pleroma.Factory
defp test_notifications_rendering(notifications, user, expected_result) do
@@ -31,6 +35,30 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
assert expected_result == result
end
+ test "ChatMessage notification" do
+ user = insert(:user)
+ recipient = insert(:user)
+ {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "what's up my dude")
+
+ {:ok, [notification]} = Notification.create_notifications(activity)
+
+ object = Object.normalize(activity)
+ chat = Chat.get(recipient.id, user.ap_id)
+
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ expected = %{
+ id: to_string(notification.id),
+ pleroma: %{is_seen: false, is_muted: false},
+ type: "pleroma:chat_mention",
+ account: AccountView.render("show.json", %{user: user, for: recipient}),
+ chat_message: MessageReferenceView.render("show.json", %{chat_message_reference: cm_ref}),
+ created_at: Utils.to_masto_date(notification.inserted_at)
+ }
+
+ test_notifications_rendering([notification], recipient, [expected])
+ end
+
test "Mention notification" do
user = insert(:user)
mentioned_user = insert(:user)
@@ -40,7 +68,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
- pleroma: %{is_seen: false},
+ pleroma: %{is_seen: false, is_muted: false},
type: "mention",
account:
AccountView.render("show.json", %{
@@ -64,7 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
- pleroma: %{is_seen: false},
+ pleroma: %{is_seen: false, is_muted: false},
type: "favourite",
account: AccountView.render("show.json", %{user: another_user, for: user}),
status: StatusView.render("show.json", %{activity: create_activity, for: user}),
@@ -84,7 +112,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
- pleroma: %{is_seen: false},
+ pleroma: %{is_seen: false, is_muted: false},
type: "reblog",
account: AccountView.render("show.json", %{user: another_user, for: user}),
status: StatusView.render("show.json", %{activity: reblog_activity, for: user}),
@@ -102,7 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
- pleroma: %{is_seen: false},
+ pleroma: %{is_seen: false, is_muted: false},
type: "follow",
account: AccountView.render("show.json", %{user: follower, for: followed}),
created_at: Utils.to_masto_date(notification.inserted_at)
@@ -111,9 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
test_notifications_rendering([notification], followed, [expected])
User.perform(:delete, follower)
- notification = Notification |> Repo.one() |> Repo.preload(:activity)
-
- test_notifications_rendering([notification], followed, [])
+ refute Repo.one(Notification)
end
@tag capture_log: true
@@ -145,7 +171,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
- pleroma: %{is_seen: false},
+ pleroma: %{is_seen: false, is_muted: false},
type: "move",
account: AccountView.render("show.json", %{user: old_user, for: follower}),
target: AccountView.render("show.json", %{user: new_user, for: follower}),
@@ -170,7 +196,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
expected = %{
id: to_string(notification.id),
- pleroma: %{is_seen: false},
+ pleroma: %{is_seen: false, is_muted: false},
type: "pleroma:emoji_reaction",
emoji: "☕",
account: AccountView.render("show.json", %{user: other_user, for: user}),
@@ -180,4 +206,26 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
test_notifications_rendering([notification], user, [expected])
end
+
+ test "muted notification" do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ {:ok, _} = Pleroma.UserRelationship.create_mute(user, another_user)
+ {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"})
+ {:ok, favorite_activity} = CommonAPI.favorite(another_user, create_activity.id)
+ {:ok, [notification]} = Notification.create_notifications(favorite_activity)
+ create_activity = Activity.get_by_id(create_activity.id)
+
+ expected = %{
+ id: to_string(notification.id),
+ pleroma: %{is_seen: false, is_muted: true},
+ type: "favourite",
+ account: AccountView.render("show.json", %{user: another_user, for: user}),
+ status: StatusView.render("show.json", %{activity: create_activity, for: user}),
+ created_at: Utils.to_masto_date(notification.inserted_at)
+ }
+
+ test_notifications_rendering([notification], user, [expected])
+ end
end
diff --git a/test/web/media_proxy/invalidation_test.exs b/test/web/media_proxy/invalidation_test.exs
new file mode 100644
index 000000000..926ae74ca
--- /dev/null
+++ b/test/web/media_proxy/invalidation_test.exs
@@ -0,0 +1,64 @@
+defmodule Pleroma.Web.MediaProxy.InvalidationTest do
+ use ExUnit.Case
+ use Pleroma.Tests.Helpers
+
+ alias Pleroma.Config
+ alias Pleroma.Web.MediaProxy.Invalidation
+
+ import ExUnit.CaptureLog
+ import Mock
+ import Tesla.Mock
+
+ setup do: clear_config([:media_proxy])
+
+ setup do
+ on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
+ end
+
+ describe "Invalidation.Http" do
+ test "perform request to clear cache" do
+ Config.put([:media_proxy, :enabled], false)
+ Config.put([:media_proxy, :invalidation, :enabled], true)
+ Config.put([:media_proxy, :invalidation, :provider], Invalidation.Http)
+
+ Config.put([Invalidation.Http], method: :purge, headers: [{"x-refresh", 1}])
+ image_url = "http://example.com/media/example.jpg"
+ Pleroma.Web.MediaProxy.put_in_banned_urls(image_url)
+
+ mock(fn
+ %{
+ method: :purge,
+ url: "http://example.com/media/example.jpg",
+ headers: [{"x-refresh", 1}]
+ } ->
+ %Tesla.Env{status: 200}
+ end)
+
+ assert capture_log(fn ->
+ assert Pleroma.Web.MediaProxy.in_banned_urls(image_url)
+ assert Invalidation.purge([image_url]) == {:ok, [image_url]}
+ assert Pleroma.Web.MediaProxy.in_banned_urls(image_url)
+ end) =~ "Running cache purge: [\"#{image_url}\"]"
+ end
+ end
+
+ describe "Invalidation.Script" do
+ test "run script to clear cache" do
+ Config.put([:media_proxy, :enabled], false)
+ Config.put([:media_proxy, :invalidation, :enabled], true)
+ Config.put([:media_proxy, :invalidation, :provider], Invalidation.Script)
+ Config.put([Invalidation.Script], script_path: "purge-nginx")
+
+ image_url = "http://example.com/media/example.jpg"
+ Pleroma.Web.MediaProxy.put_in_banned_urls(image_url)
+
+ with_mocks [{System, [], [cmd: fn _, _ -> {"ok", 0} end]}] do
+ assert capture_log(fn ->
+ assert Pleroma.Web.MediaProxy.in_banned_urls(image_url)
+ assert Invalidation.purge([image_url]) == {:ok, [image_url]}
+ assert Pleroma.Web.MediaProxy.in_banned_urls(image_url)
+ end) =~ "Running cache purge: [\"#{image_url}\"]"
+ end
+ end
+ end
+end
diff --git a/test/web/media_proxy/invalidations/http_test.exs b/test/web/media_proxy/invalidations/http_test.exs
index 8a3b4141c..a1bef5237 100644
--- a/test/web/media_proxy/invalidations/http_test.exs
+++ b/test/web/media_proxy/invalidations/http_test.exs
@@ -5,6 +5,10 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.HttpTest do
import ExUnit.CaptureLog
import Tesla.Mock
+ setup do
+ on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
+ end
+
test "logs hasn't error message when request is valid" do
mock(fn
%{method: :purge, url: "http://example.com/media/example.jpg"} ->
@@ -14,8 +18,8 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.HttpTest do
refute capture_log(fn ->
assert Invalidation.Http.purge(
["http://example.com/media/example.jpg"],
- %{}
- ) == {:ok, "success"}
+ []
+ ) == {:ok, ["http://example.com/media/example.jpg"]}
end) =~ "Error while cache purge"
end
@@ -28,8 +32,8 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.HttpTest do
assert capture_log(fn ->
assert Invalidation.Http.purge(
["http://example.com/media/example1.jpg"],
- %{}
- ) == {:ok, "success"}
+ []
+ ) == {:ok, ["http://example.com/media/example1.jpg"]}
end) =~ "Error while cache purge: url - http://example.com/media/example1.jpg"
end
end
diff --git a/test/web/media_proxy/invalidations/script_test.exs b/test/web/media_proxy/invalidations/script_test.exs
index 1358963ab..51833ab18 100644
--- a/test/web/media_proxy/invalidations/script_test.exs
+++ b/test/web/media_proxy/invalidations/script_test.exs
@@ -4,17 +4,23 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.ScriptTest do
import ExUnit.CaptureLog
+ setup do
+ on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
+ end
+
test "it logger error when script not found" do
assert capture_log(fn ->
assert Invalidation.Script.purge(
["http://example.com/media/example.jpg"],
- %{script_path: "./example"}
- ) == {:error, "\"%ErlangError{original: :enoent}\""}
- end) =~ "Error while cache purge: \"%ErlangError{original: :enoent}\""
+ script_path: "./example"
+ ) == {:error, "%ErlangError{original: :enoent}"}
+ end) =~ "Error while cache purge: %ErlangError{original: :enoent}"
- assert Invalidation.Script.purge(
- ["http://example.com/media/example.jpg"],
- %{}
- ) == {:error, "not found script path"}
+ capture_log(fn ->
+ assert Invalidation.Script.purge(
+ ["http://example.com/media/example.jpg"],
+ []
+ ) == {:error, "\"not found script path\""}
+ end)
end
end
diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs
index da79d38a5..d61cef83b 100644
--- a/test/web/media_proxy/media_proxy_controller_test.exs
+++ b/test/web/media_proxy/media_proxy_controller_test.exs
@@ -10,6 +10,10 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
setup do: clear_config(:media_proxy)
setup do: clear_config([Pleroma.Web.Endpoint, :secret_key_base])
+ setup do
+ on_exit(fn -> Cachex.clear(:banned_urls_cache) end)
+ end
+
test "it returns 404 when MediaProxy disabled", %{conn: conn} do
Config.put([:media_proxy, :enabled], false)
@@ -66,4 +70,16 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
assert %Plug.Conn{status: :success} = get(conn, url)
end
end
+
+ test "it returns 404 when url contains in banned_urls cache", %{conn: conn} do
+ Config.put([:media_proxy, :enabled], true)
+ Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000")
+ url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png")
+ Pleroma.Web.MediaProxy.put_in_banned_urls("https://google.fn/test.png")
+
+ with_mock Pleroma.ReverseProxy,
+ call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do
+ assert %Plug.Conn{status: 404, resp_body: "Not Found"} = get(conn, url)
+ end
+ end
end
diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs
index 9bcc07b37..06b33607f 100644
--- a/test/web/node_info_test.exs
+++ b/test/web/node_info_test.exs
@@ -67,10 +67,10 @@ defmodule Pleroma.Web.NodeInfoTest do
end
test "returns fieldsLimits field", %{conn: conn} do
- Config.put([:instance, :max_account_fields], 10)
- Config.put([:instance, :max_remote_account_fields], 15)
- Config.put([:instance, :account_field_name_length], 255)
- Config.put([:instance, :account_field_value_length], 2048)
+ clear_config([:instance, :max_account_fields], 10)
+ clear_config([:instance, :max_remote_account_fields], 15)
+ clear_config([:instance, :account_field_name_length], 255)
+ clear_config([:instance, :account_field_value_length], 2048)
response =
conn
@@ -84,8 +84,7 @@ defmodule Pleroma.Web.NodeInfoTest do
end
test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do
- option = Config.get([:instance, :safe_dm_mentions])
- Config.put([:instance, :safe_dm_mentions], true)
+ clear_config([:instance, :safe_dm_mentions], true)
response =
conn
@@ -102,8 +101,6 @@ defmodule Pleroma.Web.NodeInfoTest do
|> json_response(:ok)
refute "safe_dm_mentions" in response["metadata"]["features"]
-
- Config.put([:instance, :safe_dm_mentions], option)
end
describe "`metadata/federation/enabled`" do
@@ -145,7 +142,8 @@ defmodule Pleroma.Web.NodeInfoTest do
"shareable_emoji_packs",
"multifetch",
"pleroma_emoji_reactions",
- "pleroma:api/v1/notifications:include_types_filter"
+ "pleroma:api/v1/notifications:include_types_filter",
+ "pleroma_chat_messages"
]
assert MapSet.subset?(
@@ -155,14 +153,11 @@ defmodule Pleroma.Web.NodeInfoTest do
end
test "it shows MRF transparency data if enabled", %{conn: conn} do
- config = Config.get([:instance, :rewrite_policy])
- Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
-
- option = Config.get([:instance, :mrf_transparency])
- Config.put([:instance, :mrf_transparency], true)
+ clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
+ clear_config([:mrf, :transparency], true)
simple_config = %{"reject" => ["example.com"]}
- Config.put(:mrf_simple, simple_config)
+ clear_config(:mrf_simple, simple_config)
response =
conn
@@ -170,26 +165,17 @@ defmodule Pleroma.Web.NodeInfoTest do
|> json_response(:ok)
assert response["metadata"]["federation"]["mrf_simple"] == simple_config
-
- Config.put([:instance, :rewrite_policy], config)
- Config.put([:instance, :mrf_transparency], option)
- Config.put(:mrf_simple, %{})
end
test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do
- config = Config.get([:instance, :rewrite_policy])
- Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
-
- option = Config.get([:instance, :mrf_transparency])
- Config.put([:instance, :mrf_transparency], true)
-
- exclusions = Config.get([:instance, :mrf_transparency_exclusions])
- Config.put([:instance, :mrf_transparency_exclusions], ["other.site"])
+ clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
+ clear_config([:mrf, :transparency], true)
+ clear_config([:mrf, :transparency_exclusions], ["other.site"])
simple_config = %{"reject" => ["example.com", "other.site"]}
- expected_config = %{"reject" => ["example.com"]}
+ clear_config(:mrf_simple, simple_config)
- Config.put(:mrf_simple, simple_config)
+ expected_config = %{"reject" => ["example.com"]}
response =
conn
@@ -198,10 +184,5 @@ defmodule Pleroma.Web.NodeInfoTest do
assert response["metadata"]["federation"]["mrf_simple"] == expected_config
assert response["metadata"]["federation"]["exclusions"] == true
-
- Config.put([:instance, :rewrite_policy], config)
- Config.put([:instance, :mrf_transparency], option)
- Config.put([:instance, :mrf_transparency_exclusions], exclusions)
- Config.put(:mrf_simple, %{})
end
end
diff --git a/test/web/pleroma_api/controllers/chat_controller_test.exs b/test/web/pleroma_api/controllers/chat_controller_test.exs
new file mode 100644
index 000000000..82e16741d
--- /dev/null
+++ b/test/web/pleroma_api/controllers/chat_controller_test.exs
@@ -0,0 +1,336 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "POST /api/v1/pleroma/chats/:id/messages/:message_id/read" do
+ setup do: oauth_access(["write:chats"])
+
+ test "it marks one message as read", %{conn: conn, user: user} do
+ other_user = insert(:user)
+
+ {:ok, create} = CommonAPI.post_chat_message(other_user, user, "sup")
+ {:ok, _create} = CommonAPI.post_chat_message(other_user, user, "sup part 2")
+ {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
+ object = Object.normalize(create, false)
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ assert cm_ref.unread == true
+
+ result =
+ conn
+ |> post("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}/read")
+ |> json_response_and_validate_schema(200)
+
+ assert result["unread"] == false
+
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ assert cm_ref.unread == false
+ end
+ end
+
+ describe "POST /api/v1/pleroma/chats/:id/read" do
+ setup do: oauth_access(["write:chats"])
+
+ test "given a `last_read_id`, it marks everything until then as read", %{
+ conn: conn,
+ user: user
+ } do
+ other_user = insert(:user)
+
+ {:ok, create} = CommonAPI.post_chat_message(other_user, user, "sup")
+ {:ok, _create} = CommonAPI.post_chat_message(other_user, user, "sup part 2")
+ {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
+ object = Object.normalize(create, false)
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ assert cm_ref.unread == true
+
+ result =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/chats/#{chat.id}/read", %{"last_read_id" => cm_ref.id})
+ |> json_response_and_validate_schema(200)
+
+ assert result["unread"] == 1
+
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ assert cm_ref.unread == false
+ end
+ end
+
+ describe "POST /api/v1/pleroma/chats/:id/messages" do
+ setup do: oauth_access(["write:chats"])
+
+ test "it posts a message to the chat", %{conn: conn, user: user} do
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
+
+ result =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{"content" => "Hallo!!"})
+ |> json_response_and_validate_schema(200)
+
+ assert result["content"] == "Hallo!!"
+ assert result["chat_id"] == chat.id |> to_string()
+ end
+
+ test "it fails if there is no content", %{conn: conn, user: user} do
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
+
+ result =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/chats/#{chat.id}/messages")
+ |> json_response_and_validate_schema(400)
+
+ assert result
+ end
+
+ test "it works with an attachment", %{conn: conn, user: user} do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
+
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
+
+ result =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{
+ "media_id" => to_string(upload.id)
+ })
+ |> json_response_and_validate_schema(200)
+
+ assert result["attachment"]
+ end
+ end
+
+ describe "DELETE /api/v1/pleroma/chats/:id/messages/:message_id" do
+ setup do: oauth_access(["write:chats"])
+
+ test "it deletes a message from the chat", %{conn: conn, user: user} do
+ recipient = insert(:user)
+
+ {:ok, message} =
+ CommonAPI.post_chat_message(user, recipient, "Hello darkness my old friend")
+
+ {:ok, other_message} = CommonAPI.post_chat_message(recipient, user, "nico nico ni")
+
+ object = Object.normalize(message, false)
+
+ chat = Chat.get(user.id, recipient.ap_id)
+
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ # Deleting your own message removes the message and the reference
+ result =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> delete("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}")
+ |> json_response_and_validate_schema(200)
+
+ assert result["id"] == cm_ref.id
+ refute MessageReference.get_by_id(cm_ref.id)
+ assert %{data: %{"type" => "Tombstone"}} = Object.get_by_id(object.id)
+
+ # Deleting other people's messages just removes the reference
+ object = Object.normalize(other_message, false)
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ result =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> delete("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}")
+ |> json_response_and_validate_schema(200)
+
+ assert result["id"] == cm_ref.id
+ refute MessageReference.get_by_id(cm_ref.id)
+ assert Object.get_by_id(object.id)
+ end
+ end
+
+ describe "GET /api/v1/pleroma/chats/:id/messages" do
+ setup do: oauth_access(["read:chats"])
+
+ test "it paginates", %{conn: conn, user: user} do
+ recipient = insert(:user)
+
+ Enum.each(1..30, fn _ ->
+ {:ok, _} = CommonAPI.post_chat_message(user, recipient, "hey")
+ end)
+
+ chat = Chat.get(user.id, recipient.ap_id)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats/#{chat.id}/messages")
+ |> json_response_and_validate_schema(200)
+
+ assert length(result) == 20
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}")
+ |> json_response_and_validate_schema(200)
+
+ assert length(result) == 10
+ end
+
+ test "it returns the messages for a given chat", %{conn: conn, user: user} do
+ other_user = insert(:user)
+ third_user = insert(:user)
+
+ {:ok, _} = CommonAPI.post_chat_message(user, other_user, "hey")
+ {:ok, _} = CommonAPI.post_chat_message(user, third_user, "hey")
+ {:ok, _} = CommonAPI.post_chat_message(user, other_user, "how are you?")
+ {:ok, _} = CommonAPI.post_chat_message(other_user, user, "fine, how about you?")
+
+ chat = Chat.get(user.id, other_user.ap_id)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats/#{chat.id}/messages")
+ |> json_response_and_validate_schema(200)
+
+ result
+ |> Enum.each(fn message ->
+ assert message["chat_id"] == chat.id |> to_string()
+ end)
+
+ assert length(result) == 3
+
+ # Trying to get the chat of a different user
+ result =
+ conn
+ |> assign(:user, other_user)
+ |> get("/api/v1/pleroma/chats/#{chat.id}/messages")
+
+ assert result |> json_response(404)
+ end
+ end
+
+ describe "POST /api/v1/pleroma/chats/by-account-id/:id" do
+ setup do: oauth_access(["write:chats"])
+
+ test "it creates or returns a chat", %{conn: conn} do
+ other_user = insert(:user)
+
+ result =
+ conn
+ |> post("/api/v1/pleroma/chats/by-account-id/#{other_user.id}")
+ |> json_response_and_validate_schema(200)
+
+ assert result["id"]
+ end
+ end
+
+ describe "GET /api/v1/pleroma/chats/:id" do
+ setup do: oauth_access(["read:chats"])
+
+ test "it returns a chat", %{conn: conn, user: user} do
+ other_user = insert(:user)
+
+ {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats/#{chat.id}")
+ |> json_response_and_validate_schema(200)
+
+ assert result["id"] == to_string(chat.id)
+ end
+ end
+
+ describe "GET /api/v1/pleroma/chats" do
+ setup do: oauth_access(["read:chats"])
+
+ test "it does not return chats with users you blocked", %{conn: conn, user: user} do
+ recipient = insert(:user)
+
+ {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats")
+ |> json_response_and_validate_schema(200)
+
+ assert length(result) == 1
+
+ User.block(user, recipient)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats")
+ |> json_response_and_validate_schema(200)
+
+ assert length(result) == 0
+ end
+
+ test "it returns all chats", %{conn: conn, user: user} do
+ Enum.each(1..30, fn _ ->
+ recipient = insert(:user)
+ {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
+ end)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats")
+ |> json_response_and_validate_schema(200)
+
+ assert length(result) == 30
+ end
+
+ test "it return a list of chats the current user is participating in, in descending order of updates",
+ %{conn: conn, user: user} do
+ har = insert(:user)
+ jafnhar = insert(:user)
+ tridi = insert(:user)
+
+ {:ok, chat_1} = Chat.get_or_create(user.id, har.ap_id)
+ :timer.sleep(1000)
+ {:ok, _chat_2} = Chat.get_or_create(user.id, jafnhar.ap_id)
+ :timer.sleep(1000)
+ {:ok, chat_3} = Chat.get_or_create(user.id, tridi.ap_id)
+ :timer.sleep(1000)
+
+ # bump the second one
+ {:ok, chat_2} = Chat.bump_or_create(user.id, jafnhar.ap_id)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats")
+ |> json_response_and_validate_schema(200)
+
+ ids = Enum.map(result, & &1["id"])
+
+ assert ids == [
+ chat_2.id |> to_string(),
+ chat_3.id |> to_string(),
+ chat_1.id |> to_string()
+ ]
+ end
+ end
+end
diff --git a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs b/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs
index ee3d281a0..df58a5eb6 100644
--- a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs
+++ b/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs
@@ -30,15 +30,55 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
test "GET /api/pleroma/emoji/packs", %{conn: conn} do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
- shared = resp["test_pack"]
- assert shared["files"] == %{"blank" => "blank.png"}
+ assert resp["count"] == 3
+
+ assert resp["packs"]
+ |> Map.keys()
+ |> length() == 3
+
+ shared = resp["packs"]["test_pack"]
+ assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"}
assert Map.has_key?(shared["pack"], "download-sha256")
assert shared["pack"]["can-download"]
assert shared["pack"]["share-files"]
- non_shared = resp["test_pack_nonshared"]
+ non_shared = resp["packs"]["test_pack_nonshared"]
assert non_shared["pack"]["share-files"] == false
assert non_shared["pack"]["can-download"] == false
+
+ resp =
+ conn
+ |> get("/api/pleroma/emoji/packs?page_size=1")
+ |> json_response_and_validate_schema(200)
+
+ assert resp["count"] == 3
+
+ packs = Map.keys(resp["packs"])
+
+ assert length(packs) == 1
+
+ [pack1] = packs
+
+ resp =
+ conn
+ |> get("/api/pleroma/emoji/packs?page_size=1&page=2")
+ |> json_response_and_validate_schema(200)
+
+ assert resp["count"] == 3
+ packs = Map.keys(resp["packs"])
+ assert length(packs) == 1
+ [pack2] = packs
+
+ resp =
+ conn
+ |> get("/api/pleroma/emoji/packs?page_size=1&page=3")
+ |> json_response_and_validate_schema(200)
+
+ assert resp["count"] == 3
+ packs = Map.keys(resp["packs"])
+ assert length(packs) == 1
+ [pack3] = packs
+ assert [pack1, pack2, pack3] |> Enum.uniq() |> length() == 3
end
describe "GET /api/pleroma/emoji/packs/remote" do
@@ -332,7 +372,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
Map.put(
new_data,
"fallback-src-sha256",
- "74409E2674DAA06C072729C6C8426C4CB3B7E0B85ED77792DB7A436E11D76DAF"
+ "1967BB4E42BCC34BCC12D57BE7811D3B7BE52F965BCE45C87BD377B9499CE11D"
)
assert ctx[:admin_conn]
@@ -398,7 +438,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{
- shortcode: "blank2",
+ shortcode: "blank3",
filename: "dir/blank.png",
file: %Plug.Upload{
filename: "blank.png",
@@ -407,7 +447,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
})
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
- "blank2" => "dir/blank.png"
+ "blank2" => "blank2.png",
+ "blank3" => "dir/blank.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
@@ -431,7 +472,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{
- shortcode: "blank2",
+ shortcode: "blank3",
filename: "dir/blank.png",
file: %Plug.Upload{
filename: "blank.png",
@@ -440,7 +481,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
})
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
- "blank2" => "dir/blank.png"
+ "blank2" => "blank2.png",
+ "blank3" => "dir/blank.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
@@ -448,14 +490,15 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{
- shortcode: "blank2",
- new_shortcode: "blank3",
+ shortcode: "blank3",
+ new_shortcode: "blank4",
new_filename: "dir_2/blank_3.png",
force: true
})
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
- "blank3" => "dir_2/blank_3.png"
+ "blank2" => "blank2.png",
+ "blank4" => "dir_2/blank_3.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png")
@@ -481,7 +524,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/not_loaded/files", %{
- shortcode: "blank2",
+ shortcode: "blank3",
filename: "dir/blank.png",
file: %Plug.Upload{
filename: "blank.png",
@@ -535,7 +578,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
})
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
- "blank4" => "dir/blank.png"
+ "blank4" => "dir/blank.png",
+ "blank2" => "blank2.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
@@ -549,7 +593,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
})
|> json_response_and_validate_schema(200) == %{
"blank3" => "dir_2/blank_3.png",
- "blank" => "blank.png"
+ "blank" => "blank.png",
+ "blank2" => "blank2.png"
}
refute File.exists?("#{@emoji_path}/test_pack/dir/")
@@ -557,7 +602,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3")
- |> json_response_and_validate_schema(200) == %{"blank" => "blank.png"}
+ |> json_response_and_validate_schema(200) == %{
+ "blank" => "blank.png",
+ "blank2" => "blank2.png"
+ }
refute File.exists?("#{@emoji_path}/test_pack/dir_2/")
@@ -581,7 +629,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
})
|> json_response_and_validate_schema(200) == %{
"blank_url" => "blank_url.png",
- "blank" => "blank.png"
+ "blank" => "blank.png",
+ "blank2" => "blank2.png"
}
assert File.exists?("#{@emoji_path}/test_pack/blank_url.png")
@@ -602,15 +651,16 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
})
|> json_response_and_validate_schema(200) == %{
"shortcode" => "shortcode.png",
- "blank" => "blank.png"
+ "blank" => "blank.png",
+ "blank2" => "blank2.png"
}
end
test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do
assert admin_conn
- |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank2")
+ |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3")
|> json_response_and_validate_schema(:bad_request) == %{
- "error" => "Emoji \"blank2\" does not exist"
+ "error" => "Emoji \"blank3\" does not exist"
}
end
@@ -618,12 +668,12 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{
- shortcode: "blank2",
- new_shortcode: "blank3",
+ shortcode: "blank3",
+ new_shortcode: "blank4",
new_filename: "dir_2/blank_3.png"
})
|> json_response_and_validate_schema(:bad_request) == %{
- "error" => "Emoji \"blank2\" does not exist"
+ "error" => "Emoji \"blank3\" does not exist"
}
end
@@ -651,7 +701,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
assert Jason.decode!(File.read!("#{@emoji_path}/test_created/pack.json")) == %{
"pack" => %{},
- "files" => %{}
+ "files" => %{},
+ "files_count" => 0
}
assert admin_conn
@@ -709,14 +760,14 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
- refute Map.has_key?(resp, "test_pack_for_import")
+ refute Map.has_key?(resp["packs"], "test_pack_for_import")
assert admin_conn
|> get("/api/pleroma/emoji/packs/import")
|> json_response_and_validate_schema(200) == ["test_pack_for_import"]
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
- assert resp["test_pack_for_import"]["files"] == %{"blank" => "blank.png"}
+ assert resp["packs"]["test_pack_for_import"]["files"] == %{"blank" => "blank.png"}
File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
refute File.exists?("#{@emoji_path}/test_pack_for_import/pack.json")
@@ -736,7 +787,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
- assert resp["test_pack_for_import"]["files"] == %{
+ assert resp["packs"]["test_pack_for_import"]["files"] == %{
"blank" => "blank.png",
"blank2" => "blank.png",
"foo" => "blank.png"
@@ -746,7 +797,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
describe "GET /api/pleroma/emoji/packs/:name" do
test "shows pack.json", %{conn: conn} do
assert %{
- "files" => %{"blank" => "blank.png"},
+ "files" => files,
+ "files_count" => 2,
"pack" => %{
"can-download" => true,
"description" => "Test description",
@@ -759,6 +811,28 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
conn
|> get("/api/pleroma/emoji/packs/test_pack")
|> json_response_and_validate_schema(200)
+
+ assert files == %{"blank" => "blank.png", "blank2" => "blank2.png"}
+
+ assert %{
+ "files" => files,
+ "files_count" => 2
+ } =
+ conn
+ |> get("/api/pleroma/emoji/packs/test_pack?page_size=1")
+ |> json_response_and_validate_schema(200)
+
+ assert files |> Map.keys() |> length() == 1
+
+ assert %{
+ "files" => files,
+ "files_count" => 2
+ } =
+ conn
+ |> get("/api/pleroma/emoji/packs/test_pack?page_size=1&page=2")
+ |> json_response_and_validate_schema(200)
+
+ assert files |> Map.keys() |> length() == 1
end
test "non existing pack", %{conn: conn} do
diff --git a/test/web/pleroma_api/views/chat/message_reference_view_test.exs b/test/web/pleroma_api/views/chat/message_reference_view_test.exs
new file mode 100644
index 000000000..e5b165255
--- /dev/null
+++ b/test/web/pleroma_api/views/chat/message_reference_view_test.exs
@@ -0,0 +1,61 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceViewTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
+
+ import Pleroma.Factory
+
+ test "it displays a chat message" do
+ user = insert(:user)
+ recipient = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
+ {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:")
+
+ chat = Chat.get(user.id, recipient.ap_id)
+
+ object = Object.normalize(activity)
+
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ chat_message = MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
+
+ assert chat_message[:id] == cm_ref.id
+ assert chat_message[:content] == "kippis :firefox:"
+ assert chat_message[:account_id] == user.id
+ assert chat_message[:chat_id]
+ assert chat_message[:created_at]
+ assert chat_message[:unread] == false
+ assert match?([%{shortcode: "firefox"}], chat_message[:emojis])
+
+ {:ok, activity} = CommonAPI.post_chat_message(recipient, user, "gkgkgk", media_id: upload.id)
+
+ object = Object.normalize(activity)
+
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ chat_message_two = MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
+
+ assert chat_message_two[:id] == cm_ref.id
+ assert chat_message_two[:content] == "gkgkgk"
+ assert chat_message_two[:account_id] == recipient.id
+ assert chat_message_two[:chat_id] == chat_message[:chat_id]
+ assert chat_message_two[:attachment]
+ assert chat_message_two[:unread] == true
+ end
+end
diff --git a/test/web/pleroma_api/views/chat_view_test.exs b/test/web/pleroma_api/views/chat_view_test.exs
new file mode 100644
index 000000000..14eecb1bd
--- /dev/null
+++ b/test/web/pleroma_api/views/chat_view_test.exs
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.ChatViewTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.Object
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
+ alias Pleroma.Web.PleromaAPI.ChatView
+
+ import Pleroma.Factory
+
+ test "it represents a chat" do
+ user = insert(:user)
+ recipient = insert(:user)
+
+ {:ok, chat} = Chat.get_or_create(user.id, recipient.ap_id)
+
+ represented_chat = ChatView.render("show.json", chat: chat)
+
+ assert represented_chat == %{
+ id: "#{chat.id}",
+ account: AccountView.render("show.json", user: recipient),
+ unread: 0,
+ last_message: nil,
+ updated_at: Utils.to_masto_date(chat.updated_at)
+ }
+
+ {:ok, chat_message_creation} = CommonAPI.post_chat_message(user, recipient, "hello")
+
+ chat_message = Object.normalize(chat_message_creation, false)
+
+ {:ok, chat} = Chat.get_or_create(user.id, recipient.ap_id)
+
+ represented_chat = ChatView.render("show.json", chat: chat)
+
+ cm_ref = MessageReference.for_chat_and_object(chat, chat_message)
+
+ assert represented_chat[:last_message] ==
+ MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
+ end
+end
diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs
index a826b24c9..b48952b29 100644
--- a/test/web/push/impl_test.exs
+++ b/test/web/push/impl_test.exs
@@ -5,8 +5,10 @@
defmodule Pleroma.Web.Push.ImplTest do
use Pleroma.DataCase
+ alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Push.Impl
alias Pleroma.Web.Push.Subscription
@@ -60,7 +62,8 @@ defmodule Pleroma.Web.Push.ImplTest do
notif =
insert(:notification,
user: user,
- activity: activity
+ activity: activity,
+ type: "mention"
)
assert Impl.perform(notif) == {:ok, [:ok, :ok]}
@@ -126,7 +129,7 @@ defmodule Pleroma.Web.Push.ImplTest do
) ==
"@Bob: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini..."
- assert Impl.format_title(%{activity: activity}) ==
+ assert Impl.format_title(%{activity: activity, type: "mention"}) ==
"New Mention"
end
@@ -136,9 +139,10 @@ defmodule Pleroma.Web.Push.ImplTest do
{:ok, _, _, activity} = CommonAPI.follow(user, other_user)
object = Object.normalize(activity, false)
- assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you"
+ assert Impl.format_body(%{activity: activity, type: "follow"}, user, object) ==
+ "@Bob has followed you"
- assert Impl.format_title(%{activity: activity}) ==
+ assert Impl.format_title(%{activity: activity, type: "follow"}) ==
"New Follower"
end
@@ -157,7 +161,7 @@ defmodule Pleroma.Web.Push.ImplTest do
assert Impl.format_body(%{activity: announce_activity}, user, object) ==
"@#{user.nickname} repeated: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini..."
- assert Impl.format_title(%{activity: announce_activity}) ==
+ assert Impl.format_title(%{activity: announce_activity, type: "reblog"}) ==
"New Repeat"
end
@@ -173,9 +177,10 @@ defmodule Pleroma.Web.Push.ImplTest do
{:ok, activity} = CommonAPI.favorite(user, activity.id)
object = Object.normalize(activity)
- assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post"
+ assert Impl.format_body(%{activity: activity, type: "favourite"}, user, object) ==
+ "@Bob has favorited your post"
- assert Impl.format_title(%{activity: activity}) ==
+ assert Impl.format_title(%{activity: activity, type: "favourite"}) ==
"New Favorite"
end
@@ -193,6 +198,46 @@ defmodule Pleroma.Web.Push.ImplTest do
end
describe "build_content/3" do
+ test "builds content for chat messages" do
+ user = insert(:user)
+ recipient = insert(:user)
+
+ {:ok, chat} = CommonAPI.post_chat_message(user, recipient, "hey")
+ object = Object.normalize(chat, false)
+ [notification] = Notification.for_user(recipient)
+
+ res = Impl.build_content(notification, user, object)
+
+ assert res == %{
+ body: "@#{user.nickname}: hey",
+ title: "New Chat Message"
+ }
+ end
+
+ test "builds content for chat messages with no content" do
+ user = insert(:user)
+ recipient = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
+
+ {:ok, chat} = CommonAPI.post_chat_message(user, recipient, nil, media_id: upload.id)
+ object = Object.normalize(chat, false)
+ [notification] = Notification.for_user(recipient)
+
+ res = Impl.build_content(notification, user, object)
+
+ assert res == %{
+ body: "@#{user.nickname}: (Attachment)",
+ title: "New Chat Message"
+ }
+ end
+
test "hides details for notifications when privacy option enabled" do
user = insert(:user, nickname: "Bob")
user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true})
@@ -218,7 +263,7 @@ defmodule Pleroma.Web.Push.ImplTest do
status: "<Lorem ipsum dolor sit amet."
})
- notif = insert(:notification, user: user2, activity: activity)
+ notif = insert(:notification, user: user2, activity: activity, type: "mention")
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
object = Object.normalize(activity)
@@ -229,7 +274,7 @@ defmodule Pleroma.Web.Push.ImplTest do
{:ok, activity} = CommonAPI.favorite(user, activity.id)
- notif = insert(:notification, user: user2, activity: activity)
+ notif = insert(:notification, user: user2, activity: activity, type: "favourite")
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
object = Object.normalize(activity)
@@ -268,7 +313,7 @@ defmodule Pleroma.Web.Push.ImplTest do
"<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
})
- notif = insert(:notification, user: user2, activity: activity)
+ notif = insert(:notification, user: user2, activity: activity, type: "mention")
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
object = Object.normalize(activity)
@@ -281,7 +326,7 @@ defmodule Pleroma.Web.Push.ImplTest do
{:ok, activity} = CommonAPI.favorite(user, activity.id)
- notif = insert(:notification, user: user2, activity: activity)
+ notif = insert(:notification, user: user2, activity: activity, type: "favourite")
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
object = Object.normalize(activity)
diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs
index e54a13bc8..420a612c6 100644
--- a/test/web/rich_media/parser_test.exs
+++ b/test/web/rich_media/parser_test.exs
@@ -60,19 +60,19 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
test "doesn't just add a title" do
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") ==
{:error,
- "Found metadata was invalid or incomplete: %{url: \"http://example.com/non-ogp\"}"}
+ "Found metadata was invalid or incomplete: %{\"url\" => \"http://example.com/non-ogp\"}"}
end
test "parses ogp" do
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp") ==
{:ok,
%{
- image: "http://ia.media-imdb.com/images/rock.jpg",
- title: "The Rock",
- description:
+ "image" => "http://ia.media-imdb.com/images/rock.jpg",
+ "title" => "The Rock",
+ "description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
- type: "video.movie",
- url: "http://example.com/ogp"
+ "type" => "video.movie",
+ "url" => "http://example.com/ogp"
}}
end
@@ -80,12 +80,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp-missing-title") ==
{:ok,
%{
- image: "http://ia.media-imdb.com/images/rock.jpg",
- title: "The Rock (1996)",
- description:
+ "image" => "http://ia.media-imdb.com/images/rock.jpg",
+ "title" => "The Rock (1996)",
+ "description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
- type: "video.movie",
- url: "http://example.com/ogp-missing-title"
+ "type" => "video.movie",
+ "url" => "http://example.com/ogp-missing-title"
}}
end
@@ -93,12 +93,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/twitter-card") ==
{:ok,
%{
- card: "summary",
- site: "@flickr",
- image: "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
- title: "Small Island Developing States Photo Submission",
- description: "View the album on Flickr.",
- url: "http://example.com/twitter-card"
+ "card" => "summary",
+ "site" => "@flickr",
+ "image" => "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
+ "title" => "Small Island Developing States Photo Submission",
+ "description" => "View the album on Flickr.",
+ "url" => "http://example.com/twitter-card"
}}
end
@@ -106,27 +106,28 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/oembed") ==
{:ok,
%{
- author_name: "‮‭‬bees‬",
- author_url: "https://www.flickr.com/photos/bees/",
- cache_age: 3600,
- flickr_type: "photo",
- height: "768",
- html:
+ "author_name" => "‮‭‬bees‬",
+ "author_url" => "https://www.flickr.com/photos/bees/",
+ "cache_age" => 3600,
+ "flickr_type" => "photo",
+ "height" => "768",
+ "html" =>
"<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by ‮‭‬bees‬, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>",
- license: "All Rights Reserved",
- license_id: 0,
- provider_name: "Flickr",
- provider_url: "https://www.flickr.com/",
- thumbnail_height: 150,
- thumbnail_url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg",
- thumbnail_width: 150,
- title: "Bacon Lollys",
- type: "photo",
- url: "http://example.com/oembed",
- version: "1.0",
- web_page: "https://www.flickr.com/photos/bees/2362225867/",
- web_page_short_url: "https://flic.kr/p/4AK2sc",
- width: "1024"
+ "license" => "All Rights Reserved",
+ "license_id" => 0,
+ "provider_name" => "Flickr",
+ "provider_url" => "https://www.flickr.com/",
+ "thumbnail_height" => 150,
+ "thumbnail_url" =>
+ "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg",
+ "thumbnail_width" => 150,
+ "title" => "Bacon Lollys",
+ "type" => "photo",
+ "url" => "http://example.com/oembed",
+ "version" => "1.0",
+ "web_page" => "https://www.flickr.com/photos/bees/2362225867/",
+ "web_page_short_url" => "https://flic.kr/p/4AK2sc",
+ "width" => "1024"
}}
end
diff --git a/test/web/rich_media/parsers/twitter_card_test.exs b/test/web/rich_media/parsers/twitter_card_test.exs
index 87c767c15..219f005a2 100644
--- a/test/web/rich_media/parsers/twitter_card_test.exs
+++ b/test/web/rich_media/parsers/twitter_card_test.exs
@@ -7,8 +7,7 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
alias Pleroma.Web.RichMedia.Parsers.TwitterCard
test "returns error when html not contains twitter card" do
- assert TwitterCard.parse([{"html", [], [{"head", [], []}, {"body", [], []}]}], %{}) ==
- {:error, "No twitter card metadata found"}
+ assert TwitterCard.parse([{"html", [], [{"head", [], []}, {"body", [], []}]}], %{}) == %{}
end
test "parses twitter card with only name attributes" do
@@ -17,15 +16,21 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
|> Floki.parse_document!()
assert TwitterCard.parse(html, %{}) ==
- {:ok,
- %{
- "app:id:googleplay": "com.nytimes.android",
- "app:name:googleplay": "NYTimes",
- "app:url:googleplay": "nytimes://reader/id/100000006583622",
- site: nil,
- title:
- "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times"
- }}
+ %{
+ "app:id:googleplay" => "com.nytimes.android",
+ "app:name:googleplay" => "NYTimes",
+ "app:url:googleplay" => "nytimes://reader/id/100000006583622",
+ "site" => nil,
+ "description" =>
+ "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
+ "image" =>
+ "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-facebookJumbo.jpg",
+ "type" => "article",
+ "url" =>
+ "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
+ "title" =>
+ "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database."
+ }
end
test "parses twitter card with only property attributes" do
@@ -34,19 +39,19 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
|> Floki.parse_document!()
assert TwitterCard.parse(html, %{}) ==
- {:ok,
- %{
- card: "summary_large_image",
- description:
- "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
- image:
- "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
- "image:alt": "",
- title:
- "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
- url:
- "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html"
- }}
+ %{
+ "card" => "summary_large_image",
+ "description" =>
+ "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
+ "image" =>
+ "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
+ "image:alt" => "",
+ "title" =>
+ "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
+ "url" =>
+ "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
+ "type" => "article"
+ }
end
test "parses twitter card with name & property attributes" do
@@ -55,23 +60,23 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
|> Floki.parse_document!()
assert TwitterCard.parse(html, %{}) ==
- {:ok,
- %{
- "app:id:googleplay": "com.nytimes.android",
- "app:name:googleplay": "NYTimes",
- "app:url:googleplay": "nytimes://reader/id/100000006583622",
- card: "summary_large_image",
- description:
- "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
- image:
- "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
- "image:alt": "",
- site: nil,
- title:
- "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
- url:
- "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html"
- }}
+ %{
+ "app:id:googleplay" => "com.nytimes.android",
+ "app:name:googleplay" => "NYTimes",
+ "app:url:googleplay" => "nytimes://reader/id/100000006583622",
+ "card" => "summary_large_image",
+ "description" =>
+ "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
+ "image" =>
+ "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg",
+ "image:alt" => "",
+ "site" => nil,
+ "title" =>
+ "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
+ "url" =>
+ "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html",
+ "type" => "article"
+ }
end
test "respect only first title tag on the page" do
@@ -84,14 +89,17 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
File.read!("test/fixtures/margaret-corbin-grave-west-point.html") |> Floki.parse_document!()
assert TwitterCard.parse(html, %{}) ==
- {:ok,
- %{
- site: "@atlasobscura",
- title:
- "The Missing Grave of Margaret Corbin, Revolutionary War Veteran - Atlas Obscura",
- card: "summary_large_image",
- image: image_path
- }}
+ %{
+ "site" => "@atlasobscura",
+ "title" => "The Missing Grave of Margaret Corbin, Revolutionary War Veteran",
+ "card" => "summary_large_image",
+ "image" => image_path,
+ "description" =>
+ "She's the only woman veteran honored with a monument at West Point. But where was she buried?",
+ "site_name" => "Atlas Obscura",
+ "type" => "article",
+ "url" => "http://www.atlasobscura.com/articles/margaret-corbin-grave-west-point"
+ }
end
test "takes first founded title in html head if there is html markup error" do
@@ -100,14 +108,20 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do
|> Floki.parse_document!()
assert TwitterCard.parse(html, %{}) ==
- {:ok,
- %{
- site: nil,
- title:
- "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times",
- "app:id:googleplay": "com.nytimes.android",
- "app:name:googleplay": "NYTimes",
- "app:url:googleplay": "nytimes://reader/id/100000006583622"
- }}
+ %{
+ "site" => nil,
+ "title" =>
+ "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.",
+ "app:id:googleplay" => "com.nytimes.android",
+ "app:name:googleplay" => "NYTimes",
+ "app:url:googleplay" => "nytimes://reader/id/100000006583622",
+ "description" =>
+ "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.",
+ "image" =>
+ "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-facebookJumbo.jpg",
+ "type" => "article",
+ "url" =>
+ "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html"
+ }
end
end
diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs
index 3f012259a..245f6e63f 100644
--- a/test/web/streamer/streamer_test.exs
+++ b/test/web/streamer/streamer_test.exs
@@ -7,11 +7,15 @@ defmodule Pleroma.Web.StreamerTest do
import Pleroma.Factory
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
alias Pleroma.Conversation.Participation
alias Pleroma.List
+ alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Streamer
+ alias Pleroma.Web.StreamerView
@moduletag needs_streamer: true, capture_log: true
@@ -145,6 +149,57 @@ defmodule Pleroma.Web.StreamerTest do
refute Streamer.filtered_by_user?(user, notify)
end
+ test "it sends chat messages to the 'user:pleroma_chat' stream", %{user: user} do
+ other_user = insert(:user)
+
+ {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno")
+ object = Object.normalize(create_activity, false)
+ chat = Chat.get(user.id, other_user.ap_id)
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+ cm_ref = %{cm_ref | chat: chat, object: object}
+
+ Streamer.get_topic_and_add_socket("user:pleroma_chat", user)
+ Streamer.stream("user:pleroma_chat", {user, cm_ref})
+
+ text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
+
+ assert text =~ "hey cirno"
+ assert_receive {:text, ^text}
+ end
+
+ test "it sends chat messages to the 'user' stream", %{user: user} do
+ other_user = insert(:user)
+
+ {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno")
+ object = Object.normalize(create_activity, false)
+ chat = Chat.get(user.id, other_user.ap_id)
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+ cm_ref = %{cm_ref | chat: chat, object: object}
+
+ Streamer.get_topic_and_add_socket("user", user)
+ Streamer.stream("user", {user, cm_ref})
+
+ text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
+
+ assert text =~ "hey cirno"
+ assert_receive {:text, ^text}
+ end
+
+ test "it sends chat message notifications to the 'user:notification' stream", %{user: user} do
+ other_user = insert(:user)
+
+ {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey")
+
+ notify =
+ Repo.get_by(Pleroma.Notification, user_id: user.id, activity_id: create_activity.id)
+ |> Repo.preload(:activity)
+
+ Streamer.get_topic_and_add_socket("user:notification", user)
+ Streamer.stream("user:notification", notify)
+ assert_receive {:render_with_user, _, _, ^notify}
+ refute Streamer.filtered_by_user?(user, notify)
+ end
+
test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{
user: user
} do
diff --git a/test/workers/cron/purge_expired_activities_worker_test.exs b/test/workers/cron/purge_expired_activities_worker_test.exs
index 5864f9e5f..b1db59fdf 100644
--- a/test/workers/cron/purge_expired_activities_worker_test.exs
+++ b/test/workers/cron/purge_expired_activities_worker_test.exs
@@ -11,7 +11,9 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorkerTest do
import Pleroma.Factory
import ExUnit.CaptureLog
- setup do: clear_config([ActivityExpiration, :enabled])
+ setup do
+ clear_config([ActivityExpiration, :enabled])
+ end
test "deletes an expiration activity" do
Pleroma.Config.put([ActivityExpiration, :enabled], true)
@@ -36,6 +38,32 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorkerTest do
refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id)
end
+ test "works with ActivityExpirationPolicy" do
+ Pleroma.Config.put([ActivityExpiration, :enabled], true)
+
+ clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
+
+ user = insert(:user)
+
+ days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
+
+ {:ok, %{id: id} = activity} = Pleroma.Web.CommonAPI.post(user, %{status: "cofe"})
+
+ past_date =
+ NaiveDateTime.utc_now() |> Timex.shift(days: -days) |> NaiveDateTime.truncate(:second)
+
+ activity
+ |> Repo.preload(:expiration)
+ |> Map.get(:expiration)
+ |> Ecto.Changeset.change(%{scheduled_at: past_date})
+ |> Repo.update!()
+
+ Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(:ops, :pid)
+
+ assert [%{data: %{"type" => "Delete", "deleted_activity_id" => ^id}}] =
+ Pleroma.Repo.all(Pleroma.Activity)
+ end
+
describe "delete_activity/1" do
test "adds log message if activity isn't find" do
assert capture_log([level: :error], fn ->