Merge develop
authorRoman Chvanikov <chvanikoff@pm.me>
Tue, 9 Jul 2019 18:21:09 +0000 (21:21 +0300)
committerRoman Chvanikov <chvanikoff@pm.me>
Tue, 9 Jul 2019 18:21:09 +0000 (21:21 +0300)
224 files changed:
.gitlab-ci.yml
CHANGELOG.md
config/config.exs
config/test.exs
docs/api/admin_api.md
docs/config.md
docs/installation/otp_en.md
lib/mix/tasks/pleroma/ecto/ecto.ex
lib/mix/tasks/pleroma/instance.ex
lib/pleroma/application.ex
lib/pleroma/object.ex
lib/pleroma/object/fetcher.ex
lib/pleroma/reverse_proxy/client.ex [new file with mode: 0644]
lib/pleroma/reverse_proxy/reverse_proxy.ex [moved from lib/pleroma/reverse_proxy.ex with 97% similarity]
lib/pleroma/uploaders/swift/keystone.ex [deleted file]
lib/pleroma/uploaders/swift/swift.ex [deleted file]
lib/pleroma/uploaders/swift/uploader.ex [deleted file]
lib/pleroma/user.ex
lib/pleroma/user/query.ex
lib/pleroma/user/search.ex
lib/pleroma/user/synchronization.ex [new file with mode: 0644]
lib/pleroma/user/synchronization_worker.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/views/report_view.ex
lib/pleroma/web/federator/federator.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/search_controller.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/mastodon_api/websocket_handler.ex
lib/pleroma/web/media_proxy/media_proxy.ex
lib/pleroma/web/metadata/opengraph.ex
lib/pleroma/web/metadata/twitter_card.ex
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
lib/pleroma/web/ostatus/handlers/note_handler.ex
lib/pleroma/web/ostatus/ostatus.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
mix.exs
mix.lock
priv/repo/migrations/20170320193800_create_user.exs
priv/repo/migrations/20170321074828_create_activity.exs
priv/repo/migrations/20170321074832_create_object.exs
priv/repo/migrations/20170330153447_add_index_to_objects.exs
priv/repo/migrations/20170415141210_add_unique_index_to_email_and_nickname.exs
priv/repo/migrations/20170418200143_create_webssub_server_subscription.exs
priv/repo/migrations/20170426154155_create_websub_client_subscription.exs
priv/repo/migrations/20170501133231_add_id_contraints_to_activities_and_objects_part_two.exs
priv/repo/migrations/20170502083023_add_local_field_to_activities.exs
priv/repo/migrations/20170506222027_add_unique_index_to_apid.exs
priv/repo/migrations/20170522160642_case_insensivtivity.exs
priv/repo/migrations/20170529093232_longer_bios.exs
priv/repo/migrations/20170620095947_remove_activities_index.exs
priv/repo/migrations/20170620133028_add_object_activity_index.exs
priv/repo/migrations/20170620142420_add_object_activity_index_part_two.exs
priv/repo/migrations/20170701142005_add_actor_index_to_activity.exs
priv/repo/migrations/20170906120646_add_mastodon_apps.exs
priv/repo/migrations/20170906143140_create_o_auth_authorizations.exs
priv/repo/migrations/20170906152508_create_o_auth_token.exs
priv/repo/migrations/20170911123607_create_notifications.exs
priv/repo/migrations/20171019141706_create_password_reset_tokens.exs
priv/repo/migrations/20171109091239_add_actor_to_activity.exs
priv/repo/migrations/20171130135819_add_local_index_to_user.exs
priv/repo/migrations/20171212163643_add_recipients_to_activities.exs
priv/repo/migrations/20171212164525_fill_recipients_in_activities.exs
priv/repo/migrations/20180221210540_make_following_postgres_array.exs
priv/repo/migrations/20180327174350_drop_local_index_on_activities.exs
priv/repo/migrations/20180327175831_actually_drop_local_index.exs
priv/repo/migrations/20180429094642_create_lists.exs
priv/repo/migrations/20180516154905_create_user_trigram_index.exs
priv/repo/migrations/20180530123448_add_list_follow_index.exs
priv/repo/migrations/20180612110515_create_user_invite_tokens.exs
priv/repo/migrations/20180813003722_create_filters.exs
priv/repo/migrations/20180829082446_add_recipients_to_and_cc_fields_to_activities.exs
priv/repo/migrations/20180829182612_activities_add_to_cc_indices.exs
priv/repo/migrations/20180829183529_remove_recipients_to_and_cc_fields_from_activities.exs
priv/repo/migrations/20180903114437_users_add_is_moderator_index.exs
priv/repo/migrations/20180918182427_create_push_subscriptions.exs
priv/repo/migrations/20181201104428_add_uuid_extension.exs
priv/repo/migrations/20181201105617_add_uui_ds_to_user_info.exs
priv/repo/migrations/20181206125616_add_tags_to_users.exs
priv/repo/migrations/20181218172826_users_and_activities_flake_id.exs
priv/repo/migrations/20190109152453_add_visibility_function.exs
priv/repo/migrations/20190115085500_create_user_fts_index.exs
priv/repo/migrations/20190118074940_fix_user_trigram_index.exs
priv/repo/migrations/20190123092341_users_add_is_admin_index.exs
priv/repo/migrations/20190123125546_create_instances.exs
priv/repo/migrations/20190123125839_fix_info_ids.exs
priv/repo/migrations/20190126160540_change_push_subscriptions_varchar.exs
priv/repo/migrations/20190204200237_add_correct_dm_index.exs
priv/repo/migrations/20190205114625_create_thread_mutes.exs
priv/repo/migrations/20190315101315_create_registrations.exs
priv/repo/migrations/20190325185009_create_notification_id_index.exs
priv/repo/migrations/20190328053912_create_scheduled_activities.exs
priv/repo/migrations/20190403131720_add_oauth_token_indexes.exs
priv/repo/migrations/20190405160700_add_index_on_subscribers.exs
priv/repo/migrations/20190408123347_create_conversations.exs
priv/repo/migrations/20190410152859_add_participation_updated_at_index.exs
priv/repo/migrations/20190411094120_add_index_on_user_info_deactivated.exs
priv/repo/migrations/20190413082658_create_bookmarks.exs
priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs
priv/repo/migrations/20190501125843_add_fts_index_to_objects.exs
priv/repo/migrations/20190501133552_add_refresh_token_index_to_token.exs
priv/repo/migrations/20190513175809_change_hide_column_in_filter_table.exs
priv/repo/migrations/20190515222404_add_thread_visibility_function.exs
priv/repo/migrations/20190518032627_create_config.exs
priv/repo/migrations/20190525071417_add_non_follows_and_non_followers_fields_to_notification_settings.exs
priv/repo/migrations/20190603115238_add_index_on_activities_local.exs
priv/repo/migrations/20190603173419_add_tag_index_to_objects.exs
priv/repo/migrations/20190622151019_add_group_key_to_config.exs
priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs
priv/templates/sample_config.eex
rel/files/bin/pleroma_ctl
test/conversation_test.exs
test/fixtures/tesla_mock/7369654.atom [moved from test/fixtures/httpoison_mock/7369654.atom with 100% similarity]
test/fixtures/tesla_mock/7369654.html [moved from test/fixtures/httpoison_mock/7369654.html with 100% similarity]
test/fixtures/tesla_mock/7even.json [moved from test/fixtures/httpoison_mock/7even.json with 100% similarity]
test/fixtures/tesla_mock/admin@mastdon.example.org.json [moved from test/fixtures/httpoison_mock/admin@mastdon.example.org.json with 100% similarity]
test/fixtures/tesla_mock/atarifrosch_feed.xml [moved from test/fixtures/httpoison_mock/atarifrosch_feed.xml with 100% similarity]
test/fixtures/tesla_mock/atarifrosch_webfinger.xml [moved from test/fixtures/httpoison_mock/atarifrosch_webfinger.xml with 100% similarity]
test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json [moved from test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json with 100% similarity]
test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json [moved from test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json with 100% similarity]
test/fixtures/tesla_mock/eal_sakamoto.xml [moved from test/fixtures/httpoison_mock/eal_sakamoto.xml with 100% similarity]
test/fixtures/tesla_mock/emelie.atom [moved from test/fixtures/httpoison_mock/emelie.atom with 100% similarity]
test/fixtures/tesla_mock/emelie.json [moved from test/fixtures/httpoison_mock/emelie.json with 100% similarity]
test/fixtures/tesla_mock/framasoft@framatube.org.json [moved from test/fixtures/httpoison_mock/framasoft@framatube.org.json with 100% similarity]
test/fixtures/tesla_mock/framatube.org_host_meta [moved from test/fixtures/httpoison_mock/framatube.org_host_meta with 100% similarity]
test/fixtures/tesla_mock/gerzilla.de_host_meta [moved from test/fixtures/httpoison_mock/gerzilla.de_host_meta with 100% similarity]
test/fixtures/tesla_mock/gnusocial.de_host_meta [moved from test/fixtures/httpoison_mock/gnusocial.de_host_meta with 100% similarity]
test/fixtures/tesla_mock/gs.example.org_host_meta [moved from test/fixtures/httpoison_mock/gs.example.org_host_meta with 100% similarity]
test/fixtures/tesla_mock/hellpie.json [moved from test/fixtures/httpoison_mock/hellpie.json with 100% similarity]
test/fixtures/tesla_mock/http___gs.example.org_4040_index.php_user_1.xml [moved from test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml with 100% similarity]
test/fixtures/tesla_mock/http___mastodon.example.org_users_admin_status_1234.json [moved from test/fixtures/httpoison_mock/http___mastodon.example.org_users_admin_status_1234.json with 100% similarity]
test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml [moved from test/fixtures/httpoison_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml with 100% similarity]
test/fixtures/tesla_mock/https___info.pleroma.site_actor.json [moved from test/fixtures/httpoison_mock/https___info.pleroma.site_actor.json with 100% similarity]
test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom [moved from test/fixtures/httpoison_mock/https___mamot.fr_users_Skruyb.atom with 100% similarity]
test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.atom [moved from test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.atom with 100% similarity]
test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.xml [moved from test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml with 100% similarity]
test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json [moved from test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json with 100% similarity]
test/fixtures/tesla_mock/https___pawoo.net_users_aqidaqidaqid.xml [moved from test/fixtures/httpoison_mock/https___pawoo.net_users_aqidaqidaqid.xml with 100% similarity]
test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom [moved from test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.atom with 100% similarity]
test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.xml [moved from test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml with 100% similarity]
test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain.xml [moved from test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml with 100% similarity]
test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml [moved from test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml with 100% similarity]
test/fixtures/tesla_mock/https___prismo.news__mxb.json [moved from test/fixtures/httpoison_mock/https___prismo.news__mxb.json with 100% similarity]
test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml [moved from test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml with 100% similarity]
test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml [moved from test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml with 100% similarity]
test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.html [moved from test/fixtures/httpoison_mock/https___shitposter.club_notice_2827873.html with 100% similarity]
test/fixtures/tesla_mock/https___shitposter.club_user_1.xml [moved from test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml with 100% similarity]
test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml [moved from test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml with 100% similarity]
test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml [moved from test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml with 100% similarity]
test/fixtures/tesla_mock/https___social.heldscal.la_user_23211.xml [moved from test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml with 100% similarity]
test/fixtures/tesla_mock/https___social.heldscal.la_user_29191.xml [moved from test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml with 100% similarity]
test/fixtures/tesla_mock/https__info.pleroma.site_activity.json [moved from test/fixtures/httpoison_mock/https__info.pleroma.site_activity.json with 100% similarity]
test/fixtures/tesla_mock/https__info.pleroma.site_activity2.json [moved from test/fixtures/httpoison_mock/https__info.pleroma.site_activity2.json with 100% similarity]
test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json [moved from test/fixtures/httpoison_mock/https__info.pleroma.site_activity3.json with 100% similarity]
test/fixtures/tesla_mock/https__info.pleroma.site_activity4.json [moved from test/fixtures/httpoison_mock/https__info.pleroma.site_activity4.json with 100% similarity]
test/fixtures/tesla_mock/kaniini@gerzilla.de.json [moved from test/fixtures/httpoison_mock/kaniini@gerzilla.de.json with 100% similarity]
test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json [moved from test/fixtures/httpoison_mock/kaniini@hubzilla.example.org.json with 100% similarity]
test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml [moved from test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml with 100% similarity]
test/fixtures/tesla_mock/lucifermysticus.json [moved from test/fixtures/httpoison_mock/lucifermysticus.json with 100% similarity]
test/fixtures/tesla_mock/macgirvin.com_host_meta [moved from test/fixtures/httpoison_mock/macgirvin.com_host_meta with 100% similarity]
test/fixtures/tesla_mock/mamot.fr_host_meta [moved from test/fixtures/httpoison_mock/mamot.fr_host_meta with 100% similarity]
test/fixtures/tesla_mock/mastodon.social_host_meta [moved from test/fixtures/httpoison_mock/mastodon.social_host_meta with 100% similarity]
test/fixtures/tesla_mock/mastodon.xyz_host_meta [moved from test/fixtures/httpoison_mock/mastodon.xyz_host_meta with 100% similarity]
test/fixtures/tesla_mock/mayumayu.json [moved from test/fixtures/httpoison_mock/mayumayu.json with 100% similarity]
test/fixtures/tesla_mock/mayumayupost.json [moved from test/fixtures/httpoison_mock/mayumayupost.json with 100% similarity]
test/fixtures/tesla_mock/mike@osada.macgirvin.com.json [moved from test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json with 100% similarity]
test/fixtures/tesla_mock/nonexistant@social.heldscal.la.xml [moved from test/fixtures/httpoison_mock/nonexistant@social.heldscal.la.xml with 100% similarity]
test/fixtures/tesla_mock/pawoo.net_host_meta [moved from test/fixtures/httpoison_mock/pawoo.net_host_meta with 100% similarity]
test/fixtures/tesla_mock/peertube.moe-vid.json [moved from test/fixtures/httpoison_mock/peertube.moe-vid.json with 100% similarity]
test/fixtures/tesla_mock/pleroma.soykaf.com_host_meta [moved from test/fixtures/httpoison_mock/pleroma.soykaf.com_host_meta with 100% similarity]
test/fixtures/tesla_mock/puckipedia.com.json [moved from test/fixtures/httpoison_mock/puckipedia.com.json with 100% similarity]
test/fixtures/tesla_mock/rinpatch.json [moved from test/fixtures/httpoison_mock/rinpatch.json with 100% similarity]
test/fixtures/tesla_mock/rye.json [moved from test/fixtures/httpoison_mock/rye.json with 100% similarity]
test/fixtures/tesla_mock/sakamoto.atom [moved from test/fixtures/httpoison_mock/sakamoto.atom with 100% similarity]
test/fixtures/tesla_mock/sakamoto_eal_feed.atom [moved from test/fixtures/httpoison_mock/sakamoto_eal_feed.atom with 100% similarity]
test/fixtures/tesla_mock/shitposter.club_host_meta [moved from test/fixtures/httpoison_mock/shitposter.club_host_meta with 100% similarity]
test/fixtures/tesla_mock/shp@pleroma.soykaf.com.feed [moved from test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.feed with 100% similarity]
test/fixtures/tesla_mock/shp@pleroma.soykaf.com.webfigner [moved from test/fixtures/httpoison_mock/shp@pleroma.soykaf.com.webfigner with 100% similarity]
test/fixtures/tesla_mock/shp@social.heldscal.la.xml [moved from test/fixtures/httpoison_mock/shp@social.heldscal.la.xml with 100% similarity]
test/fixtures/tesla_mock/skruyb@mamot.fr.atom [moved from test/fixtures/httpoison_mock/skruyb@mamot.fr.atom with 100% similarity]
test/fixtures/tesla_mock/social.heldscal.la_host_meta [moved from test/fixtures/httpoison_mock/social.heldscal.la_host_meta with 100% similarity]
test/fixtures/tesla_mock/social.sakamoto.gq_host_meta [moved from test/fixtures/httpoison_mock/social.sakamoto.gq_host_meta with 100% similarity]
test/fixtures/tesla_mock/social.stopwatchingus-heidelberg.de_host_meta [moved from test/fixtures/httpoison_mock/social.stopwatchingus-heidelberg.de_host_meta with 100% similarity]
test/fixtures/tesla_mock/social.wxcafe.net_host_meta [moved from test/fixtures/httpoison_mock/social.wxcafe.net_host_meta with 100% similarity]
test/fixtures/tesla_mock/spc_5381.atom [moved from test/fixtures/httpoison_mock/spc_5381.atom with 100% similarity]
test/fixtures/tesla_mock/spc_5381_xrd.xml [moved from test/fixtures/httpoison_mock/spc_5381_xrd.xml with 100% similarity]
test/fixtures/tesla_mock/squeet.me_host_meta [moved from test/fixtures/httpoison_mock/squeet.me_host_meta with 100% similarity]
test/fixtures/tesla_mock/status.alpicola.com_host_meta [moved from test/fixtures/httpoison_mock/status.alpicola.com_host_meta with 100% similarity]
test/fixtures/tesla_mock/status.emelie.json [moved from test/fixtures/httpoison_mock/status.emelie.json with 100% similarity]
test/fixtures/tesla_mock/webfinger_emelie.json [moved from test/fixtures/httpoison_mock/webfinger_emelie.json with 100% similarity]
test/fixtures/tesla_mock/winterdienst_webfinger.json [moved from test/fixtures/httpoison_mock/winterdienst_webfinger.json with 100% similarity]
test/fixtures/users_mock/masto_closed_followers.json [new file with mode: 0644]
test/fixtures/users_mock/masto_closed_following.json [new file with mode: 0644]
test/fixtures/users_mock/pleroma_followers.json [new file with mode: 0644]
test/fixtures/users_mock/pleroma_following.json [new file with mode: 0644]
test/http/request_builder_test.exs [new file with mode: 0644]
test/integration/mastodon_websocket_test.exs
test/media_proxy_test.exs
test/reverse_proxy_test.exs [new file with mode: 0644]
test/support/helpers.ex
test/support/http_request_mock.ex
test/tasks/ecto/ecto_test.exs [new file with mode: 0644]
test/tasks/pleroma_test.exs [new file with mode: 0644]
test/tasks/robots_txt_test.exs [new file with mode: 0644]
test/test_helper.exs
test/user/synchronization_test.exs [new file with mode: 0644]
test/user/synchronization_worker_test.exs [new file with mode: 0644]
test/user_search_test.exs [new file with mode: 0644]
test/user_test.exs
test/web/activity_pub/activity_pub_controller_test.exs
test/web/activity_pub/transmogrifier_test.exs
test/web/admin_api/views/report_view_test.exs
test/web/federator_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs
test/web/mastodon_api/status_view_test.exs
test/web/ostatus/ostatus_controller_test.exs
test/web/ostatus/ostatus_test.exs
test/web/plugs/federating_plug_test.exs
test/web/twitter_api/twitter_api_controller_test.exs
test/web/web_finger/web_finger_controller_test.exs
test/web/websub/websub_controller_test.exs

index f8711f299e032b8ce6b3a6f386e0a1772d388043..d0c540b16f7ebcb5fd648ab1a60aa6d962a75098 100644 (file)
@@ -35,6 +35,7 @@ docs-build:
   - develop@pleroma/pleroma
   variables:
     MIX_ENV: dev
+    PLEROMA_BUILD_ENV: prod
   script:
     - mix deps.get
     - mix compile
index bd7ca14e44b10fe6779f7af6b5851b520b7a2085..f486e01ac0349cdf4a5ba9f841d592821ba120a4 100644 (file)
@@ -6,14 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ## [Unreleased]
 ### Added
 - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
+Configuration: `federation_incoming_replies_max_depth` option
 - Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
+- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header
+- Admin API: Return users' tags when querying reports
+- Admin API: Return avatar and display name when querying users
+- Admin API: Allow querying user by ID
+- Added synchronization of following/followers counters for external users
 
 ### Fixed
 - Not being able to pin unlisted posts
+- Metadata rendering errors resulting in the entire page being inaccessible
+- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
+- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
 
 ### Changed
+- Configuration: OpenGraph and TwitterCard providers enabled by default
 - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
 
+### Changed
+- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
+
 ## [1.0.0] - 2019-06-29
 ### Security
 - Mastodon API: Fix display names not being sanitized
@@ -75,6 +88,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - OAuth: added job to clean expired access tokens
 - MRF: Support for rejecting reports from specific instances (`mrf_simple`)
 - MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`)
+- Ability to reset avatar, profile banner and backgroud
 - MRF: Support for running subchains.
 - Configuration: `skip_thread_containment` option
 - Configuration: `rate_limit` option. See `Pleroma.Plugs.RateLimiter` documentation for details.
index cb3ee531ccd3c84597aab04f5fba74906db8bb48..d8c8b1a6d385a34d312253a3d784cb40b851f44e 100644 (file)
@@ -218,6 +218,7 @@ config :pleroma, :instance,
   },
   registrations_open: true,
   federating: true,
+  federation_incoming_replies_max_depth: 100,
   federation_reachability_timeout_days: 7,
   federation_publisher_modules: [
     Pleroma.Web.ActivityPub.Publisher,
@@ -248,7 +249,14 @@ config :pleroma, :instance,
   remote_post_retention_days: 90,
   skip_thread_containment: true,
   limit_to_local_content: :unauthenticated,
-  dynamic_configuration: false
+  dynamic_configuration: false,
+  external_user_synchronization: [
+    enabled: false,
+    # every 2 hours
+    interval: 60 * 60 * 2,
+    max_retries: 3,
+    limit: 500
+  ]
 
 config :pleroma, :markup,
   # XXX - unfortunately, inline images must be enabled by default right now, because
@@ -358,7 +366,11 @@ config :pleroma, :gopher,
   port: 9999
 
 config :pleroma, Pleroma.Web.Metadata,
-  providers: [Pleroma.Web.Metadata.Providers.RelMe],
+  providers: [
+    Pleroma.Web.Metadata.Providers.OpenGraph,
+    Pleroma.Web.Metadata.Providers.TwitterCard,
+    Pleroma.Web.Metadata.Providers.RelMe
+  ],
   unfurl_nsfw: false
 
 config :pleroma, :suggestions,
index 1838cffc8eb15302b02bd05e8870e274e5fd343f..1635a5d92feaf8c5b114ba62dc478457af2a315a 100644 (file)
@@ -28,7 +28,8 @@ config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test
 config :pleroma, :instance,
   email: "admin@example.com",
   notify_email: "noreply@example.com",
-  skip_thread_containment: false
+  skip_thread_containment: false,
+  federating: false
 
 # Configure your database
 config :pleroma, Pleroma.Repo,
@@ -76,6 +77,8 @@ IO.puts("RUM enabled: #{rum_enabled}")
 
 config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ"
 
+config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
+
 try do
   import_config "test.secret.exs"
 rescue
index 4be0ab0f8db8df0f5bdb91b32916a19a322419b9..bce5e399bbedecc806f9c50f7171a108dade74c3 100644 (file)
@@ -38,7 +38,9 @@ Authentication is required and the user must be an admin.
         "moderator": bool
       },
       "local": bool,
-      "tags": array
+      "tags": array,
+      "avatar": string,
+      "display_name": string
     },
     ...
   ]
@@ -174,13 +176,13 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
   - `nickname`
   - `status` BOOLEAN field, false value means deactivation.
 
-## `/api/pleroma/admin/users/:nickname`
+## `/api/pleroma/admin/users/:nickname_or_id`
 
 ### Retrive the details of a user
 
 - Method: `GET`
 - Params:
-  - `nickname`
+  - `nickname` or `id`
 - Response:
   - On failure: `Not found`
   - On success: JSON of the user
@@ -331,6 +333,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
           "pleroma": {},
           "sensitive": false
         },
+        "tags": ["force_unlisted"],
         "statuses_count": 3,
         "url": "https://pleroma.example.org/users/user",
         "username": "user"
@@ -366,6 +369,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
           "pleroma": {},
           "sensitive": false
         },
+        "tags": ["force_unlisted"],
         "statuses_count": 1,
         "url": "https://pleroma.example.org/users/lain",
         "username": "lain"
index 15af7f1b22fb1cec864ba8e287882c0557ecc5e8..cbaa790d1bcc325b0befcca672594b7000cfe2d6 100644 (file)
@@ -87,6 +87,7 @@ config :pleroma, Pleroma.Emails.Mailer,
 * `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
 * `account_activation_required`: Require users to confirm their emails before signing in.
 * `federating`: Enable federation with other instances
+* `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:
@@ -124,6 +125,12 @@ config :pleroma, Pleroma.Emails.Mailer,
 * `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
 * `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
 * `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
+* `external_user_synchronization`: Following/followers counters synchronization settings.
+  * `enabled`: Enables synchronization
+  * `interval`: Interval between synchronization.
+  * `max_retries`: Max rettries for host. After exceeding the limit, the check will not be carried out for users from this host.
+  * `limit`: Users batch size for processing in one time.
+
 
 
 ## :logger
index fa71f23e1628224b774745eb85b5ae8f3b4d8c50..9b851e39562c7ef7b755f9fddc2d6c86a69d6534 100644 (file)
@@ -207,7 +207,7 @@ certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --
 
 # Add it to the daily cron
 echo '#!/bin/sh
-certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook "systemctl reload nginx"
+certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx"
 ' > /etc/cron.daily/renew-pleroma-cert
 chmod +x /etc/cron.daily/renew-pleroma-cert
 
@@ -228,7 +228,7 @@ certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --
 
 # Add it to the daily cron
 echo '#!/bin/sh
-certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook "rc-service nginx reload"
+certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "rc-service nginx reload"
 ' > /etc/periodic/daily/renew-pleroma-cert
 chmod +x /etc/periodic/daily/renew-pleroma-cert
 
index 324f57fdd789be719ca6ea3fd8b386f922c59e3b..b66f6337643b011cb20c43c489f46c4fdc2a187b 100644 (file)
@@ -1,6 +1,7 @@
 # Pleroma: A lightweight social networking server
 # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-onl
+
 defmodule Mix.Tasks.Pleroma.Ecto do
   @doc """
   Ensures the given repository's migrations path exists on the file system.
index 0231b76cdeb67a7ebb2cdb1fe002c96ba83432df..f08e5ff3fe15f675024b595578c52a2ce46d6d99 100644 (file)
@@ -149,7 +149,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
       uploads_dir =
         get_option(
           options,
-          :upload_dir,
+          :uploads_dir,
           "What directory should media uploads go in (when using the local uploader)?",
           Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
         )
index 29cd144770f5d4ca1d635ca328e541d9ca87c48a..8887e393536db2ef6a60bd8ee5c2ce979a40925a 100644 (file)
@@ -155,7 +155,11 @@ defmodule Pleroma.Application do
             start: {Pleroma.Web.Endpoint, :start_link, []},
             type: :supervisor
           },
-          %{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}}
+          %{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}},
+          %{
+            id: Pleroma.User.SynchronizationWorker,
+            start: {Pleroma.User.SynchronizationWorker, :start_link, []}
+          }
         ]
 
     # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
index 4b181ec59f0ab225d3c1c44add562587036cf226..b8647dd26ed9f8aa9bc9a2f661e8eee34a28525c 100644 (file)
@@ -44,20 +44,20 @@ defmodule Pleroma.Object do
     Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
   end
 
-  def normalize(_, fetch_remote \\ true)
+  def normalize(_, fetch_remote \\ true, options \\ [])
   # If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
   # Use this whenever possible, especially when walking graphs in an O(N) loop!
-  def normalize(%Object{} = object, _), do: object
-  def normalize(%Activity{object: %Object{} = object}, _), do: object
+  def normalize(%Object{} = object, _, _), do: object
+  def normalize(%Activity{object: %Object{} = object}, _, _), do: object
 
   # A hack for fake activities
-  def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _) do
+  def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _, _) do
     %Object{id: "pleroma:fake_object_id", data: data}
   end
 
   # Catch and log Object.normalize() calls where the Activity's child object is not
   # preloaded.
-  def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote) do
+  def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do
     Logger.debug(
       "Object.normalize() called without preloaded object (#{ap_id}).  Consider preloading the object!"
     )
@@ -67,7 +67,7 @@ defmodule Pleroma.Object do
     normalize(ap_id, fetch_remote)
   end
 
-  def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote) do
+  def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do
     Logger.debug(
       "Object.normalize() called without preloaded object (#{ap_id}).  Consider preloading the object!"
     )
@@ -78,10 +78,14 @@ defmodule Pleroma.Object do
   end
 
   # Old way, try fetching the object through cache.
-  def normalize(%{"id" => ap_id}, fetch_remote), do: normalize(ap_id, fetch_remote)
-  def normalize(ap_id, false) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
-  def normalize(ap_id, true) when is_binary(ap_id), do: Fetcher.fetch_object_from_id!(ap_id)
-  def normalize(_, _), do: nil
+  def normalize(%{"id" => ap_id}, fetch_remote, _), do: normalize(ap_id, fetch_remote)
+  def normalize(ap_id, false, _) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
+
+  def normalize(ap_id, true, options) when is_binary(ap_id) do
+    Fetcher.fetch_object_from_id!(ap_id, options)
+  end
+
+  def normalize(_, _, _), do: nil
 
   # Owned objects can only be mutated by their owner
   def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),
index c422490ac4ae922399589d7ee842e5eb21a4a434..fffbf2bbb23062bdb983e922d95bf0e6f1aed069 100644 (file)
@@ -22,7 +22,7 @@ defmodule Pleroma.Object.Fetcher do
 
   # TODO:
   # This will create a Create activity, which we need internally at the moment.
-  def fetch_object_from_id(id) do
+  def fetch_object_from_id(id, options \\ []) do
     if object = Object.get_cached_by_ap_id(id) do
       {:ok, object}
     else
@@ -38,7 +38,7 @@ defmodule Pleroma.Object.Fetcher do
              "object" => data
            },
            :ok <- Containment.contain_origin(id, params),
-           {:ok, activity} <- Transmogrifier.handle_incoming(params),
+           {:ok, activity} <- Transmogrifier.handle_incoming(params, options),
            {:object, _data, %Object{} = object} <-
              {:object, data, Object.normalize(activity, false)} do
         {:ok, object}
@@ -63,8 +63,8 @@ defmodule Pleroma.Object.Fetcher do
     end
   end
 
-  def fetch_object_from_id!(id) do
-    with {:ok, object} <- fetch_object_from_id(id) do
+  def fetch_object_from_id!(id, options \\ []) do
+    with {:ok, object} <- fetch_object_from_id(id, options) do
       object
     else
       _e ->
diff --git a/lib/pleroma/reverse_proxy/client.ex b/lib/pleroma/reverse_proxy/client.ex
new file mode 100644 (file)
index 0000000..57c2d2c
--- /dev/null
@@ -0,0 +1,24 @@
+defmodule Pleroma.ReverseProxy.Client do
+  @callback request(atom(), String.t(), [tuple()], String.t(), list()) ::
+              {:ok, pos_integer(), [tuple()], reference() | map()}
+              | {:ok, pos_integer(), [tuple()]}
+              | {:ok, reference()}
+              | {:error, term()}
+
+  @callback stream_body(reference() | pid() | map()) ::
+              {:ok, binary()} | :done | {:error, String.t()}
+
+  @callback close(reference() | pid() | map()) :: :ok
+
+  def request(method, url, headers, "", opts \\ []) do
+    client().request(method, url, headers, "", opts)
+  end
+
+  def stream_body(ref), do: client().stream_body(ref)
+
+  def close(ref), do: client().close(ref)
+
+  defp client do
+    Pleroma.Config.get([Pleroma.ReverseProxy.Client], :hackney)
+  end
+end
similarity index 97%
rename from lib/pleroma/reverse_proxy.ex
rename to lib/pleroma/reverse_proxy/reverse_proxy.ex
index de0f6e1bc6ed73caec4b481bd81565745f89c367..bf31e9cba26a1b2919732b75f3237fe444b9a622 100644 (file)
@@ -146,7 +146,7 @@ defmodule Pleroma.ReverseProxy do
     Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
     method = method |> String.downcase() |> String.to_existing_atom()
 
-    case hackney().request(method, url, headers, "", hackney_opts) do
+    case client().request(method, url, headers, "", hackney_opts) do
       {:ok, code, headers, client} when code in @valid_resp_codes ->
         {:ok, code, downcase_headers(headers), client}
 
@@ -173,7 +173,7 @@ defmodule Pleroma.ReverseProxy do
         halt(conn)
 
       {:error, :closed, conn} ->
-        :hackney.close(client)
+        client().close(client)
         halt(conn)
 
       {:error, error, conn} ->
@@ -181,7 +181,7 @@ defmodule Pleroma.ReverseProxy do
           "#{__MODULE__} request to #{url} failed while reading/chunking: #{inspect(error)}"
         )
 
-        :hackney.close(client)
+        client().close(client)
         halt(conn)
     end
   end
@@ -196,7 +196,7 @@ defmodule Pleroma.ReverseProxy do
              duration,
              Keyword.get(opts, :max_read_duration, @max_read_duration)
            ),
-         {:ok, data} <- hackney().stream_body(client),
+         {:ok, data} <- client().stream_body(client),
          {:ok, duration} <- increase_read_duration(duration),
          sent_so_far = sent_so_far + byte_size(data),
          :ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)),
@@ -378,5 +378,5 @@ defmodule Pleroma.ReverseProxy do
     {:ok, :no_duration_limit, :no_duration_limit}
   end
 
-  defp hackney, do: Pleroma.Config.get(:hackney, :hackney)
+  defp client, do: Pleroma.ReverseProxy.Client
 end
diff --git a/lib/pleroma/uploaders/swift/keystone.ex b/lib/pleroma/uploaders/swift/keystone.ex
deleted file mode 100644 (file)
index dd44c75..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Uploaders.Swift.Keystone do
-  use HTTPoison.Base
-
-  def process_url(url) do
-    Enum.join(
-      [Pleroma.Config.get!([Pleroma.Uploaders.Swift, :auth_url]), url],
-      "/"
-    )
-  end
-
-  def process_response_body(body) do
-    body
-    |> Jason.decode!()
-  end
-
-  def get_token do
-    settings = Pleroma.Config.get(Pleroma.Uploaders.Swift)
-    username = Keyword.fetch!(settings, :username)
-    password = Keyword.fetch!(settings, :password)
-    tenant_id = Keyword.fetch!(settings, :tenant_id)
-
-    case post(
-           "/tokens",
-           make_auth_body(username, password, tenant_id),
-           ["Content-Type": "application/json"],
-           hackney: [:insecure]
-         ) do
-      {:ok, %Tesla.Env{status: 200, body: body}} ->
-        body["access"]["token"]["id"]
-
-      {:ok, %Tesla.Env{status: _}} ->
-        ""
-    end
-  end
-
-  def make_auth_body(username, password, tenant) do
-    Jason.encode!(%{
-      :auth => %{
-        :passwordCredentials => %{
-          :username => username,
-          :password => password
-        },
-        :tenantId => tenant
-      }
-    })
-  end
-end
diff --git a/lib/pleroma/uploaders/swift/swift.ex b/lib/pleroma/uploaders/swift/swift.ex
deleted file mode 100644 (file)
index 2b0f2ad..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Uploaders.Swift.Client do
-  use HTTPoison.Base
-
-  def process_url(url) do
-    Enum.join(
-      [Pleroma.Config.get!([Pleroma.Uploaders.Swift, :storage_url]), url],
-      "/"
-    )
-  end
-
-  def upload_file(filename, body, content_type) do
-    token = Pleroma.Uploaders.Swift.Keystone.get_token()
-
-    case put("#{filename}", body, "X-Auth-Token": token, "Content-Type": content_type) do
-      {:ok, %Tesla.Env{status: 201}} ->
-        {:ok, {:file, filename}}
-
-      {:ok, %Tesla.Env{status: 401}} ->
-        {:error, "Unauthorized, Bad Token"}
-
-      {:error, _} ->
-        {:error, "Swift Upload Error"}
-    end
-  end
-end
diff --git a/lib/pleroma/uploaders/swift/uploader.ex b/lib/pleroma/uploaders/swift/uploader.ex
deleted file mode 100644 (file)
index d122b09..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Uploaders.Swift do
-  @behaviour Pleroma.Uploaders.Uploader
-
-  def get_file(name) do
-    {:ok, {:url, Path.join([Pleroma.Config.get!([__MODULE__, :object_url]), name])}}
-  end
-
-  def put_file(upload) do
-    Pleroma.Uploaders.Swift.Client.upload_file(
-      upload.path,
-      File.read!(upload.tmpfile),
-      upload.content_type
-    )
-  end
-end
index 9be4b1483d3e7e314bcb7e363b3f15227c076f5e..791676ee28e5e9327714d4ef2304e04068fb5e32 100644 (file)
@@ -108,15 +108,25 @@ defmodule Pleroma.User do
   def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
   def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
 
-  def user_info(%User{} = user) do
+  def user_info(%User{} = user, args \\ %{}) do
+    following_count =
+      if args[:following_count], do: args[:following_count], else: following_count(user)
+
+    follower_count =
+      if args[:follower_count], do: args[:follower_count], else: user.info.follower_count
+
     %{
-      following_count: following_count(user),
       note_count: user.info.note_count,
-      follower_count: user.info.follower_count,
       locked: user.info.locked,
       confirmation_pending: user.info.confirmation_pending,
       default_scope: user.info.default_scope
     }
+    |> Map.put(:following_count, following_count)
+    |> Map.put(:follower_count, follower_count)
+  end
+
+  def set_info_cache(user, args) do
+    Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
   end
 
   def restrict_deactivated(query) do
@@ -837,15 +847,12 @@ defmodule Pleroma.User do
   def mutes?(nil, _), do: false
   def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
 
-  def blocks?(user, %{ap_id: ap_id}) do
-    blocks = user.info.blocks
-    domain_blocks = user.info.domain_blocks
+  def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
+    blocks = info.blocks
+    domain_blocks = info.domain_blocks
     %{host: host} = URI.parse(ap_id)
 
-    Enum.member?(blocks, ap_id) ||
-      Enum.any?(domain_blocks, fn domain ->
-        host == domain
-      end)
+    Enum.member?(blocks, ap_id) || Enum.any?(domain_blocks, &(&1 == host))
   end
 
   def subscribed_to?(user, %{ap_id: ap_id}) do
@@ -1004,6 +1011,56 @@ defmodule Pleroma.User do
     )
   end
 
+  @spec sync_follow_counter() :: :ok
+  def sync_follow_counter,
+    do: PleromaJobQueue.enqueue(:background, __MODULE__, [:sync_follow_counters])
+
+  @spec perform(:sync_follow_counters) :: :ok
+  def perform(:sync_follow_counters) do
+    {:ok, _pid} = Agent.start_link(fn -> %{} end, name: :domain_errors)
+    config = Pleroma.Config.get([:instance, :external_user_synchronization])
+
+    :ok = sync_follow_counters(config)
+    Agent.stop(:domain_errors)
+  end
+
+  @spec sync_follow_counters(keyword()) :: :ok
+  def sync_follow_counters(opts \\ []) do
+    users = external_users(opts)
+
+    if length(users) > 0 do
+      errors = Agent.get(:domain_errors, fn state -> state end)
+      {last, updated_errors} = User.Synchronization.call(users, errors, opts)
+      Agent.update(:domain_errors, fn _state -> updated_errors end)
+      sync_follow_counters(max_id: last.id, limit: opts[:limit])
+    else
+      :ok
+    end
+  end
+
+  @spec external_users(keyword()) :: [User.t()]
+  def external_users(opts \\ []) do
+    query =
+      User.Query.build(%{
+        external: true,
+        active: true,
+        order_by: :id,
+        select: [:id, :ap_id, :info]
+      })
+
+    query =
+      if opts[:max_id],
+        do: where(query, [u], u.id > ^opts[:max_id]),
+        else: query
+
+    query =
+      if opts[:limit],
+        do: limit(query, ^opts[:limit]),
+        else: query
+
+    Repo.all(query)
+  end
+
   def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers),
     do:
       PleromaJobQueue.enqueue(:background, __MODULE__, [
index ace9c05f2555257d871113a13be87ddd50374c3f..f9bcc9e19ec5991dffc81d12e35a5c8b6ad06b3b 100644 (file)
@@ -7,7 +7,7 @@ defmodule Pleroma.User.Query do
   User query builder module. Builds query from new query or another user query.
 
     ## Example:
-        query = Pleroma.User.Query(%{nickname: "nickname"})
+        query = Pleroma.User.Query.build(%{nickname: "nickname"})
         another_query = Pleroma.User.Query.build(query, %{email: "email@example.com"})
         Pleroma.Repo.all(query)
         Pleroma.Repo.all(another_query)
@@ -47,7 +47,10 @@ defmodule Pleroma.User.Query do
             friends: User.t(),
             recipients_from_activity: [String.t()],
             nickname: [String.t()],
-            ap_id: [String.t()]
+            ap_id: [String.t()],
+            order_by: term(),
+            select: term(),
+            limit: pos_integer()
           }
           | %{}
 
@@ -141,6 +144,18 @@ defmodule Pleroma.User.Query do
     where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to))
   end
 
+  defp compose_query({:order_by, key}, query) do
+    order_by(query, [u], field(u, ^key))
+  end
+
+  defp compose_query({:select, keys}, query) do
+    select(query, [u], ^keys)
+  end
+
+  defp compose_query({:limit, limit}, query) do
+    limit(query, ^limit)
+  end
+
   defp compose_query(_unsupported_param, query), do: query
 
   defp prepare_tag_criteria(tag, query) do
index ed06c2ab92f2f367a90c7176279b12bb408b8b42..64eb6d2bc484df842a22562b7468d80d807aba86 100644 (file)
@@ -43,6 +43,8 @@ defmodule Pleroma.User.Search do
   defp search_query(query_string, for_user, following) do
     for_user
     |> base_query(following)
+    |> filter_blocked_user(for_user)
+    |> filter_blocked_domains(for_user)
     |> search_subqueries(query_string)
     |> union_subqueries
     |> distinct_query()
@@ -55,6 +57,25 @@ defmodule Pleroma.User.Search do
   defp base_query(_user, false), do: User
   defp base_query(user, true), do: User.get_followers_query(user)
 
+  defp filter_blocked_user(query, %User{info: %{blocks: blocks}})
+       when length(blocks) > 0 do
+    from(q in query, where: not (q.ap_id in ^blocks))
+  end
+
+  defp filter_blocked_user(query, _), do: query
+
+  defp filter_blocked_domains(query, %User{info: %{domain_blocks: domain_blocks}})
+       when length(domain_blocks) > 0 do
+    domains = Enum.join(domain_blocks, ",")
+
+    from(
+      q in query,
+      where: fragment("substring(ap_id from '.*://([^/]*)') NOT IN (?)", ^domains)
+    )
+  end
+
+  defp filter_blocked_domains(query, _), do: query
+
   defp paginate(query, limit, offset) do
     from(q in query, limit: ^limit, offset: ^offset)
   end
@@ -129,7 +150,7 @@ defmodule Pleroma.User.Search do
   @spec fts_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
   defp fts_search_subquery(query, term) do
     processed_query =
-      term
+      String.trim_trailing(term, "@" <> local_domain())
       |> String.replace(~r/\W+/, " ")
       |> String.trim()
       |> String.split()
@@ -171,6 +192,8 @@ defmodule Pleroma.User.Search do
 
   @spec trigram_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
   defp trigram_search_subquery(query, term) do
+    term = String.trim_trailing(term, "@" <> local_domain())
+
     from(
       u in query,
       select_merge: %{
@@ -188,4 +211,6 @@ defmodule Pleroma.User.Search do
     )
     |> User.restrict_deactivated()
   end
+
+  defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
 end
diff --git a/lib/pleroma/user/synchronization.ex b/lib/pleroma/user/synchronization.ex
new file mode 100644 (file)
index 0000000..93660e0
--- /dev/null
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.Synchronization do
+  alias Pleroma.HTTP
+  alias Pleroma.User
+
+  @spec call([User.t()], map(), keyword()) :: {User.t(), map()}
+  def call(users, errors, opts \\ []) do
+    do_call(users, errors, opts)
+  end
+
+  defp do_call([user | []], errors, opts) do
+    updated = fetch_counters(user, errors, opts)
+    {user, updated}
+  end
+
+  defp do_call([user | others], errors, opts) do
+    updated = fetch_counters(user, errors, opts)
+    do_call(others, updated, opts)
+  end
+
+  defp fetch_counters(user, errors, opts) do
+    %{host: host} = URI.parse(user.ap_id)
+
+    info = %{}
+    {following, errors} = fetch_counter(user.ap_id <> "/following", host, errors, opts)
+    info = if following, do: Map.put(info, :following_count, following), else: info
+
+    {followers, errors} = fetch_counter(user.ap_id <> "/followers", host, errors, opts)
+    info = if followers, do: Map.put(info, :follower_count, followers), else: info
+
+    User.set_info_cache(user, info)
+    errors
+  end
+
+  defp available_domain?(domain, errors, opts) do
+    max_retries = Keyword.get(opts, :max_retries, 3)
+    not (Map.has_key?(errors, domain) && errors[domain] >= max_retries)
+  end
+
+  defp fetch_counter(url, host, errors, opts) do
+    with true <- available_domain?(host, errors, opts),
+         {:ok, %{body: body, status: code}} when code in 200..299 <-
+           HTTP.get(
+             url,
+             [{:Accept, "application/activity+json"}]
+           ),
+         {:ok, data} <- Jason.decode(body) do
+      {data["totalItems"], errors}
+    else
+      false ->
+        {nil, errors}
+
+      _ ->
+        {nil, Map.update(errors, host, 1, &(&1 + 1))}
+    end
+  end
+end
diff --git a/lib/pleroma/user/synchronization_worker.ex b/lib/pleroma/user/synchronization_worker.ex
new file mode 100644 (file)
index 0000000..ba9cc35
--- /dev/null
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-onl
+
+defmodule Pleroma.User.SynchronizationWorker do
+  use GenServer
+
+  def start_link do
+    config = Pleroma.Config.get([:instance, :external_user_synchronization])
+
+    if config[:enabled] do
+      GenServer.start_link(__MODULE__, interval: config[:interval])
+    else
+      :ignore
+    end
+  end
+
+  def init(opts) do
+    schedule_next(opts)
+    {:ok, opts}
+  end
+
+  def handle_info(:sync_follow_counters, opts) do
+    Pleroma.User.sync_follow_counter()
+    schedule_next(opts)
+    {:noreply, opts}
+  end
+
+  defp schedule_next(opts) do
+    Process.send_after(self(), :sync_follow_counters, opts[:interval])
+  end
+end
index 3bb8b40b5c0284024ba2558950e1fee312ac965b..543d4bb7d1dd89c7e03ec0df027bb4a78ba769fd 100644 (file)
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
+  alias Pleroma.Web.Federator
 
   import Ecto.Query
 
@@ -22,20 +23,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   @doc """
   Modifies an incoming AP object (mastodon format) to our internal format.
   """
-  def fix_object(object) do
+  def fix_object(object, options \\ []) do
     object
     |> fix_actor
     |> fix_url
     |> fix_attachments
     |> fix_context
-    |> fix_in_reply_to
+    |> fix_in_reply_to(options)
     |> fix_emoji
     |> fix_tag
     |> fix_content_map
     |> fix_likes
     |> fix_addressing
     |> fix_summary
-    |> fix_type
+    |> fix_type(options)
   end
 
   def fix_summary(%{"summary" => nil} = object) do
@@ -164,7 +165,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     object
   end
 
-  def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
+  def fix_in_reply_to(object, options \\ [])
+
+  def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
       when not is_nil(in_reply_to) do
     in_reply_to_id =
       cond do
@@ -182,28 +185,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           ""
       end
 
-    case get_obj_helper(in_reply_to_id) do
-      {:ok, replied_object} ->
-        with %Activity{} = _activity <-
-               Activity.get_create_by_object_ap_id(replied_object.data["id"]) 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"])
-        else
-          e ->
-            Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+    object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
+
+    if Federator.allowed_incoming_reply_depth?(options[:depth]) do
+      case get_obj_helper(in_reply_to_id, options) do
+        {:ok, replied_object} ->
+          with %Activity{} = _activity <-
+                 Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
             object
-        end
+            |> 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"])
+          else
+            e ->
+              Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+              object
+          end
 
-      e ->
-        Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
-        object
+        e ->
+          Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+          object
+      end
+    else
+      object
     end
   end
 
-  def fix_in_reply_to(object), do: object
+  def fix_in_reply_to(object, _options), do: object
 
   def fix_context(object) do
     context = object["context"] || object["conversation"] || Utils.generate_context_id()
@@ -336,8 +345,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def fix_content_map(object), do: object
 
-  def fix_type(%{"inReplyTo" => reply_id} = object) when is_binary(reply_id) do
-    reply = Object.normalize(reply_id)
+  def fix_type(object, options \\ [])
+
+  def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do
+    reply =
+      if Federator.allowed_incoming_reply_depth?(options[:depth]) do
+        Object.normalize(reply_id, true)
+      end
 
     if reply && (reply.data["type"] == "Question" and object["name"]) do
       Map.put(object, "type", "Answer")
@@ -346,7 +360,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
-  def fix_type(object), do: object
+  def fix_type(object, _), do: object
 
   defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
     with true <- id =~ "follows",
@@ -374,9 +388,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
+  def handle_incoming(data, options \\ [])
+
   # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
   # with nil ID.
-  def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data) do
+  def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do
     with context <- data["context"] || Utils.generate_context_id(),
          content <- data["content"] || "",
          %User{} = actor <- User.get_cached_by_ap_id(actor),
@@ -409,15 +425,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   # disallow objects with bogus IDs
-  def handle_incoming(%{"id" => nil}), do: :error
-  def handle_incoming(%{"id" => ""}), do: :error
+  def handle_incoming(%{"id" => nil}, _options), do: :error
+  def handle_incoming(%{"id" => ""}, _options), do: :error
   # length of https:// = 8, should validate better, but good enough for now.
-  def handle_incoming(%{"id" => id}) when not (is_binary(id) and length(id) > 8), do: :error
+  def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8),
+    do: :error
 
   # TODO: validate those with a Ecto scheme
   # - tags
   # - emoji
-  def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
+  def handle_incoming(
+        %{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
+        options
+      )
       when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do
     actor = Containment.get_actor(data)
 
@@ -427,7 +447,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
     with nil <- Activity.get_create_by_object_ap_id(object["id"]),
          {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
-      object = fix_object(data["object"])
+      options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
+      object = fix_object(data["object"], options)
 
       params = %{
         to: data["to"],
@@ -452,7 +473,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   def handle_incoming(
-        %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
+        %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
+        _options
       ) do
     with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
          {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
@@ -503,7 +525,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   def handle_incoming(
-        %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data
+        %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
+        _options
       ) do
     with actor <- Containment.get_actor(data),
          {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
@@ -524,7 +547,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   def handle_incoming(
-        %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data
+        %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
+        _options
       ) do
     with actor <- Containment.get_actor(data),
          {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
@@ -548,7 +572,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   def handle_incoming(
-        %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data
+        %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data,
+        _options
       ) do
     with actor <- Containment.get_actor(data),
          {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -561,7 +586,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   def handle_incoming(
-        %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
+        %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data,
+        _options
       ) do
     with actor <- Containment.get_actor(data),
          {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -576,7 +602,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def handle_incoming(
         %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
-          data
+          data,
+        _options
       )
       when object_type in ["Person", "Application", "Service", "Organization"] do
     with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
@@ -614,7 +641,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   # an error or a tombstone.  This would allow us to verify that a deletion actually took
   # place.
   def handle_incoming(
-        %{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data
+        %{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data,
+        _options
       ) do
     object_id = Utils.get_ap_id(object_id)
 
@@ -635,7 +663,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           "object" => %{"type" => "Announce", "object" => object_id},
           "actor" => _actor,
           "id" => id
-        } = data
+        } = data,
+        _options
       ) do
     with actor <- Containment.get_actor(data),
          {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -653,7 +682,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           "object" => %{"type" => "Follow", "object" => followed},
           "actor" => follower,
           "id" => id
-        } = _data
+        } = _data,
+        _options
       ) do
     with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
          {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
@@ -671,7 +701,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           "object" => %{"type" => "Block", "object" => blocked},
           "actor" => blocker,
           "id" => id
-        } = _data
+        } = _data,
+        _options
       ) do
     with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
          %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
@@ -685,7 +716,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   def handle_incoming(
-        %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data
+        %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
+        _options
       ) do
     with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
          %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
@@ -705,7 +737,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           "object" => %{"type" => "Like", "object" => object_id},
           "actor" => _actor,
           "id" => id
-        } = data
+        } = data,
+        _options
       ) do
     with actor <- Containment.get_actor(data),
          {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -717,10 +750,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
-  def handle_incoming(_), do: :error
+  def handle_incoming(_, _), do: :error
 
-  def get_obj_helper(id) do
-    if object = Object.normalize(id), do: {:ok, object}, else: nil
+  def get_obj_helper(id, options \\ []) do
+    if object = Object.normalize(id, true, options), do: {:ok, object}, else: nil
   end
 
   def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
index 514266ceefaa314484dbf9d840a51a1525195725..4288ea4c838a8cf291394663291246bea3d7c9bd 100644 (file)
@@ -170,14 +170,17 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   Enqueues an activity for federation if it's local
   """
   def maybe_federate(%Activity{local: true} = activity) do
-    priority =
-      case activity.data["type"] do
-        "Delete" -> 10
-        "Create" -> 1
-        _ -> 5
-      end
+    if Pleroma.Config.get!([:instance, :federating]) do
+      priority =
+        case activity.data["type"] do
+          "Delete" -> 10
+          "Create" -> 1
+          _ -> 5
+        end
+
+      Pleroma.Web.Federator.publish(activity, priority)
+    end
 
-    Pleroma.Web.Federator.publish(activity, priority)
     :ok
   end
 
index 498beb56a02df7c05d01466414b1778e7cb2ced1..0a2482a8c85c45224d8d2916f503f8778ed4c381 100644 (file)
@@ -74,7 +74,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   end
 
   def user_show(conn, %{"nickname" => nickname}) do
-    with %User{} = user <- User.get_cached_by_nickname(nickname) do
+    with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
       conn
       |> json(AccountView.render("show.json", %{user: user}))
     else
index 73ccdc582ae97b6da8434c9343629925295966cb..a25f3f1fed39c80f3685b1301a7535df9e4e5a79 100644 (file)
@@ -46,8 +46,10 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
     }
   end
 
-  defp merge_account_views(user) do
+  defp merge_account_views(%User{} = user) do
     Pleroma.Web.MastodonAPI.AccountView.render("account.json", %{user: user})
     |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}))
   end
+
+  defp merge_account_views(_), do: %{}
 end
index f4c9fe28403d4991f6569b7bc91897529fbcd155..f4f9e83e06ab55f71b715301631865bc46b11c00 100644 (file)
@@ -22,6 +22,18 @@ defmodule Pleroma.Web.Federator do
     refresh_subscriptions()
   end
 
+  @doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)"
+  # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
+  def allowed_incoming_reply_depth?(depth) do
+    max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth])
+
+    if max_replies_depth do
+      (depth || 1) <= max_replies_depth
+    else
+      true
+    end
+  end
+
   # Client API
 
   def incoming_doc(doc) do
index ceb88511b0e5e0d03d8dfc922f4ce9651c3f5607..0d3a878bb11511190c30b758a45cfa99fe02193d 100644 (file)
@@ -167,6 +167,69 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+    change = Changeset.change(user, %{avatar: nil})
+    {:ok, user} = User.update_and_set_cache(change)
+    CommonAPI.update(user)
+
+    json(conn, %{url: nil})
+  end
+
+  def update_avatar(%{assigns: %{user: user}} = conn, params) do
+    {:ok, object} = ActivityPub.upload(params, type: :avatar)
+    change = Changeset.change(user, %{avatar: object.data})
+    {:ok, user} = User.update_and_set_cache(change)
+    CommonAPI.update(user)
+    %{"url" => [%{"href" => href} | _]} = object.data
+
+    json(conn, %{url: href})
+  end
+
+  def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
+    with new_info <- %{"banner" => %{}},
+         info_cng <- User.Info.profile_update(user.info, new_info),
+         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         {:ok, user} <- User.update_and_set_cache(changeset) do
+      CommonAPI.update(user)
+
+      json(conn, %{url: nil})
+    end
+  end
+
+  def update_banner(%{assigns: %{user: user}} = conn, params) do
+    with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
+         new_info <- %{"banner" => object.data},
+         info_cng <- User.Info.profile_update(user.info, new_info),
+         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         {:ok, user} <- User.update_and_set_cache(changeset) do
+      CommonAPI.update(user)
+      %{"url" => [%{"href" => href} | _]} = object.data
+
+      json(conn, %{url: href})
+    end
+  end
+
+  def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+    with new_info <- %{"background" => %{}},
+         info_cng <- User.Info.profile_update(user.info, new_info),
+         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         {:ok, _user} <- User.update_and_set_cache(changeset) do
+      json(conn, %{url: nil})
+    end
+  end
+
+  def update_background(%{assigns: %{user: user}} = conn, params) do
+    with {:ok, object} <- ActivityPub.upload(params, type: :background),
+         new_info <- %{"background" => object.data},
+         info_cng <- User.Info.profile_update(user.info, new_info),
+         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         {:ok, _user} <- User.update_and_set_cache(changeset) do
+      %{"url" => [%{"href" => href} | _]} = object.data
+
+      json(conn, %{url: href})
+    end
+  end
+
   def verify_credentials(%{assigns: %{user: user}} = conn, _) do
     chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
 
index 0d1e2355d203d73350fea6adbf5b8036d9956df9..efa9cc78824eb00add3735568e57a89943b5d69e 100644 (file)
@@ -17,8 +17,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
   plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
 
   def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
-    accounts = User.search(query, search_options(params, user))
-    statuses = Activity.search(user, query)
+    accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, [])
+    statuses = with_fallback(fn -> Activity.search(user, query) end, [])
     tags_path = Web.base_url() <> "/tag/"
 
     tags =
@@ -40,8 +40,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
   end
 
   def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
-    accounts = User.search(query, search_options(params, user))
-    statuses = Activity.search(user, query)
+    accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, [])
+    statuses = with_fallback(fn -> Activity.search(user, query) end, [])
 
     tags =
       query
@@ -76,4 +76,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
       for_user: user
     ]
   end
+
+  defp with_fallback(f, fallback) do
+    try do
+      f.()
+    rescue
+      error ->
+        Logger.error("#{__MODULE__} search error: #{inspect(error)}")
+        fallback
+    end
+  end
 end
index 6836d331a7ae89c5d2e58102f68ae55a26c5f698..ec582b919c626aa5c8f1e9330a03c0d1afafac8d 100644 (file)
@@ -104,7 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       id: to_string(activity.id),
       uri: activity_object.data["id"],
       url: activity_object.data["id"],
-      account: AccountView.render("account.json", %{user: user}),
+      account: AccountView.render("account.json", %{user: user, for: opts[:for]}),
       in_reply_to_id: nil,
       in_reply_to_account_id: nil,
       reblog: reblogged,
@@ -221,7 +221,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       id: to_string(activity.id),
       uri: object.data["id"],
       url: url,
-      account: AccountView.render("account.json", %{user: user}),
+      account: AccountView.render("account.json", %{user: user, for: opts[:for]}),
       in_reply_to_id: reply_to && to_string(reply_to.id),
       in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
       reblog: nil,
index 3299e1721948a754c3fba44dd4d4863aa5be3ae3..dbd3542eadcc57929addcbbad7f682fa95d34d2d 100644 (file)
@@ -29,9 +29,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
 
   def init(%{qs: qs} = req, state) do
     with params <- :cow_qs.parse_qs(qs),
+         sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil),
          access_token <- List.keyfind(params, "access_token", 0),
          {_, stream} <- List.keyfind(params, "stream", 0),
-         {:ok, user} <- allow_request(stream, access_token),
+         {:ok, user} <- allow_request(stream, [access_token, sec_websocket]),
          topic when is_binary(topic) <- expand_topic(stream, params) do
       {:cowboy_websocket, req, %{user: user, topic: topic}, %{idle_timeout: @timeout}}
     else
@@ -84,13 +85,21 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
   end
 
   # Public streams without authentication.
-  defp allow_request(stream, nil) when stream in @anonymous_streams do
+  defp allow_request(stream, [nil, nil]) when stream in @anonymous_streams do
     {:ok, nil}
   end
 
   # Authenticated streams.
-  defp allow_request(stream, {"access_token", access_token}) when stream in @streams do
-    with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token),
+  defp allow_request(stream, [access_token, sec_websocket]) when stream in @streams do
+    token =
+      with {"access_token", token} <- access_token do
+        token
+      else
+        _ -> sec_websocket
+      end
+
+    with true <- is_bitstring(token),
+         %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
          user = %User{} <- User.get_cached_by_id(user_id) do
       {:ok, user}
     else
index cee6d8481a33942d1d41d51a0cb16ba6fb98ac0b..dd8888a021635357b4fe88c1bb2748517c7c6062 100644 (file)
@@ -33,20 +33,7 @@ defmodule Pleroma.Web.MediaProxy do
 
   def encode_url(url) do
     secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
-
-    # Must preserve `%2F` for compatibility with S3
-    # https://git.pleroma.social/pleroma/pleroma/issues/580
-    replacement = get_replacement(url, ":2F:")
-
-    # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
-    base64 =
-      url
-      |> String.replace("%2F", replacement)
-      |> URI.decode()
-      |> URI.encode()
-      |> String.replace(replacement, "%2F")
-      |> Base.url_encode64(@base64_opts)
-
+    base64 = Base.url_encode64(url, @base64_opts)
     sig = :crypto.hmac(:sha, secret, base64)
     sig64 = sig |> Base.url_encode64(@base64_opts)
 
@@ -80,12 +67,4 @@ defmodule Pleroma.Web.MediaProxy do
     |> Enum.filter(fn value -> value end)
     |> Path.join()
   end
-
-  defp get_replacement(url, replacement) do
-    if String.contains?(url, replacement) do
-      get_replacement(url, replacement <> replacement)
-    else
-      replacement
-    end
-  end
 end
index 357b80a2d50e7959bd8d5b8dfe36d2468f25def9..4033ec38ff17d2592777565c47c8dc9c62686cb8 100644 (file)
@@ -121,4 +121,6 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
       acc ++ rendered_tags
     end)
   end
+
+  defp build_attachments(_), do: []
 end
index 040b872e7034d840705ffb324628d223a9f197b3..8dd01e0d5f33ff55391dc4cd77ac1c52ff2b157e 100644 (file)
@@ -117,6 +117,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
     end)
   end
 
+  defp build_attachments(_id, _object), do: []
+
   defp player_url(id) do
     Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
   end
index 32be430b7691aa31ab695893456362a3d953cafe..869dda5c5cd3d44eac41aa853ff23d92b1332e8e 100644 (file)
@@ -162,7 +162,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
         accountActivationRequired: Config.get([:instance, :account_activation_required], false),
         invitesEnabled: Config.get([:instance, :invites_enabled], false),
         features: features,
-        restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames])
+        restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
+        skipThreadContainment: Config.get([:instance, :skip_thread_containment], false)
       }
     }
   end
index ec6e5cfaf3226cdb61e4ba7b1e72d3bba161a532..8e0adad91f38b7c87008f37b932e426934eb0dd8 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.Federator
   alias Pleroma.Web.OStatus
   alias Pleroma.Web.XML
 
@@ -88,14 +89,15 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
     Map.put(note, "external_url", url)
   end
 
-  def fetch_replied_to_activity(entry, in_reply_to) do
+  def fetch_replied_to_activity(entry, in_reply_to, options \\ []) do
     with %Activity{} = activity <- Activity.get_create_by_object_ap_id(in_reply_to) do
       activity
     else
       _e ->
-        with in_reply_to_href when not is_nil(in_reply_to_href) <-
+        with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
+             in_reply_to_href when not is_nil(in_reply_to_href) <-
                XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry),
-             {:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href) do
+             {:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href, options) do
           activity
         else
           _e -> nil
@@ -104,7 +106,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
   end
 
   # TODO: Clean this up a bit.
-  def handle_note(entry, doc \\ nil) do
+  def handle_note(entry, doc \\ nil, options \\ []) do
     with id <- XML.string_from_xpath("//id", entry),
          activity when is_nil(activity) <- Activity.get_create_by_object_ap_id_with_object(id),
          [author] <- :xmerl_xpath.string('//author[1]', doc),
@@ -112,7 +114,8 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
          content_html <- OStatus.get_content(entry),
          cw <- OStatus.get_cw(entry),
          in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),
-         in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to),
+         options <- Keyword.put(options, :depth, (options[:depth] || 0) + 1),
+         in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to, options),
          in_reply_to_object <-
            (in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil,
          in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to,
index 6ed089d8458f6e51aacf0ca3da25462559340faf..502410c83cfd7ed0bf7620970f10f1f996bfd021 100644 (file)
@@ -54,7 +54,7 @@ defmodule Pleroma.Web.OStatus do
     "#{Web.base_url()}/ostatus_subscribe?acct={uri}"
   end
 
-  def handle_incoming(xml_string) do
+  def handle_incoming(xml_string, options \\ []) do
     with doc when doc != :error <- parse_document(xml_string) do
       with {:ok, actor_user} <- find_make_or_update_user(doc),
            do: Pleroma.Instances.set_reachable(actor_user.ap_id)
@@ -91,10 +91,12 @@ defmodule Pleroma.Web.OStatus do
               _ ->
                 case object_type do
                   'http://activitystrea.ms/schema/1.0/note' ->
-                    with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
+                    with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options),
+                         do: activity
 
                   'http://activitystrea.ms/schema/1.0/comment' ->
-                    with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity
+                    with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options),
+                         do: activity
 
                   _ ->
                     Logger.error("Couldn't parse incoming document")
@@ -359,7 +361,7 @@ defmodule Pleroma.Web.OStatus do
     end
   end
 
-  def fetch_activity_from_atom_url(url) do
+  def fetch_activity_from_atom_url(url, options \\ []) do
     with true <- String.starts_with?(url, "http"),
          {:ok, %{body: body, status: code}} when code in 200..299 <-
            HTTP.get(
@@ -367,7 +369,7 @@ defmodule Pleroma.Web.OStatus do
              [{:Accept, "application/atom+xml"}]
            ) do
       Logger.debug("Got document from #{url}, handling...")
-      handle_incoming(body)
+      handle_incoming(body, options)
     else
       e ->
         Logger.debug("Couldn't get #{url}: #{inspect(e)}")
@@ -375,13 +377,13 @@ defmodule Pleroma.Web.OStatus do
     end
   end
 
-  def fetch_activity_from_html_url(url) do
+  def fetch_activity_from_html_url(url, options \\ []) do
     Logger.debug("Trying to fetch #{url}")
 
     with true <- String.starts_with?(url, "http"),
          {:ok, %{body: body}} <- HTTP.get(url, []),
          {:ok, atom_url} <- get_atom_url(body) do
-      fetch_activity_from_atom_url(atom_url)
+      fetch_activity_from_atom_url(atom_url, options)
     else
       e ->
         Logger.debug("Couldn't get #{url}: #{inspect(e)}")
@@ -389,11 +391,11 @@ defmodule Pleroma.Web.OStatus do
     end
   end
 
-  def fetch_activity_from_url(url) do
-    with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url) do
+  def fetch_activity_from_url(url, options \\ []) do
+    with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url, options) do
       {:ok, activities}
     else
-      _e -> fetch_activity_from_html_url(url)
+      _e -> fetch_activity_from_html_url(url, options)
     end
   rescue
     e ->
index 9cb8db7fdfe8343afc227e012a4c7bc85c7c5f57..b25e4ffd87db0000fcc5bf98bfbf016933321297 100644 (file)
@@ -322,6 +322,10 @@ defmodule Pleroma.Web.Router do
 
       patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
 
+      patch("/accounts/update_avatar", MastodonAPIController, :update_avatar)
+      patch("/accounts/update_banner", MastodonAPIController, :update_banner)
+      patch("/accounts/update_background", MastodonAPIController, :update_background)
+
       post("/statuses", MastodonAPIController, :post_status)
       delete("/statuses/:id", MastodonAPIController, :delete_status)
 
@@ -726,6 +730,7 @@ end
 
 defmodule Fallback.RedirectController do
   use Pleroma.Web, :controller
+  require Logger
   alias Pleroma.User
   alias Pleroma.Web.Metadata
 
@@ -752,7 +757,20 @@ defmodule Fallback.RedirectController do
 
   def redirector_with_meta(conn, params) do
     {:ok, index_content} = File.read(index_file_path())
-    tags = Metadata.build_tags(params)
+
+    tags =
+      try do
+        Metadata.build_tags(params)
+      rescue
+        e ->
+          Logger.error(
+            "Metadata rendering for #{conn.request_path} failed.\n" <>
+              Exception.format(:error, e, __STACKTRACE__)
+          )
+
+          ""
+      end
+
     response = String.replace(index_content, "<!--server-generated-meta-->", tags)
 
     conn
index 6cf107d172c48164f14600d9df0f5ed14bf64a84..45ef7be3d277bdc14f7468012ad3ad5f28794e92 100644 (file)
@@ -456,6 +456,16 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
     end
   end
 
+  def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+    change = Changeset.change(user, %{avatar: nil})
+    {:ok, user} = User.update_and_set_cache(change)
+    CommonAPI.update(user)
+
+    conn
+    |> put_view(UserView)
+    |> render("show.json", %{user: user, for: user})
+  end
+
   def update_avatar(%{assigns: %{user: user}} = conn, params) do
     {:ok, object} = ActivityPub.upload(params, type: :avatar)
     change = Changeset.change(user, %{avatar: object.data})
@@ -467,6 +477,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
     |> render("show.json", %{user: user, for: user})
   end
 
+  def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
+    with new_info <- %{"banner" => %{}},
+         info_cng <- User.Info.profile_update(user.info, new_info),
+         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         {:ok, user} <- User.update_and_set_cache(changeset) do
+      CommonAPI.update(user)
+      response = %{url: nil} |> Jason.encode!()
+
+      conn
+      |> json_reply(200, response)
+    end
+  end
+
   def update_banner(%{assigns: %{user: user}} = conn, params) do
     with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
          new_info <- %{"banner" => object.data},
@@ -482,6 +505,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
     end
   end
 
+  def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+    with new_info <- %{"background" => %{}},
+         info_cng <- User.Info.profile_update(user.info, new_info),
+         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         {:ok, _user} <- User.update_and_set_cache(changeset) do
+      response = %{url: nil} |> Jason.encode!()
+
+      conn
+      |> json_reply(200, response)
+    end
+  end
+
   def update_background(%{assigns: %{user: user}} = conn, params) do
     with {:ok, object} <- ActivityPub.upload(params, type: :background),
          new_info <- %{"background" => object.data},
diff --git a/mix.exs b/mix.exs
index 45717ba07d23f2998c39e924ed0e77a047040559..b61bde168b60acf81dfa8f80cdc453416d50977c 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -109,7 +109,6 @@ defmodule Pleroma.Mixfile do
       {:phoenix_html, "~> 2.10"},
       {:calendar, "~> 0.17.4"},
       {:cachex, "~> 3.0.2"},
-      {:httpoison, "~> 1.2.0"},
       {:poison, "~> 3.0", override: true},
       {:tesla, "~> 1.2"},
       {:jason, "~> 1.0"},
@@ -154,7 +153,8 @@ defmodule Pleroma.Mixfile do
       {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
       {:ex_rated, "~> 1.3"},
       {:plug_static_index_html, "~> 1.0.0"},
-      {:excoveralls, "~> 0.11.1", only: :test}
+      {:excoveralls, "~> 0.11.1", only: :test},
+      {:mox, "~> 0.5", only: :test}
     ] ++ oauth_deps()
   end
 
@@ -177,10 +177,14 @@ defmodule Pleroma.Mixfile do
   # Builds a version string made of:
   # * the application version
   # * a pre-release if ahead of the tag: the describe string (-count-commithash)
-  # * build info:
+  # * branch name
+  # * build metadata:
   #   * a build name if `PLEROMA_BUILD_NAME` or `:pleroma, :build_name` is defined
   #   * the mix environment if different than prod
   defp version(version) do
+    identifier_filter = ~r/[^0-9a-z\-]+/i
+
+    # Pre-release version, denoted from patch version with a hyphen
     {git_tag, git_pre_release} =
       with {tag, 0} <-
              System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true),
@@ -201,6 +205,19 @@ defmodule Pleroma.Mixfile do
       )
     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"]),
+           branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
+           true <- branch_name != "master" do
+        branch_name =
+          branch_name
+          |> String.trim()
+          |> String.replace(identifier_filter, "-")
+
+        "." <> branch_name
+      end
+
     build_name =
       cond do
         name = Application.get_env(:pleroma, :build_name) -> name
@@ -209,28 +226,26 @@ defmodule Pleroma.Mixfile do
       end
 
     env_name = if Mix.env() != :prod, do: to_string(Mix.env())
+    env_override = System.get_env("PLEROMA_BUILD_ENV")
 
-    build =
+    env_name =
+      case env_override do
+        nil -> env_name
+        env_override when env_override in ["", "prod"] -> nil
+        env_override -> env_override
+      end
+
+    # Build metadata, denoted with a plus sign
+    build_metadata =
       [build_name, env_name]
       |> Enum.filter(fn string -> string && string != "" end)
-      |> Enum.join("-")
+      |> Enum.join(".")
       |> (fn
             "" -> nil
-            string -> "+" <> string
+            string -> "+" <> String.replace(string, identifier_filter, "-")
           end).()
 
-    branch_name =
-      with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
-           branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name,
-           true <- branch_name != "master" do
-        branch_name =
-          String.trim(branch_name)
-          |> String.replace(~r/[^0-9a-z\-\.]+/i, "-")
-
-        "-" <> branch_name
-      end
-
-    [version, git_pre_release, branch_name, build]
+    [version, git_pre_release, branch_name, build_metadata]
     |> Enum.filter(fn string -> string && string != "" end)
     |> Enum.join()
   end
index 02932b2c067cc673ff88d31fb64d6e7e39354a97..addd72e4bf6498400206e54ec55ca844cbb376b6 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -57,6 +57,7 @@
   "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
   "mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
   "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
+  "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
   "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
   "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
   "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
-  "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
   "postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
   "prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"},
   "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
   "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
   "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
   "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
-  "prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.3", "657386e8f142fc817347d95c1f3a05ab08710f7df9e7f86db6facaed107ed929", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
   "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
   "quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"},
   "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
index 2d2f7fbf02bc2407b65fa6698b17962c87b9dd8b..089964a26eaadaf57397008c35941cee51769838 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreatePleroma.User do
   use Ecto.Migration
 
   def change do
-    create table(:users) do
+    create_if_not_exists table(:users) do
       add :email, :string
       add :password_hash, :string
       add :name, :string
index 6e875ae43f866762e8301fca7d8d149a0386875d..f5c872721d2bef6f336c2b302b9371d8bbd44810 100644 (file)
@@ -2,13 +2,13 @@ defmodule Pleroma.Repo.Migrations.CreatePleroma.Activity do
   use Ecto.Migration
 
   def change do
-    create table(:activities) do
+    create_if_not_exists table(:activities) do
       add :data, :map
 
       timestamps()
     end
 
-    create index(:activities, [:data], using: :gin)
+    create_if_not_exists index(:activities, [:data], using: :gin)
 
   end
 end
index b8bd4974730f67090d94c1f1d475f2c61ef8c5d3..b184672ad6a578b653a8469397f8efa861338994 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreatePleroma.Object do
   use Ecto.Migration
 
   def change do
-    create table(:objects) do
+    create_if_not_exists table(:objects) do
       add :data, :map
 
       timestamps()
index 30ed61f514b267537d25d8790c6a60426600dfe7..25e308533ab394db7a12003f80d8f861f733c672 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddIndexToObjects do
   use Ecto.Migration
 
   def change do
-    create index(:objects, [:data], using: :gin)
+    create_if_not_exists index(:objects, [:data], using: :gin)
   end
 end
index 361ca04daf6297ae12f5579c5102dd8b0991930c..42da88954707afb17e05d0a1322ec692ed09839a 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.AddUniqueIndexToEmailAndNickname do
   use Ecto.Migration
 
   def change do
-    create unique_index(:users, [:email])
-    create unique_index(:users, [:nickname])
+    create_if_not_exists unique_index(:users, [:email])
+    create_if_not_exists unique_index(:users, [:nickname])
   end
 end
index fe2fa230414a0cb7d78f7924e3e8cf6a3700b824..2432803781ef368bb97f0a13845667298afca246 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateWebsubServerSubscription do
   use Ecto.Migration
 
   def change do
-    create table(:websub_server_subscriptions) do
+    create_if_not_exists table(:websub_server_subscriptions) do
       add :topic, :string
       add :callback, :string
       add :secret, :string
index 89d3af7aee5ff7ac67c47c44b50f3fc29213c270..4b79d7506203ef15067a1dfa47d32d72458e568c 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateWebsubClientSubscription do
   use Ecto.Migration
 
   def change do
-    create table(:websub_client_subscriptions) do
+    create_if_not_exists table(:websub_client_subscriptions) do
       add :topic, :string
       add :secret, :string
       add :valid_until, :naive_datetime_usec
index 1472b60b4cd8a369e3083e834d297918716677ae..f5e5cd269809e708198d22db35c502831f8c78c8 100644 (file)
@@ -1,10 +1,12 @@
 defmodule Pleroma.Repo.Migrations.AddIdContraintsToActivitiesAndObjectsPartTwo do
   use Ecto.Migration
 
-  def change do
+  def up do
     drop_if_exists index(:objects, ["(data->>\"id\")"], name: :objects_unique_apid_index)
     drop_if_exists index(:activities, ["(data->>\"id\")"], name: :activities_unique_apid_index)
-    create unique_index(:objects, ["(data->>'id')"], name: :objects_unique_apid_index)
-    create unique_index(:activities, ["(data->>'id')"], name: :activities_unique_apid_index)
+    create_if_not_exists unique_index(:objects, ["(data->>'id')"], name: :objects_unique_apid_index)
+    create_if_not_exists unique_index(:activities, ["(data->>'id')"], name: :activities_unique_apid_index)
   end
+
+  def down, do: :ok
 end
index 088d68f675a60b979b5899781b7991efaccbee68..cebc11d217965e0568ba982f27127cfd1015317f 100644 (file)
@@ -6,6 +6,6 @@ defmodule Pleroma.Repo.Migrations.AddLocalFieldToActivities do
       add :local, :boolean, default: true
     end
 
-    create index(:activities, [:local])
+    create_if_not_exists index(:activities, [:local])
   end
 end
index 864b5e47dab81fb64ee94c6163d2e2e40e8f67d0..1b7e33b70eda8da9f40b1ecce2b42c125b4767e4 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddUniqueIndexToAPID do
   use Ecto.Migration
 
   def change do
-    create unique_index(:users, [:ap_id])
+    create_if_not_exists unique_index(:users, [:ap_id])
   end
 end
index c7565946e022b342a6f794789d3517e396529f05..9a67727e9965830d39e9c2c5bc00b7aacbac855e 100644 (file)
@@ -1,19 +1,31 @@
 defmodule Pleroma.Repo.Migrations.CaseInsensivtivity do
   use Ecto.Migration
 
+  # Two-steps alters are intentional.
+  # When alter of 2 columns is done in a single operation,
+  # inconsistent failures happen because of index on `email` column.
+
   def up do
-    execute ("create extension if not exists citext")
+    execute("create extension if not exists citext")
+
+    alter table(:users) do
+      modify(:email, :citext)
+    end
+
     alter table(:users) do
-      modify :email, :citext
-      modify :nickname, :citext
+      modify(:nickname, :citext)
     end
   end
 
   def down do
     alter table(:users) do
-      modify :email, :string
-      modify :nickname, :string
+      modify(:email, :string)
     end
-    execute ("drop extension if exists citext")
+
+    alter table(:users) do
+      modify(:nickname, :string)
+    end
+
+    execute("drop extension if exists citext")
   end
 end
index f8c14b1175ca44d891af06a2ef59a4cedaed25bd..9188f4beef691aa70524a6d2b8bff87050fbf564 100644 (file)
@@ -1,9 +1,16 @@
 defmodule Pleroma.Repo.Migrations.LongerBios do
   use Ecto.Migration
 
-  def change do
+  def up do
     alter table(:users) do
       modify :bio, :text
     end
   end
+
+  def down do
+    alter table(:users) do
+      modify :bio, :string
+    end
+  end
+
 end
index 85feabeebf6886b582bbfe56df1180598706cd81..e7d41eac449a50bd7315aae9bd926b8aaee66338 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.RemoveActivitiesIndex do
   use Ecto.Migration
 
   def change do
-    drop index(:activities, [:data])
+    drop_if_exists index(:activities, [:data])
   end
 end
index df0b8391e50e544cd94ae6d7506984a082d71004..5c312b8f43ec15868c59b40b2a3e98de20614b48 100644 (file)
@@ -3,6 +3,6 @@ defmodule Pleroma.Repo.Migrations.AddObjectActivityIndex do
 
   def change do
     # This was wrong, now a noop
-    # create index(:objects, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index)
+    # create_if_not_exists index(:objects, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index)
   end
 end
index 8d5a95c45e9707b2b1333ba20db9bec3db8a5b38..c95218fad4ee454b10a9ad3eda9830e9d3e50789 100644 (file)
@@ -3,6 +3,6 @@ defmodule Pleroma.Repo.Migrations.AddObjectActivityIndexPartTwo do
 
   def change do
     drop_if_exists index(:objects, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index)
-    create index(:activities, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index)
+    create_if_not_exists index(:activities, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index)
   end
 end
index 82c64396f3090bcd68b76999e4e793c3d154bc2b..807fe37287d48b4ec874f0e13572cbf7aafb7d50 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddActorIndexToActivity do
   use Ecto.Migration
 
   def change do
-    create index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index)
+    create_if_not_exists index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index)
   end
 end
index d3dd317dd6082cdd3e3b220b0d704c71c5eb8c11..ccd5e3fe2091579f89ddbcaf0ac30d853f3055e9 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.AddMastodonApps do
   use Ecto.Migration
 
   def change do
-    create table(:apps) do
+    create_if_not_exists table(:apps) do
       add :client_name, :string
       add :redirect_uris, :string
       add :scopes, :string
index ead1d023e06f9bf0de991bb9e58f4d27e5908ccd..63b25c53722c4775cbde6f9231f5ce35d88f4123 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateOAuthAuthorizations do
   use Ecto.Migration
 
   def change do
-    create table(:oauth_authorizations) do
+    create_if_not_exists table(:oauth_authorizations) do
       add :app_id, references(:apps)
       add :user_id, references(:users)
       add :token, :string
index ed56bbf364ed93bbbb8b129e49a468bd2d2224fc..08471bbf8b4a6d0495ee05c2ac3105ee91c72188 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateOAuthToken do
   use Ecto.Migration
 
   def change do
-    create table(:oauth_tokens) do
+    create_if_not_exists table(:oauth_tokens) do
       add :app_id, references(:apps)
       add :user_id, references(:users)
       add :token, :string
index 5be809fb8ae0ac0fdbd0b1ea0f7755971998d4a3..50de9c5f126e09297229bdd8734d416f47bf5fd0 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateNotifications do
   use Ecto.Migration
 
   def change do
-    create table(:notifications) do
+    create_if_not_exists table(:notifications) do
       add :user_id, references(:users, on_delete: :delete_all)
       add :activity_id, references(:activities, on_delete: :delete_all)
       add :seen, :boolean, default: false
@@ -10,6 +10,6 @@ defmodule Pleroma.Repo.Migrations.CreateNotifications do
       timestamps()
     end
 
-    create index(:notifications, [:user_id])
+    create_if_not_exists index(:notifications, [:user_id])
   end
 end
index 2d9be3aabb3b830239d3afd227d3ce73ba949dd2..dde0f945fa3f3ec46345ff90968efcc22fc6d22e 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreatePasswordResetTokens do
   use Ecto.Migration
 
   def change do
-    create table(:password_reset_tokens) do
+    create_if_not_exists table(:password_reset_tokens) do
       add :token, :string
       add :user_id, references(:users)
       add :used, :boolean, default: false
index 2d8b60a918c1045af786468779a21b99246d43de..fb5f80c98f61027b5fc9215c1228f3dcece3a9b4 100644 (file)
@@ -12,7 +12,7 @@ defmodule Pleroma.Repo.Migrations.AddActorToActivity do
   end
 
   def down do
-    drop index(:activities, [:actor, "id DESC NULLS LAST"])
+    drop_if_exists index(:activities, [:actor, "id DESC NULLS LAST"])
     alter table(:activities) do
       remove :actor
     end
index 25716be213693c2036862498a340e4a7f7fa1266..3438bbbc498c1f424e1c19fb56baeec291e80eea 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddLocalIndexToUser do
   use Ecto.Migration
 
   def change do
-    create index(:users, [:local])
+    create_if_not_exists index(:users, [:local])
   end
 end
index 7bce78108df4aa1bda4f0d3596f25eb496559b05..4520b398eaf6090bff43a8fdbd7243e760a7a6f0 100644 (file)
@@ -6,6 +6,6 @@ defmodule Pleroma.Repo.Migrations.AddRecipientsToActivities do
       add :recipients, {:array, :string}
     end
 
-    create index(:activities, [:recipients], using: :gin)
+    create_if_not_exists index(:activities, [:recipients], using: :gin)
   end
 end
index 1fcc0dabb07d06fb5f359ccfc752c4376de80b54..87de64ca5a199ebda89ecc8fa674c3729dbf18b4 100644 (file)
@@ -18,4 +18,6 @@ defmodule Pleroma.Repo.Migrations.FillRecipientsInActivities do
       end)
     end
   end
+
+  def down, do: :ok
 end
index 98ca7d9d72c86030797dc8191d45a13915f265e7..5a8f8f6697b690f35884613947f3e0327f77b2b7 100644 (file)
@@ -1,7 +1,7 @@
 defmodule Pleroma.Repo.Migrations.MakeFollowingPostgresArray do
   use Ecto.Migration
 
-  def change do
+  def up do
     alter table(:users) do
       add :following_temp, {:array, :string}
     end
@@ -15,4 +15,6 @@ defmodule Pleroma.Repo.Migrations.MakeFollowingPostgresArray do
     end
     rename table(:users), :following_temp, to: :following
   end
+
+  def down, do: :ok
 end
index 70f2c9fe66b743e90f95505329cf0a13942713cb..35c4ce62f338e767d1f61fcbf2d03671ba813b10 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.DropLocalIndexOnActivities do
   use Ecto.Migration
 
   def change do
-    drop index(:users, [:local])
+    drop_if_exists index(:users, [:local])
   end
 end
index 2da65689cc6ae9e5a3b7464dacc065f071fc2a46..7556336eda1fbcc5725bd25476b8c33dedbc0a10 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.ActuallyDropLocalIndex do
   use Ecto.Migration
 
   def change do
-    create index(:users, [:local])
+    create_if_not_exists index(:users, [:local])
     drop_if_exists index("activities", :local)
   end
 end
index 64c62250e2502dd00f87147959f29eccfc726d5c..9d3ce50b34d1536117ade4825a38ab78bba110e9 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateLists do
   use Ecto.Migration
 
   def change do
-    create table(:lists) do
+    create_if_not_exists table(:lists) do
       add :user_id, references(:users, on_delete: :delete_all)
       add :title, :string
       add :following, {:array, :string}
@@ -10,6 +10,6 @@ defmodule Pleroma.Repo.Migrations.CreateLists do
       timestamps()
     end
 
-    create index(:lists, [:user_id])
+    create_if_not_exists index(:lists, [:user_id])
   end
 end
index abfa4b3cc41fcc8aac91f5dfa1a8962161082c0a..58622a87e538c7bb125a7d32b6f69d84d2b226bf 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.CreateUserTrigramIndex do
   use Ecto.Migration
 
   def change do
-    create index(:users, ["(nickname || name) gist_trgm_ops"], name: :users_trigram_index, using: :gist)
+    create_if_not_exists index(:users, ["(nickname || name) gist_trgm_ops"], name: :users_trigram_index, using: :gist)
   end
 end
index d6603e916100691f52db5b90ad2c25eca0a4d951..86b8de30a3e54e71b2c6d0eb428ad0ce469de4ba 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddListFollowIndex do
   use Ecto.Migration
 
   def change do
-    create index(:lists, [:following])
+    create_if_not_exists index(:lists, [:following])
   end
 end
index d0a1cf784791050210cfedede38f87b608bdd97c..faee379f0fc929d50db603dd280add4db885507a 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateUserInviteTokens do
   use Ecto.Migration
 
   def change do
-    create table(:user_invite_tokens) do
+    create_if_not_exists table(:user_invite_tokens) do
       add :token, :string
       add :used, :boolean, default: false
 
index 8e7129f342c3c9cd24fbce573e0c7226306c30f3..541cf46a10621f796bdd8d0b9c2995a48d030125 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateFilters do
   use Ecto.Migration
 
   def change do
-    create table(:filters) do
+    create_if_not_exists table(:filters) do
       add :user_id, references(:users, on_delete: :delete_all)
       add :filter_id, :integer
       add :hide, :boolean
@@ -14,7 +14,7 @@ defmodule Pleroma.Repo.Migrations.CreateFilters do
       timestamps()
     end
 
-    create index(:filters, [:user_id])
-    create index(:filters, [:phrase], where: "hide = true", name: :hided_phrases_index)
+    create_if_not_exists index(:filters, [:user_id])
+    create_if_not_exists index(:filters, [:phrase], where: "hide = true", name: :hided_phrases_index)
   end
 end
index 96af412f0afbb318c2e3436b1cdb4a491299012c..af9d521c0f17ba2145e4b5348e809c33c25dcff1 100644 (file)
@@ -7,7 +7,7 @@ defmodule Pleroma.Repo.Migrations.AddRecipientsToAndCcFieldsToActivities do
       add :recipients_cc, {:array, :string}
     end
 
-    create index(:activities, [:recipients_to], using: :gin)
-    create index(:activities, [:recipients_cc], using: :gin)
+    create_if_not_exists index(:activities, [:recipients_to], using: :gin)
+    create_if_not_exists index(:activities, [:recipients_cc], using: :gin)
   end
 end
index f6c622e3ec8c4007a4bd611e95d2b42e7b9abee2..9d31f67790689d813bac798e06ef63ec09ad476f 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.ActivitiesAddToCcIndices do
   use Ecto.Migration
 
   def change do
-    create index(:activities, ["(data->'to')"], name: :activities_to_index, using: :gin)
-    create index(:activities, ["(data->'cc')"], name: :activities_cc_index, using: :gin)
+    create_if_not_exists index(:activities, ["(data->'to')"], name: :activities_to_index, using: :gin)
+    create_if_not_exists index(:activities, ["(data->'cc')"], name: :activities_cc_index, using: :gin)
   end
 end
index ed4f5af302021c14455aef1374871afc216da607..017ef161fabb1d5cc7c99ff173af3d101dbfb760 100644 (file)
@@ -1,10 +1,17 @@
 defmodule Pleroma.Repo.Migrations.RemoveRecipientsToAndCcFieldsFromActivities do
   use Ecto.Migration
 
-  def change do
+  def up do
     alter table(:activities) do
       remove :recipients_to
       remove :recipients_cc
     end
   end
+
+  def down do
+    alter table(:activities) do
+      add :recipients_to, {:array, :string}
+      add :recipients_cc, {:array, :string}
+    end
+  end
 end
index ba6b90ea966efd326c6ab9cfb24233ed8da86351..adce28bdfedea406ec9fc99e82e8fcc07c908930 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.UsersAddIsModeratorIndex do
   use Ecto.Migration
 
   def change do
-    create index(:users, ["(info->'is_moderator')"], name: :users_is_moderator_index, using: :gin)
+    create_if_not_exists index(:users, ["(info->'is_moderator')"], name: :users_is_moderator_index, using: :gin)
   end
 end
index 0cc7afa5404d6e710bc337ae9ed10181fb039ff8..36bdf322aa68e2a791890399016c328dbda4e2a5 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreatePushSubscriptions do
   use Ecto.Migration
 
   def change do
-    create table("push_subscriptions") do
+    create_if_not_exists table("push_subscriptions") do
       add :user_id, references("users", on_delete: :delete_all)
       add :token_id, references("oauth_tokens", on_delete: :delete_all)
       add :endpoint, :string
@@ -13,6 +13,6 @@ defmodule Pleroma.Repo.Migrations.CreatePushSubscriptions do
       timestamps()
     end
 
-    create index("push_subscriptions", [:user_id, :token_id], unique: true)
+    create_if_not_exists index("push_subscriptions", [:user_id, :token_id], unique: true)
   end
 end
index 2509e558daccad3dab7e84443af441a67ffa68cc..99fcb957c796537328354fd7578aba3ac1f25a3a 100644 (file)
@@ -1,7 +1,9 @@
 defmodule Pleroma.Repo.Migrations.AddUUIDExtension do
   use Ecto.Migration
 
-  def change do
+  def up do
     execute("create extension if not exists \"uuid-ossp\"")
   end
+
+  def down, do: :ok
 end
index 9571a1e4ddb45765d95ee1a5455e3a37e328c7ef..964383668054bb595eab5b296523275e6e9c7c0e 100644 (file)
@@ -1,7 +1,9 @@
 defmodule Pleroma.Repo.Migrations.AddUUIDsToUserInfo do
   use Ecto.Migration
 
-  def change do
+  def up do
     execute("update users set info = jsonb_set(info, '{\"id\"}', to_jsonb(uuid_generate_v4()))")
   end
+
+  def down, do: :ok
 end
index 1502f63b6b62e1aa674ea887f673b06d9ab02add..7d42a0fba47b900e39c15f852fa4875076fd67ae 100644 (file)
@@ -6,6 +6,6 @@ defmodule Pleroma.Repo.Migrations.AddTagsToUsers do
       add :tags, {:array, :string}
     end
 
-    create index(:users, [:tags], using: :gin)
+    create_if_not_exists index(:users, [:tags], using: :gin)
   end
 end
index 47d2d02da29e1982a8d93980a5f7ab25c6e41877..a5b4c543da9625f6dc5b2a09c3b6b6f3338bfbeb 100644 (file)
@@ -12,7 +12,7 @@ defmodule Pleroma.Repo.Migrations.UsersAndActivitiesFlakeId do
   #   4- update relation pkeys with the new ids
   #   5- rename the temporary column to id
   #   6- re-create the constraints
-  def change do
+  def up do
     # Old serial int ids are transformed to 128bits with extra padding.
     # The application (in `Pleroma.FlakeId`) handles theses IDs properly as integers; to keep compatibility
     # with previously issued ids.
@@ -75,6 +75,8 @@ defmodule Pleroma.Repo.Migrations.UsersAndActivitiesFlakeId do
     stop_clippy_heartbeats(clippy)
   end
 
+  def down, do: :ok
+
   defp start_clippy_heartbeats() do
     count = from(a in "activities", select: count(a.id)) |> Repo.one!
 
index 3aadabcd7ff0edf853ca185c8d5edfa396c78dd0..b6a4e752b069962f16d0955b21917ea551dae0af 100644 (file)
@@ -37,12 +37,12 @@ defmodule Pleroma.Repo.Migrations.AddVisibilityFunction do
   end
 
   def down do
-    drop(
+    drop_if_exists(
       index(:activities, ["activity_visibility(actor, recipients, data)"],
         name: :activities_visibility_index
       )
     )
 
-    execute("drop function activity_visibility(actor varchar, recipients varchar[], data jsonb)")
+    execute("drop function if exists activity_visibility(actor varchar, recipients varchar[], data jsonb)")
   end
 end
index 499d6711388ebff94159d96006ab04a34e5a0dd9..cff9753186842253024a70b70593cbb995c86e8b 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateUserFtsIndex do
   use Ecto.Migration
 
   def change do
-    create index(
+    create_if_not_exists index(
              :users,
              [
                """
index b4e8c984ce80a8e3a2da2ca30b90717f1f716bc6..b0d272802dba2beecea8b96366f19469a9025f59 100644 (file)
@@ -4,7 +4,7 @@ defmodule Pleroma.Repo.Migrations.FixUserTrigramIndex do
   def up do
     drop_if_exists(index(:users, [], name: :users_trigram_index))
 
-    create(
+    create_if_not_exists(
       index(:users, ["(trim(nickname || ' ' || coalesce(name, ''))) gist_trgm_ops"],
         name: :users_trigram_index,
         using: :gist
@@ -15,7 +15,7 @@ defmodule Pleroma.Repo.Migrations.FixUserTrigramIndex do
   def down do
     drop_if_exists(index(:users, [], name: :users_trigram_index))
 
-    create(
+    create_if_not_exists(
       index(:users, ["(nickname || name) gist_trgm_ops"], name: :users_trigram_index, using: :gist)
     )
   end
index ba6ff78b5e1b8dbf0ac428916e6778eeca3b0cd8..25f248c595bd71de1a8580891c1f5c02f51d26bd 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.UsersAddIsAdminIndex do
   use Ecto.Migration
 
   def change do
-    create(index(:users, ["(info->'is_admin')"], name: :users_is_admin_index, using: :gin))
+    create_if_not_exists(index(:users, ["(info->'is_admin')"], name: :users_is_admin_index, using: :gin))
   end
 end
index 3d23b343e305754a9b31850c4f55864a892a0d33..a9b356bc35628b92c3bf1802accfb79eab0410b8 100644 (file)
@@ -2,14 +2,14 @@ defmodule Pleroma.Repo.Migrations.CreateInstances do
   use Ecto.Migration
 
   def change do
-    create table(:instances) do
+    create_if_not_exists table(:instances) do
       add :host, :string
       add :unreachable_since, :naive_datetime_usec
 
       timestamps()
     end
 
-    create unique_index(:instances, [:host])
-    create index(:instances, [:unreachable_since])
+    create_if_not_exists unique_index(:instances, [:host])
+    create_if_not_exists index(:instances, [:unreachable_since])
   end
 end
index 2b4c2b5a9e3b5a4dda3ca1e863bbb6d522cb21fe..5b263b3b3a93dc5f786dd9c1dc4430ad14b733ac 100644 (file)
@@ -1,9 +1,11 @@
 defmodule Pleroma.Repo.Migrations.FixInfoIds do
   use Ecto.Migration
 
-  def change do
+  def up do
     execute(
       "update users set info = jsonb_set(info, '{id}', to_jsonb(uuid_generate_v4())) where info->'id' is null;"
     )
   end
+
+  def down, do: :ok
 end
index 337fed156c13ef97c0f958d0810e1e4d5c38f6fb..8e1c0e630b4cdc034accf6d0cc1a81da5c181988 100644 (file)
@@ -1,9 +1,15 @@
 defmodule Pleroma.Repo.Migrations.ChangePushSubscriptionsVarchar do
   use Ecto.Migration
 
-  def change do
+  def up do
     alter table(:push_subscriptions) do
       modify(:endpoint, :varchar)
     end
   end
+
+  def down do
+    alter table(:push_subscriptions) do
+      modify(:endpoint, :string)
+    end
+  end
 end
index 558732cd2324a93d5ce66e7784bb43bc99f0df41..1facb37b8af6366b8c7ae9c31b068391beb2c49e 100644 (file)
@@ -19,7 +19,7 @@ defmodule Pleroma.Repo.Migrations.AddCorrectDMIndex do
   end
 
   def down do
-    drop(
+    drop_if_exists(
       index(:activities, ["activity_visibility(actor, recipients, data)", "id DESC"],
         name: :activities_visibility_index,
         concurrently: true,
index 8e9eccbae8b5592ecad540e7c0ea904e9ee465f5..7e44db1214ab8ebefb4cadbed85fb4d68f2fa472 100644 (file)
@@ -2,11 +2,11 @@ defmodule Pleroma.Repo.Migrations.CreateThreadMutes do
   use Ecto.Migration
 
   def change do
-    create table(:thread_mutes) do
+    create_if_not_exists table(:thread_mutes) do
       add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
       add :context, :string
     end
     
-    create unique_index(:thread_mutes, [:user_id, :context], name: :unique_index)
+    create_if_not_exists unique_index(:thread_mutes, [:user_id, :context], name: :unique_index)
   end
 end
index 6b28cbdd32d4de695857de9987b6501074bc4e15..34a390a937efc1f9e34f9b03d35f2a7f5578fa17 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateRegistrations do
   use Ecto.Migration
 
   def change do
-    create table(:registrations, primary_key: false) do
+    create_if_not_exists table(:registrations, primary_key: false) do
       add :id, :uuid, primary_key: true
       add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
       add :provider, :string
@@ -12,7 +12,7 @@ defmodule Pleroma.Repo.Migrations.CreateRegistrations do
       timestamps()
     end
 
-    create unique_index(:registrations, [:provider, :uid])
-    create unique_index(:registrations, [:user_id, :provider, :uid])
+    create_if_not_exists unique_index(:registrations, [:provider, :uid])
+    create_if_not_exists unique_index(:registrations, [:user_id, :provider, :uid])
   end
 end
index a6ab38d02a1d3564c0078aa01cdab9fa92d9e4d8..01cb30559e2a517fc135df8c17bf21231c26c522 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.CreateNotificationIdIndex do
   use Ecto.Migration
 
   def change do
-  create index(:notifications, ["id desc nulls last"])
+    create_if_not_exists index(:notifications, ["id desc nulls last"])
   end
 end
index dd737e25ab9553e48da39fc355ba459137ff1db0..8a01bbdb133250bd460cae5d202cb5d346dfc622 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateScheduledActivities do
   use Ecto.Migration
 
   def change do
-    create table(:scheduled_activities) do
+    create_if_not_exists table(:scheduled_activities) do
       add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
       add(:scheduled_at, :naive_datetime, null: false)
       add(:params, :map, null: false)
@@ -10,7 +10,7 @@ defmodule Pleroma.Repo.Migrations.CreateScheduledActivities do
       timestamps()
     end
 
-    create(index(:scheduled_activities, [:scheduled_at]))
-    create(index(:scheduled_activities, [:user_id]))
+    create_if_not_exists(index(:scheduled_activities, [:scheduled_at]))
+    create_if_not_exists(index(:scheduled_activities, [:user_id]))
   end
 end
index ebcd293893b830ceb838b13bf2f5836aba14833b..ab1bf2165e374d776f80189703e345c513947503 100644 (file)
@@ -2,8 +2,8 @@ defmodule Pleroma.Repo.Migrations.AddOauthTokenIndexes do
   use Ecto.Migration
 
   def change do
-    create(unique_index(:oauth_tokens, [:token]))
-    create(index(:oauth_tokens, [:app_id]))
-    create(index(:oauth_tokens, [:user_id]))
+    create_if_not_exists(unique_index(:oauth_tokens, [:token]))
+    create_if_not_exists(index(:oauth_tokens, [:app_id]))
+    create_if_not_exists(index(:oauth_tokens, [:user_id]))
   end
 end
index 232f75c92d8defb3412bdae1b8f201d8f91d558c..460dafb1b6dd71811667c426c85d4d207d916fb0 100644 (file)
@@ -1,6 +1,6 @@
 defmodule Pleroma.Repo.Migrations.AddIndexOnSubscribers do
   use Ecto.Migration
-  
+
   @disable_ddl_transaction true
   def change do
     create index(:users, ["(info->'subscribers')"], name: :users_subscribers_index, using: :gin, concurrently: true)
index 0e0af30ae80bc1ae30e6557e17b0d6748ad538c5..7b7d89da707fe76c0e7f4759fd7a53f255afdaad 100644 (file)
@@ -6,12 +6,12 @@ defmodule Pleroma.Repo.Migrations.CreateConversations do
   use Ecto.Migration
 
   def change do
-    create table(:conversations) do
+    create_if_not_exists table(:conversations) do
       add(:ap_id, :string, null: false)
       timestamps()
     end
 
-    create table(:conversation_participations) do
+    create_if_not_exists table(:conversation_participations) do
       add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
       add(:conversation_id, references(:conversations, on_delete: :delete_all))
       add(:read, :boolean, default: false)
@@ -19,8 +19,8 @@ defmodule Pleroma.Repo.Migrations.CreateConversations do
       timestamps()
     end
 
-    create index(:conversation_participations, [:conversation_id])
-    create unique_index(:conversation_participations, [:user_id, :conversation_id])
-    create unique_index(:conversations, [:ap_id])
+    create_if_not_exists index(:conversation_participations, [:conversation_id])
+    create_if_not_exists unique_index(:conversation_participations, [:user_id, :conversation_id])
+    create_if_not_exists unique_index(:conversations, [:ap_id])
   end
 end
index 1ce688c52348cf16886414c45799952c661049f6..b5ca2fc0fe4de405fca60d6f567e84e0939ef20b 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddParticipationUpdatedAtIndex do
   use Ecto.Migration
 
   def change do
-    create index(:conversation_participations, ["updated_at desc"])
+    create_if_not_exists index(:conversation_participations, ["updated_at desc"])
   end
 end
index d701dceccf16f4c0eab5086234dcfd6c6baf72f7..c19427f12b788694d24bb6e90d24b29fd1451a63 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddIndexOnUserInfoDeactivated do
   use Ecto.Migration
 
   def change do
-    create(index(:users, ["(info->'deactivated')"], name: :users_deactivated_index, using: :gin))
+    create_if_not_exists(index(:users, ["(info->'deactivated')"], name: :users_deactivated_index, using: :gin))
   end
 end
index 38b1081589a949fdaf09e4e91ff1ee2b2edd8120..cfd025fc5008be42a08a47d0a339c6ace82c80b8 100644 (file)
@@ -2,13 +2,13 @@ defmodule Pleroma.Repo.Migrations.CreateBookmarks do
   use Ecto.Migration
 
   def change do
-    create table(:bookmarks) do
+    create_if_not_exists table(:bookmarks) do
       add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
       add(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all))
 
       timestamps()
     end
 
-    create(unique_index(:bookmarks, [:user_id, :activity_id]))
+    create_if_not_exists(unique_index(:bookmarks, [:user_id, :activity_id]))
   end
 end
index 134b7c6f769285e08d0dd724e0b87e003a0b2042..ce45909541b6b0e35c9824ef9418d0a78776d966 100644 (file)
@@ -6,7 +6,7 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
   alias Pleroma.User
   alias Pleroma.Repo
 
-  def change do
+  def up do
     query =
       from(u in User,
         where: u.local == true,
@@ -18,7 +18,7 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
     |> Enum.each(fn %{id: user_id, bookmarks: bookmarks} ->
       Enum.each(bookmarks, fn ap_id ->
         activity = Activity.get_create_by_object_ap_id(ap_id)
-       unless is_nil(activity), do: {:ok, _} = Bookmark.create(user_id, activity.id)
+             unless is_nil(activity), do: {:ok, _} = Bookmark.create(user_id, activity.id)
       end)
     end)
 
@@ -26,4 +26,10 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
       remove(:bookmarks)
     end
   end
+
+  def down do
+    alter table(:users) do
+      add :bookmarks, {:array, :string}, null: false, default: []
+    end
+  end
 end
index 9b274695eb89281db6d04852efc4e8cbc2eaeb74..d4de51691f569d05afb0dc53545bd8dd65ccdfdd 100644 (file)
@@ -3,6 +3,6 @@ defmodule Pleroma.Repo.Migrations.AddFTSIndexToObjects do
 
   def change do
     drop_if_exists index(:activities, ["(to_tsvector('english', data->'object'->>'content'))"], using: :gin, name: :activities_fts)
-    create index(:objects, ["(to_tsvector('english', data->>'content'))"], using: :gin, name: :objects_fts)
+    create_if_not_exists index(:objects, ["(to_tsvector('english', data->>'content'))"], using: :gin, name: :objects_fts)
   end
 end
index 449f2a3d46f3daa6abc4b71d123ab68c26177b43..44a2669853cafa0f3508cceb4444e36eef3da6c7 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddRefreshTokenIndexToToken do
   use Ecto.Migration
 
   def change do
-    create(unique_index(:oauth_tokens, [:refresh_token]))
+    create_if_not_exists(unique_index(:oauth_tokens, [:refresh_token]))
   end
 end
index 2ffb88cc93b93f0e2dbd9066769fe04b7891c9c2..246b70cfbd3de6657d1369c5bec6c02504cb33fd 100644 (file)
@@ -1,9 +1,15 @@
 defmodule Pleroma.Repo.Migrations.ChangeHideColumnInFilterTable do
   use Ecto.Migration
 
-  def change do
+  def up do
     alter table(:filters) do
       modify :hide, :boolean, default: false
     end
   end
+
+  def down do
+    alter table(:filters) do
+      modify :hide, :boolean
+    end
+  end
 end
index dc9abc9989a46dbbdd2d5d399d33192e1d34628d..5184c0c48386ce4288ae7e7241edef7807333a60 100644 (file)
@@ -68,6 +68,6 @@ defmodule Pleroma.Repo.Migrations.AddThreadVisibilityFunction do
   end
 
   def down do
-    execute("drop function thread_visibility(actor varchar, activity_id varchar)")
+    execute("drop function if exists thread_visibility(actor varchar, activity_id varchar)")
   end
 end
index 1e4e3c6892a5adcc139c9dd05f9d553e3b97aee0..cb65f3939c20d115883f4aaac83f06176869767b 100644 (file)
@@ -2,12 +2,12 @@ defmodule Pleroma.Repo.Migrations.CreateConfig do
   use Ecto.Migration
 
   def change do
-    create table(:config) do
+    create_if_not_exists table(:config) do
       add(:key, :string)
       add(:value, :binary)
       timestamps()
     end
 
-    create(unique_index(:config, :key))
+    create_if_not_exists(unique_index(:config, :key))
   end
 end
index a88b0ea613d06680a52bed0bba0eedc225f25493..505f0acb2c25cab2c306c867805dca3fcf4f93c6 100644 (file)
@@ -1,10 +1,12 @@
 defmodule Pleroma.Repo.Migrations.AddNonFollowsAndNonFollowersFieldsToNotificationSettings do
   use Ecto.Migration
 
-  def change do
+  def up do
     execute("""
     update users set info = jsonb_set(info, '{notification_settings}', '{"local": true, "remote": true, "follows": true, "followers": true, "non_follows": true, "non_followers": true}')
     where local=true
     """)
   end
+
+  def down, do: :ok
 end
index 89daa97058ac1ef58978cbca59efd2a52f8217b9..7e6643497ae53e7ec79d11066d2487ec33774516 100644 (file)
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddIndexOnActivitiesLocal do
   use Ecto.Migration
 
   def change do
-    create(index("activities", [:local]))
+    create_if_not_exists(index("activities", [:local]))
   end
 end
index c915a021304d908ce0cbce2f38a8d80904ad5740..93d57a2494f0543b86c9de94480d9c3e6a7fd406 100644 (file)
@@ -3,6 +3,6 @@ defmodule Pleroma.Repo.Migrations.AddTagIndexToObjects do
 
   def change do
     drop_if_exists index(:activities, ["(data #> '{\"object\",\"tag\"}')"], using: :gin, name: :activities_tags)
-    create index(:objects, ["(data->'tag')"], using: :gin, name: :objects_tags)
+    create_if_not_exists index(:objects, ["(data->'tag')"], using: :gin, name: :objects_tags)
   end
 end
index d7a3785d09f7c3859f0d3d4a1db19e7b28c97023..867dafaf3825e411691f21d840c15fcb28117c50 100644 (file)
@@ -6,7 +6,7 @@ defmodule Pleroma.Repo.Migrations.AddGroupKeyToConfig do
       add(:group, :string)
     end
 
-    drop(unique_index("config", :key))
-    create(unique_index("config", [:group, :key]))
+    drop_if_exists(unique_index("config", :key))
+    create_if_not_exists(unique_index("config", [:group, :key]))
   end
 end
index b6a24441ad82ebdfcb32ec8b0d64469dfd53f9b9..6227769dccc8081cce8a343109c3b9cce235dbd3 100644 (file)
@@ -14,7 +14,7 @@ defmodule Pleroma.Repo.Migrations.AddFtsIndexToObjectsTwo do
       return new;
     end
     $$ LANGUAGE plpgsql")
-    execute("create index objects_fts on objects using RUM (fts_content rum_tsvector_addon_ops, inserted_at) with (attach = 'inserted_at', to = 'fts_content');")
+    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');")
 
     execute("CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE ON objects
     FOR EACH ROW EXECUTE PROCEDURE objects_fts_update()")
@@ -23,12 +23,12 @@ defmodule Pleroma.Repo.Migrations.AddFtsIndexToObjectsTwo do
   end
 
   def down do
-    execute "drop index objects_fts"
-    execute "drop trigger tsvectorupdate on objects"
-    execute "drop function objects_fts_update()"
+    execute "drop index if exists objects_fts"
+    execute "drop trigger if exists tsvectorupdate on objects"
+    execute "drop function if exists objects_fts_update()"
     alter table(:objects) do
       remove(:fts_content, :tsvector)
     end
-    create index(:objects, ["(to_tsvector('english', data->>'content'))"], using: :gin, name: :objects_fts)
+    create_if_not_exists index(:objects, ["(to_tsvector('english', data->>'content'))"], using: :gin, name: :objects_fts)
   end
 end
index 7f275279e710d15e1d139db06184b7e2bdff87f9..5b4dad648aa8a1d08c7ec89c51c134939587b04b 100644 (file)
@@ -68,21 +68,4 @@ config :pleroma, Pleroma.Uploaders.Local, uploads: "<%= uploads_dir %>"
 # config :ex_aws, :s3,
 #   host: "s3.wasabisys.com"
 
-
-# Configure Openstack Swift support if desired.
-#
-# Many openstack deployments are different, so config is left very open with
-# no assumptions made on which provider you're using. This should allow very
-# wide support without needing separate handlers for OVH, Rackspace, etc.
-#
-# config :pleroma, Pleroma.Uploaders.Swift,
-#  container: "some-container",
-#  username: "api-username-yyyy",
-#  password: "api-key-xxxx",
-#  tenant_id: "<openstack-project/tenant-id>",
-#  auth_url: "https://keystone-endpoint.provider.com",
-#  storage_url: "https://swift-endpoint.prodider.com/v1/AUTH_<tenant>/<container>",
-#  object_url: "https://cdn-endpoint.provider.com/<container>"
-#
-
 config :joken, default_signer: "<%= jwt_secret %>"
index 9c67b209b95ccff744455e92055e7a7974735304..e731d20eb7e6ba27321d70d365870b96d930c4e1 100755 (executable)
@@ -30,12 +30,15 @@ detect_flavour() {
 
 detect_branch() {
        version="$(cut -d' ' -f2 <"$RELEASE_ROOT"/releases/start_erl.data)"
-       branch="$(echo "$version" | cut -d'-' -f 4)"
+       # Expected format: major.minor.patch_version(-number_of_commits_ahead_of_tag-gcommit_hash).branch
+       branch="$(echo "$version" | cut -d'.' -f 4)"
        if [ "$branch" = "develop" ]; then
                echo "develop"
        elif [ "$branch" = "" ]; then
                echo "master"
        else
+         # Note: branch name in version is of SemVer format and may only contain [0-9a-zA-Z-] symbols —
+         #   if supporting releases for more branches, need to ensure they contain only these symbols.
                echo "Releases are built only for master and develop branches" >&2
                exit 1
        fi
index 5903d10ff05b1ffd94b4d4594952a1b393283b6f..aa193e0d40dadf907f974ef2cfa378ee7788844e 100644 (file)
@@ -11,6 +11,16 @@ defmodule Pleroma.ConversationTest do
 
   import Pleroma.Factory
 
+  setup_all do
+    config_path = [:instance, :federating]
+    initial_setting = Pleroma.Config.get(config_path)
+
+    Pleroma.Config.put(config_path, true)
+    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
+    :ok
+  end
+
   test "it goes through old direct conversations" do
     user = insert(:user)
     other_user = insert(:user)
diff --git a/test/fixtures/users_mock/masto_closed_followers.json b/test/fixtures/users_mock/masto_closed_followers.json
new file mode 100644 (file)
index 0000000..da29689
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "id": "http://localhost:4001/users/masto_closed/followers",
+  "type": "OrderedCollection",
+  "totalItems": 437,
+  "first": "http://localhost:4001/users/masto_closed/followers?page=1"
+}
diff --git a/test/fixtures/users_mock/masto_closed_following.json b/test/fixtures/users_mock/masto_closed_following.json
new file mode 100644 (file)
index 0000000..146d49f
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "id": "http://localhost:4001/users/masto_closed/following",
+  "type": "OrderedCollection",
+  "totalItems": 152,
+  "first": "http://localhost:4001/users/masto_closed/following?page=1"
+}
diff --git a/test/fixtures/users_mock/pleroma_followers.json b/test/fixtures/users_mock/pleroma_followers.json
new file mode 100644 (file)
index 0000000..db71d08
--- /dev/null
@@ -0,0 +1,20 @@
+{
+  "type": "OrderedCollection",
+  "totalItems": 527,
+  "id": "http://localhost:4001/users/fuser2/followers",
+  "first": {
+    "type": "OrderedCollectionPage",
+    "totalItems": 527,
+    "partOf": "http://localhost:4001/users/fuser2/followers",
+    "orderedItems": [],
+    "next": "http://localhost:4001/users/fuser2/followers?page=2",
+    "id": "http://localhost:4001/users/fuser2/followers?page=1"
+  },
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "http://localhost:4001/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ]
+}
diff --git a/test/fixtures/users_mock/pleroma_following.json b/test/fixtures/users_mock/pleroma_following.json
new file mode 100644 (file)
index 0000000..33d0877
--- /dev/null
@@ -0,0 +1,20 @@
+{
+  "type": "OrderedCollection",
+  "totalItems": 267,
+  "id": "http://localhost:4001/users/fuser2/following",
+  "first": {
+    "type": "OrderedCollectionPage",
+    "totalItems": 267,
+    "partOf": "http://localhost:4001/users/fuser2/following",
+    "orderedItems": [],
+    "next": "http://localhost:4001/users/fuser2/following?page=2",
+    "id": "http://localhost:4001/users/fuser2/following?page=1"
+  },
+  "@context": [
+    "https://www.w3.org/ns/activitystreams",
+    "http://localhost:4001/schemas/litepub-0.1.jsonld",
+    {
+      "@language": "und"
+    }
+  ]
+}
diff --git a/test/http/request_builder_test.exs b/test/http/request_builder_test.exs
new file mode 100644 (file)
index 0000000..a368999
--- /dev/null
@@ -0,0 +1,91 @@
+defmodule Pleroma.HTTP.RequestBuilderTest do
+  use ExUnit.Case, async: true
+  alias Pleroma.HTTP.RequestBuilder
+
+  describe "headers/2" do
+    test "don't send pleroma user agent" do
+      assert RequestBuilder.headers(%{}, []) == %{headers: []}
+    end
+
+    test "send pleroma user agent" do
+      send = Pleroma.Config.get([:http, :send_user_agent])
+      Pleroma.Config.put([:http, :send_user_agent], true)
+
+      on_exit(fn ->
+        Pleroma.Config.put([:http, :send_user_agent], send)
+      end)
+
+      assert RequestBuilder.headers(%{}, []) == %{
+               headers: [{"User-Agent", Pleroma.Application.user_agent()}]
+             }
+    end
+  end
+
+  describe "add_optional_params/3" do
+    test "don't add if keyword is empty" do
+      assert RequestBuilder.add_optional_params(%{}, %{}, []) == %{}
+    end
+
+    test "add query parameter" do
+      assert RequestBuilder.add_optional_params(
+               %{},
+               %{query: :query, body: :body, another: :val},
+               [
+                 {:query, "param1=val1&param2=val2"},
+                 {:body, "some body"}
+               ]
+             ) == %{query: "param1=val1&param2=val2", body: "some body"}
+    end
+  end
+
+  describe "add_param/4" do
+    test "add file parameter" do
+      %{
+        body: %Tesla.Multipart{
+          boundary: _,
+          content_type_params: [],
+          parts: [
+            %Tesla.Multipart.Part{
+              body: %File.Stream{
+                line_or_bytes: 2048,
+                modes: [:raw, :read_ahead, :read, :binary],
+                path: "some-path/filename.png",
+                raw: true
+              },
+              dispositions: [name: "filename.png", filename: "filename.png"],
+              headers: []
+            }
+          ]
+        }
+      } = RequestBuilder.add_param(%{}, :file, "filename.png", "some-path/filename.png")
+    end
+
+    test "add key to body" do
+      %{
+        body: %Tesla.Multipart{
+          boundary: _,
+          content_type_params: [],
+          parts: [
+            %Tesla.Multipart.Part{
+              body: "\"someval\"",
+              dispositions: [name: "somekey"],
+              headers: ["Content-Type": "application/json"]
+            }
+          ]
+        }
+      } = RequestBuilder.add_param(%{}, :body, "somekey", "someval")
+    end
+
+    test "add form parameter" do
+      assert RequestBuilder.add_param(%{}, :form, "somename", "someval") == %{
+               body: %{"somename" => "someval"}
+             }
+    end
+
+    test "add for location" do
+      assert RequestBuilder.add_param(%{}, :some_location, "somekey", "someval") == %{
+               some_location: [{"somekey", "someval"}]
+             }
+    end
+  end
+end
index a604713d800ec83407049116282c78a49ad733b3..3975cdcd694b3a39bc14ebce532b89faaa021b55 100644 (file)
@@ -107,5 +107,12 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
       assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
       assert {:error, {403, "Forbidden"}} = start_socket("?stream=user:notification")
     end
+
+    test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do
+      assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}])
+
+      assert {:error, {403, "Forbidden"}} =
+               start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
+    end
   end
 end
index b23aeb88be580c9642dd6675a55ded62d1af951a..1d6d170b72e02d4986ad087fb3a090c7cb59a391 100644 (file)
@@ -70,14 +70,6 @@ defmodule Pleroma.MediaProxyTest do
       assert decode_result(encoded) == url
     end
 
-    test "ensures urls are url-encoded" do
-      assert decode_result(url("https://pleroma.social/Hello world.jpg")) ==
-               "https://pleroma.social/Hello%20world.jpg"
-
-      assert decode_result(url("https://pleroma.social/Hello%20world.jpg")) ==
-               "https://pleroma.social/Hello%20world.jpg"
-    end
-
     test "validates signature" do
       secret_key_base = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
 
@@ -141,10 +133,31 @@ defmodule Pleroma.MediaProxyTest do
       assert String.starts_with?(encoded, Pleroma.Config.get([:media_proxy, :base_url]))
     end
 
-    # https://git.pleroma.social/pleroma/pleroma/issues/580
-    test "encoding S3 links (must preserve `%2F`)" do
+    # Some sites expect ASCII encoded characters in the URL to be preserved even if
+    # unnecessary.
+    # Issues: https://git.pleroma.social/pleroma/pleroma/issues/580
+    #         https://git.pleroma.social/pleroma/pleroma/issues/1055
+    test "preserve ASCII encoding" do
+      url =
+        "https://pleroma.com/%20/%21/%22/%23/%24/%25/%26/%27/%28/%29/%2A/%2B/%2C/%2D/%2E/%2F/%30/%31/%32/%33/%34/%35/%36/%37/%38/%39/%3A/%3B/%3C/%3D/%3E/%3F/%40/%41/%42/%43/%44/%45/%46/%47/%48/%49/%4A/%4B/%4C/%4D/%4E/%4F/%50/%51/%52/%53/%54/%55/%56/%57/%58/%59/%5A/%5B/%5C/%5D/%5E/%5F/%60/%61/%62/%63/%64/%65/%66/%67/%68/%69/%6A/%6B/%6C/%6D/%6E/%6F/%70/%71/%72/%73/%74/%75/%76/%77/%78/%79/%7A/%7B/%7C/%7D/%7E/%7F/%80/%81/%82/%83/%84/%85/%86/%87/%88/%89/%8A/%8B/%8C/%8D/%8E/%8F/%90/%91/%92/%93/%94/%95/%96/%97/%98/%99/%9A/%9B/%9C/%9D/%9E/%9F/%C2%A0/%A1/%A2/%A3/%A4/%A5/%A6/%A7/%A8/%A9/%AA/%AB/%AC/%C2%AD/%AE/%AF/%B0/%B1/%B2/%B3/%B4/%B5/%B6/%B7/%B8/%B9/%BA/%BB/%BC/%BD/%BE/%BF/%C0/%C1/%C2/%C3/%C4/%C5/%C6/%C7/%C8/%C9/%CA/%CB/%CC/%CD/%CE/%CF/%D0/%D1/%D2/%D3/%D4/%D5/%D6/%D7/%D8/%D9/%DA/%DB/%DC/%DD/%DE/%DF/%E0/%E1/%E2/%E3/%E4/%E5/%E6/%E7/%E8/%E9/%EA/%EB/%EC/%ED/%EE/%EF/%F0/%F1/%F2/%F3/%F4/%F5/%F6/%F7/%F8/%F9/%FA/%FB/%FC/%FD/%FE/%FF"
+
+      encoded = url(url)
+      assert decode_result(encoded) == url
+    end
+
+    # This includes unsafe/reserved characters which are not interpreted as part of the URL
+    # and would otherwise have to be ASCII encoded. It is our role to ensure the proxied URL
+    # is unmodified, so we are testing these characters anyway.
+    test "preserve non-unicode characters per RFC3986" do
       url =
-        "https://s3.amazonaws.com/example/test.png?X-Amz-Credential=your-access-key-id%2F20130721%2Fus-east-1%2Fs3%2Faws4_request"
+        "https://pleroma.com/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-._~:/?#[]@!$&'()*+,;=|^`{}"
+
+      encoded = url(url)
+      assert decode_result(encoded) == url
+    end
+
+    test "preserve unicode characters" do
+      url = "https://ko.wikipedia.org/wiki/위키백과:대문"
 
       encoded = url(url)
       assert decode_result(encoded) == url
diff --git a/test/reverse_proxy_test.exs b/test/reverse_proxy_test.exs
new file mode 100644 (file)
index 0000000..75a6144
--- /dev/null
@@ -0,0 +1,297 @@
+defmodule Pleroma.ReverseProxyTest do
+  use Pleroma.Web.ConnCase, async: true
+  import ExUnit.CaptureLog
+  import ExUnit.CaptureLog
+  import Mox
+  alias Pleroma.ReverseProxy
+  alias Pleroma.ReverseProxy.ClientMock
+
+  setup_all do
+    {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.ReverseProxy.ClientMock)
+    :ok
+  end
+
+  setup :verify_on_exit!
+
+  defp user_agent_mock(user_agent, invokes) do
+    json = Jason.encode!(%{"user-agent": user_agent})
+
+    ClientMock
+    |> expect(:request, fn :get, url, _, _, _ ->
+      Registry.register(Pleroma.ReverseProxy.ClientMock, url, 0)
+
+      {:ok, 200,
+       [
+         {"content-type", "application/json"},
+         {"content-length", byte_size(json) |> to_string()}
+       ], %{url: url}}
+    end)
+    |> expect(:stream_body, invokes, fn %{url: url} ->
+      case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
+        [{_, 0}] ->
+          Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
+          {:ok, json}
+
+        [{_, 1}] ->
+          Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
+          :done
+      end
+    end)
+  end
+
+  describe "user-agent" do
+    test "don't keep", %{conn: conn} do
+      user_agent_mock("hackney/1.15.1", 2)
+      conn = ReverseProxy.call(conn, "/user-agent")
+      assert json_response(conn, 200) == %{"user-agent" => "hackney/1.15.1"}
+    end
+
+    test "keep", %{conn: conn} do
+      user_agent_mock(Pleroma.Application.user_agent(), 2)
+      conn = ReverseProxy.call(conn, "/user-agent-keep", keep_user_agent: true)
+      assert json_response(conn, 200) == %{"user-agent" => Pleroma.Application.user_agent()}
+    end
+  end
+
+  test "closed connection", %{conn: conn} do
+    ClientMock
+    |> expect(:request, fn :get, "/closed", _, _, _ -> {:ok, 200, [], %{}} end)
+    |> expect(:stream_body, fn _ -> {:error, :closed} end)
+    |> expect(:close, fn _ -> :ok end)
+
+    conn = ReverseProxy.call(conn, "/closed")
+    assert conn.halted
+  end
+
+  describe "max_body " do
+    test "length returns error if content-length more than option", %{conn: conn} do
+      user_agent_mock("hackney/1.15.1", 0)
+
+      assert capture_log(fn ->
+               ReverseProxy.call(conn, "/user-agent", max_body_length: 4)
+             end) =~
+               "[error] Elixir.Pleroma.ReverseProxy: request to \"/user-agent\" failed: :body_too_large"
+    end
+
+    defp stream_mock(invokes, with_close? \\ false) do
+      ClientMock
+      |> expect(:request, fn :get, "/stream-bytes/" <> length, _, _, _ ->
+        Registry.register(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length, 0)
+
+        {:ok, 200, [{"content-type", "application/octet-stream"}],
+         %{url: "/stream-bytes/" <> length}}
+      end)
+      |> expect(:stream_body, invokes, fn %{url: "/stream-bytes/" <> length} ->
+        max = String.to_integer(length)
+
+        case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length) do
+          [{_, current}] when current < max ->
+            Registry.update_value(
+              Pleroma.ReverseProxy.ClientMock,
+              "/stream-bytes/" <> length,
+              &(&1 + 10)
+            )
+
+            {:ok, "0123456789"}
+
+          [{_, ^max}] ->
+            Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/stream-bytes/" <> length)
+            :done
+        end
+      end)
+
+      if with_close? do
+        expect(ClientMock, :close, fn _ -> :ok end)
+      end
+    end
+
+    test "max_body_size returns error if streaming body more than that option", %{conn: conn} do
+      stream_mock(3, true)
+
+      assert capture_log(fn ->
+               ReverseProxy.call(conn, "/stream-bytes/50", max_body_size: 30)
+             end) =~
+               "[warn] Elixir.Pleroma.ReverseProxy request to /stream-bytes/50 failed while reading/chunking: :body_too_large"
+    end
+  end
+
+  describe "HEAD requests" do
+    test "common", %{conn: conn} do
+      ClientMock
+      |> expect(:request, fn :head, "/head", _, _, _ ->
+        {:ok, 200, [{"content-type", "text/html; charset=utf-8"}]}
+      end)
+
+      conn = ReverseProxy.call(Map.put(conn, :method, "HEAD"), "/head")
+      assert html_response(conn, 200) == ""
+    end
+  end
+
+  defp error_mock(status) when is_integer(status) do
+    ClientMock
+    |> expect(:request, fn :get, "/status/" <> _, _, _, _ ->
+      {:error, status}
+    end)
+  end
+
+  describe "returns error on" do
+    test "500", %{conn: conn} do
+      error_mock(500)
+
+      capture_log(fn -> ReverseProxy.call(conn, "/status/500") end) =~
+        "[error] Elixir.Pleroma.ReverseProxy: request to /status/500 failed with HTTP status 500"
+    end
+
+    test "400", %{conn: conn} do
+      error_mock(400)
+
+      capture_log(fn -> ReverseProxy.call(conn, "/status/400") end) =~
+        "[error] Elixir.Pleroma.ReverseProxy: request to /status/400 failed with HTTP status 400"
+    end
+
+    test "204", %{conn: conn} do
+      ClientMock
+      |> expect(:request, fn :get, "/status/204", _, _, _ -> {:ok, 204, [], %{}} end)
+
+      capture_log(fn ->
+        conn = ReverseProxy.call(conn, "/status/204")
+        assert conn.resp_body == "Request failed: No Content"
+        assert conn.halted
+      end) =~
+        "[error] Elixir.Pleroma.ReverseProxy: request to \"/status/204\" failed with HTTP status 204"
+    end
+  end
+
+  test "streaming", %{conn: conn} do
+    stream_mock(21)
+    conn = ReverseProxy.call(conn, "/stream-bytes/200")
+    assert conn.state == :chunked
+    assert byte_size(conn.resp_body) == 200
+    assert Plug.Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"]
+  end
+
+  defp headers_mock(_) do
+    ClientMock
+    |> expect(:request, fn :get, "/headers", headers, _, _ ->
+      Registry.register(Pleroma.ReverseProxy.ClientMock, "/headers", 0)
+      {:ok, 200, [{"content-type", "application/json"}], %{url: "/headers", headers: headers}}
+    end)
+    |> expect(:stream_body, 2, fn %{url: url, headers: headers} ->
+      case Registry.lookup(Pleroma.ReverseProxy.ClientMock, url) do
+        [{_, 0}] ->
+          Registry.update_value(Pleroma.ReverseProxy.ClientMock, url, &(&1 + 1))
+          headers = for {k, v} <- headers, into: %{}, do: {String.capitalize(k), v}
+          {:ok, Jason.encode!(%{headers: headers})}
+
+        [{_, 1}] ->
+          Registry.unregister(Pleroma.ReverseProxy.ClientMock, url)
+          :done
+      end
+    end)
+
+    :ok
+  end
+
+  describe "keep request headers" do
+    setup [:headers_mock]
+
+    test "header passes", %{conn: conn} do
+      conn =
+        Plug.Conn.put_req_header(
+          conn,
+          "accept",
+          "text/html"
+        )
+        |> ReverseProxy.call("/headers")
+
+      %{"headers" => headers} = json_response(conn, 200)
+      assert headers["Accept"] == "text/html"
+    end
+
+    test "header is filtered", %{conn: conn} do
+      conn =
+        Plug.Conn.put_req_header(
+          conn,
+          "accept-language",
+          "en-US"
+        )
+        |> ReverseProxy.call("/headers")
+
+      %{"headers" => headers} = json_response(conn, 200)
+      refute headers["Accept-Language"]
+    end
+  end
+
+  test "returns 400 on non GET, HEAD requests", %{conn: conn} do
+    conn = ReverseProxy.call(Map.put(conn, :method, "POST"), "/ip")
+    assert conn.status == 400
+  end
+
+  describe "cache resp headers" do
+    test "returns headers", %{conn: conn} do
+      ClientMock
+      |> expect(:request, fn :get, "/cache/" <> ttl, _, _, _ ->
+        {:ok, 200, [{"cache-control", "public, max-age=" <> ttl}], %{}}
+      end)
+      |> expect(:stream_body, fn _ -> :done end)
+
+      conn = ReverseProxy.call(conn, "/cache/10")
+      assert {"cache-control", "public, max-age=10"} in conn.resp_headers
+    end
+
+    test "add cache-control", %{conn: conn} do
+      ClientMock
+      |> expect(:request, fn :get, "/cache", _, _, _ ->
+        {:ok, 200, [{"ETag", "some ETag"}], %{}}
+      end)
+      |> expect(:stream_body, fn _ -> :done end)
+
+      conn = ReverseProxy.call(conn, "/cache")
+      assert {"cache-control", "public"} in conn.resp_headers
+    end
+  end
+
+  defp disposition_headers_mock(headers) do
+    ClientMock
+    |> expect(:request, fn :get, "/disposition", _, _, _ ->
+      Registry.register(Pleroma.ReverseProxy.ClientMock, "/disposition", 0)
+
+      {:ok, 200, headers, %{url: "/disposition"}}
+    end)
+    |> expect(:stream_body, 2, fn %{url: "/disposition"} ->
+      case Registry.lookup(Pleroma.ReverseProxy.ClientMock, "/disposition") do
+        [{_, 0}] ->
+          Registry.update_value(Pleroma.ReverseProxy.ClientMock, "/disposition", &(&1 + 1))
+          {:ok, ""}
+
+        [{_, 1}] ->
+          Registry.unregister(Pleroma.ReverseProxy.ClientMock, "/disposition")
+          :done
+      end
+    end)
+  end
+
+  describe "response content disposition header" do
+    test "not atachment", %{conn: conn} do
+      disposition_headers_mock([
+        {"content-type", "image/gif"},
+        {"content-length", 0}
+      ])
+
+      conn = ReverseProxy.call(conn, "/disposition")
+
+      assert {"content-type", "image/gif"} in conn.resp_headers
+    end
+
+    test "with content-disposition header", %{conn: conn} do
+      disposition_headers_mock([
+        {"content-disposition", "attachment; filename=\"filename.jpg\""},
+        {"content-length", 0}
+      ])
+
+      conn = ReverseProxy.call(conn, "/disposition")
+
+      assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers
+    end
+  end
+end
index 6e389ce5254093200c1c414da9b570853b171b15..1a92be065b04501693d67c1200eda9e35b02b09c 100644 (file)
@@ -9,6 +9,12 @@ defmodule Pleroma.Tests.Helpers do
 
   defmacro __using__(_opts) do
     quote do
+      def collect_ids(collection) do
+        collection
+        |> Enum.map(& &1.id)
+        |> Enum.sort()
+      end
+
       def refresh_record(%{id: id, __struct__: model} = _),
         do: refresh_record(model, %{id: id})
 
index 30169edb071e48feb1f03475474605f550cf18da..c593a5e4aff92aa94f8bd2a116554e1a94e3ad9c 100644 (file)
@@ -31,8 +31,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body:
-         File.read!("test/fixtures/httpoison_mock/https___osada.macgirvin.com_channel_mike.json")
+       body: File.read!("test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json")
      }}
   end
 
@@ -40,7 +39,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/status.emelie.json")
+       body: File.read!("test/fixtures/tesla_mock/status.emelie.json")
      }}
   end
 
@@ -48,7 +47,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/emelie.json")
+       body: File.read!("test/fixtures/tesla_mock/emelie.json")
      }}
   end
 
@@ -56,7 +55,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/rinpatch.json")
+       body: File.read!("test/fixtures/tesla_mock/rinpatch.json")
      }}
   end
 
@@ -69,7 +68,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/webfinger_emelie.json")
+       body: File.read!("test/fixtures/tesla_mock/webfinger_emelie.json")
      }}
   end
 
@@ -77,7 +76,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/emelie.atom")
+       body: File.read!("test/fixtures/tesla_mock/emelie.atom")
      }}
   end
 
@@ -90,7 +89,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/mike@osada.macgirvin.com.json")
+       body: File.read!("test/fixtures/tesla_mock/mike@osada.macgirvin.com.json")
      }}
   end
 
@@ -103,7 +102,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_29191.xml")
+       body: File.read!("test/fixtures/tesla_mock/https___social.heldscal.la_user_29191.xml")
      }}
   end
 
@@ -111,7 +110,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.atom")
+       body: File.read!("test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom")
      }}
   end
 
@@ -124,7 +123,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/https___pawoo.net_users_pekorino.xml")
+       body: File.read!("test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.xml")
      }}
   end
 
@@ -137,7 +136,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/atarifrosch_feed.xml")
+       body: File.read!("test/fixtures/tesla_mock/atarifrosch_feed.xml")
      }}
   end
 
@@ -150,7 +149,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/atarifrosch_webfinger.xml")
+       body: File.read!("test/fixtures/tesla_mock/atarifrosch_webfinger.xml")
      }}
   end
 
@@ -158,7 +157,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/https___mamot.fr_users_Skruyb.atom")
+       body: File.read!("test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom")
      }}
   end
 
@@ -171,7 +170,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/skruyb@mamot.fr.atom")
+       body: File.read!("test/fixtures/tesla_mock/skruyb@mamot.fr.atom")
      }}
   end
 
@@ -184,7 +183,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/nonexistant@social.heldscal.la.xml")
+       body: File.read!("test/fixtures/tesla_mock/nonexistant@social.heldscal.la.xml")
      }}
   end
 
@@ -197,7 +196,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
+       body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml")
      }}
   end
 
@@ -210,7 +209,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/lucifermysticus.json")
+       body: File.read!("test/fixtures/tesla_mock/lucifermysticus.json")
      }}
   end
 
@@ -218,7 +217,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/https___prismo.news__mxb.json")
+       body: File.read!("test/fixtures/tesla_mock/https___prismo.news__mxb.json")
      }}
   end
 
@@ -231,7 +230,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/kaniini@hubzilla.example.org.json")
+       body: File.read!("test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json")
      }}
   end
 
@@ -239,7 +238,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/rye.json")
+       body: File.read!("test/fixtures/tesla_mock/rye.json")
      }}
   end
 
@@ -247,7 +246,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/rye.json")
+       body: File.read!("test/fixtures/tesla_mock/rye.json")
      }}
   end
 
@@ -257,7 +256,7 @@ defmodule HttpRequestMock do
        status: 200,
        body:
          File.read!(
-           "test/fixtures/httpoison_mock/http___mastodon.example.org_users_admin_status_1234.json"
+           "test/fixtures/tesla_mock/http___mastodon.example.org_users_admin_status_1234.json"
          )
      }}
   end
@@ -266,7 +265,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/puckipedia.com.json")
+       body: File.read!("test/fixtures/tesla_mock/puckipedia.com.json")
      }}
   end
 
@@ -274,7 +273,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/7even.json")
+       body: File.read!("test/fixtures/tesla_mock/7even.json")
      }}
   end
 
@@ -282,7 +281,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/peertube.moe-vid.json")
+       body: File.read!("test/fixtures/tesla_mock/peertube.moe-vid.json")
      }}
   end
 
@@ -290,7 +289,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-user.json")
+       body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json")
      }}
   end
 
@@ -298,7 +297,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/baptiste.gelex.xyz-article.json")
+       body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json")
      }}
   end
 
@@ -306,7 +305,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/admin@mastdon.example.org.json")
+       body: File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json")
      }}
   end
 
@@ -331,7 +330,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/7369654.html")
+       body: File.read!("test/fixtures/tesla_mock/7369654.html")
      }}
   end
 
@@ -339,7 +338,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/mayumayu.json")
+       body: File.read!("test/fixtures/tesla_mock/mayumayu.json")
      }}
   end
 
@@ -352,7 +351,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/mayumayupost.json")
+       body: File.read!("test/fixtures/tesla_mock/mayumayupost.json")
      }}
   end
 
@@ -362,7 +361,7 @@ defmodule HttpRequestMock do
        status: 200,
        body:
          File.read!(
-           "test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml"
+           "test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml"
          )
      }}
   end
@@ -375,7 +374,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/https___pleroma.soykaf.com_users_lain.xml")
+       body: File.read!("test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain.xml")
      }}
   end
 
@@ -385,7 +384,7 @@ defmodule HttpRequestMock do
        status: 200,
        body:
          File.read!(
-           "test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml"
+           "test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml"
          )
      }}
   end
@@ -399,7 +398,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/https___shitposter.club_user_1.xml")
+       body: File.read!("test/fixtures/tesla_mock/https___shitposter.club_user_1.xml")
      }}
   end
 
@@ -407,8 +406,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body:
-         File.read!("test/fixtures/httpoison_mock/https___shitposter.club_notice_2827873.html")
+       body: File.read!("test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.html")
      }}
   end
 
@@ -418,7 +416,7 @@ defmodule HttpRequestMock do
        status: 200,
        body:
          File.read!(
-           "test/fixtures/httpoison_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml"
+           "test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml"
          )
      }}
   end
@@ -431,7 +429,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/spc_5381.atom")
+       body: File.read!("test/fixtures/tesla_mock/spc_5381.atom")
      }}
   end
 
@@ -444,7 +442,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/spc_5381_xrd.xml")
+       body: File.read!("test/fixtures/tesla_mock/spc_5381_xrd.xml")
      }}
   end
 
@@ -452,7 +450,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/shitposter.club_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/shitposter.club_host_meta")
      }}
   end
 
@@ -460,7 +458,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/7369654.atom")
+       body: File.read!("test/fixtures/tesla_mock/7369654.atom")
      }}
   end
 
@@ -468,7 +466,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/7369654.html")
+       body: File.read!("test/fixtures/tesla_mock/7369654.html")
      }}
   end
 
@@ -476,7 +474,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/sakamoto_eal_feed.atom")
+       body: File.read!("test/fixtures/tesla_mock/sakamoto_eal_feed.atom")
      }}
   end
 
@@ -484,7 +482,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/social.sakamoto.gq_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/social.sakamoto.gq_host_meta")
      }}
   end
 
@@ -497,7 +495,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/eal_sakamoto.xml")
+       body: File.read!("test/fixtures/tesla_mock/eal_sakamoto.xml")
      }}
   end
 
@@ -507,14 +505,14 @@ defmodule HttpRequestMock do
         _,
         Accept: "application/atom+xml"
       ) do
-    {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/sakamoto.atom")}}
+    {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sakamoto.atom")}}
   end
 
   def get("http://mastodon.social/.well-known/host-meta", _, _, _) do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/mastodon.social_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/mastodon.social_host_meta")
      }}
   end
 
@@ -528,9 +526,7 @@ defmodule HttpRequestMock do
      %Tesla.Env{
        status: 200,
        body:
-         File.read!(
-           "test/fixtures/httpoison_mock/https___mastodon.social_users_lambadalambda.xml"
-         )
+         File.read!("test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.xml")
      }}
   end
 
@@ -538,7 +534,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/gs.example.org_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/gs.example.org_host_meta")
      }}
   end
 
@@ -552,9 +548,7 @@ defmodule HttpRequestMock do
      %Tesla.Env{
        status: 200,
        body:
-         File.read!(
-           "test/fixtures/httpoison_mock/http___gs.example.org_4040_index.php_user_1.xml"
-         )
+         File.read!("test/fixtures/tesla_mock/http___gs.example.org_4040_index.php_user_1.xml")
      }}
   end
 
@@ -573,7 +567,7 @@ defmodule HttpRequestMock do
        status: 200,
        body:
          File.read!(
-           "test/fixtures/httpoison_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml"
+           "test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml"
          )
      }}
   end
@@ -584,14 +578,14 @@ defmodule HttpRequestMock do
        status: 200,
        body:
          File.read!(
-           "test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml"
+           "test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml"
          )
      }}
   end
 
   def get("http://squeet.me/.well-known/host-meta", _, _, _) do
     {:ok,
-     %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/squeet.me_host_meta")}}
+     %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/squeet.me_host_meta")}}
   end
 
   def get(
@@ -603,7 +597,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/lain_squeet.me_webfinger.xml")
+       body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml")
      }}
   end
 
@@ -616,7 +610,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/shp@social.heldscal.la.xml")
+       body: File.read!("test/fixtures/tesla_mock/shp@social.heldscal.la.xml")
      }}
   end
 
@@ -624,7 +618,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/framatube.org_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/framatube.org_host_meta")
      }}
   end
 
@@ -638,7 +632,7 @@ defmodule HttpRequestMock do
      %Tesla.Env{
        status: 200,
        headers: [{"content-type", "application/json"}],
-       body: File.read!("test/fixtures/httpoison_mock/framasoft@framatube.org.json")
+       body: File.read!("test/fixtures/tesla_mock/framasoft@framatube.org.json")
      }}
   end
 
@@ -646,7 +640,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/gnusocial.de_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/gnusocial.de_host_meta")
      }}
   end
 
@@ -659,7 +653,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/winterdienst_webfinger.json")
+       body: File.read!("test/fixtures/tesla_mock/winterdienst_webfinger.json")
      }}
   end
 
@@ -667,7 +661,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/status.alpicola.com_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/status.alpicola.com_host_meta")
      }}
   end
 
@@ -675,7 +669,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/macgirvin.com_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/macgirvin.com_host_meta")
      }}
   end
 
@@ -683,7 +677,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/gerzilla.de_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/gerzilla.de_host_meta")
      }}
   end
 
@@ -697,7 +691,7 @@ defmodule HttpRequestMock do
      %Tesla.Env{
        status: 200,
        headers: [{"content-type", "application/json"}],
-       body: File.read!("test/fixtures/httpoison_mock/kaniini@gerzilla.de.json")
+       body: File.read!("test/fixtures/tesla_mock/kaniini@gerzilla.de.json")
      }}
   end
 
@@ -707,7 +701,7 @@ defmodule HttpRequestMock do
        status: 200,
        body:
          File.read!(
-           "test/fixtures/httpoison_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml"
+           "test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml"
          )
      }}
   end
@@ -721,7 +715,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/https___social.heldscal.la_user_23211.xml")
+       body: File.read!("test/fixtures/tesla_mock/https___social.heldscal.la_user_23211.xml")
      }}
   end
 
@@ -729,7 +723,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/social.heldscal.la_host_meta")
      }}
   end
 
@@ -737,7 +731,7 @@ defmodule HttpRequestMock do
     {:ok,
      %Tesla.Env{
        status: 200,
-       body: File.read!("test/fixtures/httpoison_mock/social.heldscal.la_host_meta")
+       body: File.read!("test/fixtures/tesla_mock/social.heldscal.la_host_meta")
      }}
   end
 
@@ -765,6 +759,54 @@ defmodule HttpRequestMock do
     {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
   end
 
+  def get("http://localhost:4001/users/masto_closed/followers", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/users_mock/masto_closed_followers.json")
+     }}
+  end
+
+  def get("http://localhost:4001/users/masto_closed/following", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/users_mock/masto_closed_following.json")
+     }}
+  end
+
+  def get("http://localhost:4001/users/fuser2/followers", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/users_mock/pleroma_followers.json")
+     }}
+  end
+
+  def get("http://localhost:4001/users/fuser2/following", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/users_mock/pleroma_following.json")
+     }}
+  end
+
+  def get("http://domain-with-errors:4001/users/fuser1/followers", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 504,
+       body: ""
+     }}
+  end
+
+  def get("http://domain-with-errors:4001/users/fuser1/following", _, _, _) do
+    {:ok,
+     %Tesla.Env{
+       status: 504,
+       body: ""
+     }}
+  end
+
   def get("http://example.com/ogp-missing-data", _, _, _) do
     {:ok,
      %Tesla.Env{
diff --git a/test/tasks/ecto/ecto_test.exs b/test/tasks/ecto/ecto_test.exs
new file mode 100644 (file)
index 0000000..b48662c
--- /dev/null
@@ -0,0 +1,11 @@
+defmodule Mix.Tasks.Pleroma.EctoTest do
+  use ExUnit.Case, async: true
+
+  test "raise on bad path" do
+    assert_raise RuntimeError, ~r/Could not find migrations directory/, fn ->
+      Mix.Tasks.Pleroma.Ecto.ensure_migrations_path(Pleroma.Repo,
+        migrations_path: "some-path"
+      )
+    end
+  end
+end
diff --git a/test/tasks/pleroma_test.exs b/test/tasks/pleroma_test.exs
new file mode 100644 (file)
index 0000000..e236ccb
--- /dev/null
@@ -0,0 +1,46 @@
+defmodule Mix.PleromaTest do
+  use ExUnit.Case, async: true
+  import Mix.Pleroma
+
+  setup_all do
+    Mix.shell(Mix.Shell.Process)
+
+    on_exit(fn ->
+      Mix.shell(Mix.Shell.IO)
+    end)
+
+    :ok
+  end
+
+  describe "shell_prompt/1" do
+    test "input" do
+      send(self(), {:mix_shell_input, :prompt, "Yes"})
+
+      answer = shell_prompt("Do you want this?")
+      assert_received {:mix_shell, :prompt, [message]}
+      assert message =~ "Do you want this?"
+      assert answer == "Yes"
+    end
+
+    test "with defval" do
+      send(self(), {:mix_shell_input, :prompt, "\n"})
+
+      answer = shell_prompt("Do you want this?", "defval")
+
+      assert_received {:mix_shell, :prompt, [message]}
+      assert message =~ "Do you want this? [defval]"
+      assert answer == "defval"
+    end
+  end
+
+  describe "get_option/3" do
+    test "get from options" do
+      assert get_option([domain: "some-domain.com"], :domain, "Promt") == "some-domain.com"
+    end
+
+    test "get from prompt" do
+      send(self(), {:mix_shell_input, :prompt, "another-domain.com"})
+      assert get_option([], :domain, "Prompt") == "another-domain.com"
+    end
+  end
+end
diff --git a/test/tasks/robots_txt_test.exs b/test/tasks/robots_txt_test.exs
new file mode 100644 (file)
index 0000000..539193f
--- /dev/null
@@ -0,0 +1,43 @@
+defmodule Mix.Tasks.Pleroma.RobotsTxtTest do
+  use ExUnit.Case, async: true
+  alias Mix.Tasks.Pleroma.RobotsTxt
+
+  test "creates new dir" do
+    path = "test/fixtures/new_dir/"
+    file_path = path <> "robots.txt"
+
+    static_dir = Pleroma.Config.get([:instance, :static_dir])
+    Pleroma.Config.put([:instance, :static_dir], path)
+
+    on_exit(fn ->
+      Pleroma.Config.put([:instance, :static_dir], static_dir)
+      {:ok, ["test/fixtures/new_dir/", "test/fixtures/new_dir/robots.txt"]} = File.rm_rf(path)
+    end)
+
+    RobotsTxt.run(["disallow_all"])
+
+    assert File.exists?(file_path)
+    {:ok, file} = File.read(file_path)
+
+    assert file == "User-Agent: *\nDisallow: /\n"
+  end
+
+  test "to existance folder" do
+    path = "test/fixtures/"
+    file_path = path <> "robots.txt"
+    static_dir = Pleroma.Config.get([:instance, :static_dir])
+    Pleroma.Config.put([:instance, :static_dir], path)
+
+    on_exit(fn ->
+      Pleroma.Config.put([:instance, :static_dir], static_dir)
+      :ok = File.rm(file_path)
+    end)
+
+    RobotsTxt.run(["disallow_all"])
+
+    assert File.exists?(file_path)
+    {:ok, file} = File.read(file_path)
+
+    assert file == "User-Agent: *\nDisallow: /\n"
+  end
+end
index f604ba63d2c13fa138b1d530559f8a86be6f44d2..3e33f0335a89c03237346c421c134624591c92c2 100644 (file)
@@ -3,6 +3,6 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 ExUnit.start()
-
 Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
+Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client)
 {:ok, _} = Application.ensure_all_started(:ex_machina)
diff --git a/test/user/synchronization_test.exs b/test/user/synchronization_test.exs
new file mode 100644 (file)
index 0000000..67b6694
--- /dev/null
@@ -0,0 +1,104 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.SynchronizationTest do
+  use Pleroma.DataCase
+  import Pleroma.Factory
+  alias Pleroma.User
+  alias Pleroma.User.Synchronization
+
+  setup do
+    Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+    :ok
+  end
+
+  test "update following/followers counters" do
+    user1 =
+      insert(:user,
+        local: false,
+        ap_id: "http://localhost:4001/users/masto_closed"
+      )
+
+    user2 = insert(:user, local: false, ap_id: "http://localhost:4001/users/fuser2")
+
+    users = User.external_users()
+    assert length(users) == 2
+    {user, %{}} = Synchronization.call(users, %{})
+    assert user == List.last(users)
+
+    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user1)
+    assert followers == 437
+    assert following == 152
+
+    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user2)
+
+    assert followers == 527
+    assert following == 267
+  end
+
+  test "don't check host if errors exist" do
+    user1 = insert(:user, local: false, ap_id: "http://domain-with-errors:4001/users/fuser1")
+
+    user2 = insert(:user, local: false, ap_id: "http://domain-with-errors:4001/users/fuser2")
+
+    users = User.external_users()
+    assert length(users) == 2
+
+    {user, %{"domain-with-errors" => 2}} =
+      Synchronization.call(users, %{"domain-with-errors" => 2}, max_retries: 2)
+
+    assert user == List.last(users)
+
+    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user1)
+    assert followers == 0
+    assert following == 0
+
+    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user2)
+
+    assert followers == 0
+    assert following == 0
+  end
+
+  test "don't check host if errors appeared" do
+    user1 = insert(:user, local: false, ap_id: "http://domain-with-errors:4001/users/fuser1")
+
+    user2 = insert(:user, local: false, ap_id: "http://domain-with-errors:4001/users/fuser2")
+
+    users = User.external_users()
+    assert length(users) == 2
+
+    {user, %{"domain-with-errors" => 2}} = Synchronization.call(users, %{}, max_retries: 2)
+
+    assert user == List.last(users)
+
+    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user1)
+    assert followers == 0
+    assert following == 0
+
+    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user2)
+
+    assert followers == 0
+    assert following == 0
+  end
+
+  test "other users after error appeared" do
+    user1 = insert(:user, local: false, ap_id: "http://domain-with-errors:4001/users/fuser1")
+    user2 = insert(:user, local: false, ap_id: "http://localhost:4001/users/fuser2")
+
+    users = User.external_users()
+    assert length(users) == 2
+
+    {user, %{"domain-with-errors" => 2}} = Synchronization.call(users, %{}, max_retries: 2)
+    assert user == List.last(users)
+
+    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user1)
+    assert followers == 0
+    assert following == 0
+
+    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user2)
+
+    assert followers == 527
+    assert following == 267
+  end
+end
diff --git a/test/user/synchronization_worker_test.exs b/test/user/synchronization_worker_test.exs
new file mode 100644 (file)
index 0000000..835c532
--- /dev/null
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.SynchronizationWorkerTest do
+  use Pleroma.DataCase
+  import Pleroma.Factory
+
+  setup do
+    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+    config = Pleroma.Config.get([:instance, :external_user_synchronization])
+
+    for_update = [enabled: true, interval: 1000]
+
+    Pleroma.Config.put([:instance, :external_user_synchronization], for_update)
+
+    on_exit(fn ->
+      Pleroma.Config.put([:instance, :external_user_synchronization], config)
+    end)
+
+    :ok
+  end
+
+  test "sync follow counters" do
+    user1 =
+      insert(:user,
+        local: false,
+        ap_id: "http://localhost:4001/users/masto_closed"
+      )
+
+    user2 = insert(:user, local: false, ap_id: "http://localhost:4001/users/fuser2")
+
+    {:ok, _} = Pleroma.User.SynchronizationWorker.start_link()
+    :timer.sleep(1500)
+
+    %{follower_count: followers, following_count: following} =
+      Pleroma.User.get_cached_user_info(user1)
+
+    assert followers == 437
+    assert following == 152
+
+    %{follower_count: followers, following_count: following} =
+      Pleroma.User.get_cached_user_info(user2)
+
+    assert followers == 527
+    assert following == 267
+  end
+end
diff --git a/test/user_search_test.exs b/test/user_search_test.exs
new file mode 100644 (file)
index 0000000..3c13d8b
--- /dev/null
@@ -0,0 +1,259 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.UserSearchTest do
+  alias Pleroma.Repo
+  alias Pleroma.User
+  use Pleroma.DataCase
+
+  import Pleroma.Factory
+
+  setup_all do
+    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+    :ok
+  end
+
+  describe "User.search" do
+    test "accepts limit parameter" do
+      Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
+      assert length(User.search("john", limit: 3)) == 3
+      assert length(User.search("john")) == 5
+    end
+
+    test "accepts offset parameter" do
+      Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
+      assert length(User.search("john", limit: 3)) == 3
+      assert length(User.search("john", limit: 3, offset: 3)) == 2
+    end
+
+    test "finds a user by full or partial nickname" do
+      user = insert(:user, %{nickname: "john"})
+
+      Enum.each(["john", "jo", "j"], fn query ->
+        assert user ==
+                 User.search(query)
+                 |> List.first()
+                 |> Map.put(:search_rank, nil)
+                 |> Map.put(:search_type, nil)
+      end)
+    end
+
+    test "finds a user by full or partial name" do
+      user = insert(:user, %{name: "John Doe"})
+
+      Enum.each(["John Doe", "JOHN", "doe", "j d", "j", "d"], fn query ->
+        assert user ==
+                 User.search(query)
+                 |> List.first()
+                 |> Map.put(:search_rank, nil)
+                 |> Map.put(:search_type, nil)
+      end)
+    end
+
+    test "finds users, preferring nickname matches over name matches" do
+      u1 = insert(:user, %{name: "lain", nickname: "nick1"})
+      u2 = insert(:user, %{nickname: "lain", name: "nick1"})
+
+      assert [u2.id, u1.id] == Enum.map(User.search("lain"), & &1.id)
+    end
+
+    test "finds users, considering density of matched tokens" do
+      u1 = insert(:user, %{name: "Bar Bar plus Word Word"})
+      u2 = insert(:user, %{name: "Word Word Bar Bar Bar"})
+
+      assert [u2.id, u1.id] == Enum.map(User.search("bar word"), & &1.id)
+    end
+
+    test "finds users, ranking by similarity" do
+      u1 = insert(:user, %{name: "lain"})
+      _u2 = insert(:user, %{name: "ean"})
+      u3 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social"})
+      u4 = insert(:user, %{nickname: "lain@pleroma.soykaf.com"})
+
+      assert [u4.id, u3.id, u1.id] == Enum.map(User.search("lain@ple", for_user: u1), & &1.id)
+    end
+
+    test "finds users, handling misspelled requests" do
+      u1 = insert(:user, %{name: "lain"})
+
+      assert [u1.id] == Enum.map(User.search("laiin"), & &1.id)
+    end
+
+    test "finds users, boosting ranks of friends and followers" do
+      u1 = insert(:user)
+      u2 = insert(:user, %{name: "Doe"})
+      follower = insert(:user, %{name: "Doe"})
+      friend = insert(:user, %{name: "Doe"})
+
+      {:ok, follower} = User.follow(follower, u1)
+      {:ok, u1} = User.follow(u1, friend)
+
+      assert [friend.id, follower.id, u2.id] --
+               Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
+    end
+
+    test "finds followers of user by partial name" do
+      u1 = insert(:user)
+      u2 = insert(:user, %{name: "Jimi"})
+      follower_jimi = insert(:user, %{name: "Jimi Hendrix"})
+      follower_lizz = insert(:user, %{name: "Lizz Wright"})
+      friend = insert(:user, %{name: "Jimi"})
+
+      {:ok, follower_jimi} = User.follow(follower_jimi, u1)
+      {:ok, _follower_lizz} = User.follow(follower_lizz, u2)
+      {:ok, u1} = User.follow(u1, friend)
+
+      assert Enum.map(User.search("jimi", following: true, for_user: u1), & &1.id) == [
+               follower_jimi.id
+             ]
+
+      assert User.search("lizz", following: true, for_user: u1) == []
+    end
+
+    test "find local and remote users for authenticated users" do
+      u1 = insert(:user, %{name: "lain"})
+      u2 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
+      u3 = insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
+
+      results =
+        "lain"
+        |> User.search(for_user: u1)
+        |> Enum.map(& &1.id)
+        |> Enum.sort()
+
+      assert [u1.id, u2.id, u3.id] == results
+    end
+
+    test "find only local users for unauthenticated users" do
+      %{id: id} = insert(:user, %{name: "lain"})
+      insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
+      insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
+
+      assert [%{id: ^id}] = User.search("lain")
+    end
+
+    test "find only local users for authenticated users when `limit_to_local_content` is `:all`" do
+      Pleroma.Config.put([:instance, :limit_to_local_content], :all)
+
+      %{id: id} = insert(:user, %{name: "lain"})
+      insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
+      insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
+
+      assert [%{id: ^id}] = User.search("lain")
+
+      Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
+    end
+
+    test "find all users for unauthenticated users when `limit_to_local_content` is `false`" do
+      Pleroma.Config.put([:instance, :limit_to_local_content], false)
+
+      u1 = insert(:user, %{name: "lain"})
+      u2 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
+      u3 = insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
+
+      results =
+        "lain"
+        |> User.search()
+        |> Enum.map(& &1.id)
+        |> Enum.sort()
+
+      assert [u1.id, u2.id, u3.id] == results
+
+      Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
+    end
+
+    test "finds a user whose name is nil" do
+      _user = insert(:user, %{name: "notamatch", nickname: "testuser@pleroma.amplifie.red"})
+      user_two = insert(:user, %{name: nil, nickname: "lain@pleroma.soykaf.com"})
+
+      assert user_two ==
+               User.search("lain@pleroma.soykaf.com")
+               |> List.first()
+               |> Map.put(:search_rank, nil)
+               |> Map.put(:search_type, nil)
+    end
+
+    test "does not yield false-positive matches" do
+      insert(:user, %{name: "John Doe"})
+
+      Enum.each(["mary", "a", ""], fn query ->
+        assert [] == User.search(query)
+      end)
+    end
+
+    test "works with URIs" do
+      user = insert(:user)
+
+      results =
+        User.search("http://mastodon.example.org/users/admin", resolve: true, for_user: user)
+
+      result = results |> List.first()
+
+      user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
+
+      assert length(results) == 1
+
+      expected =
+        result
+        |> Map.put(:search_rank, nil)
+        |> Map.put(:search_type, nil)
+        |> Map.put(:last_digest_emailed_at, nil)
+
+      assert user == expected
+    end
+
+    test "excludes a blocked users from search result" do
+      user = insert(:user, %{nickname: "Bill"})
+
+      [blocked_user | users] = Enum.map(0..3, &insert(:user, %{nickname: "john#{&1}"}))
+
+      blocked_user2 =
+        insert(
+          :user,
+          %{nickname: "john awful", ap_id: "https://awful-and-rude-instance.com/user/bully"}
+        )
+
+      User.block_domain(user, "awful-and-rude-instance.com")
+      User.block(user, blocked_user)
+
+      account_ids = User.search("john", for_user: refresh_record(user)) |> collect_ids
+
+      assert account_ids == collect_ids(users)
+      refute Enum.member?(account_ids, blocked_user.id)
+      refute Enum.member?(account_ids, blocked_user2.id)
+      assert length(account_ids) == 3
+    end
+
+    test "local user has the same search_rank as for users with the same nickname, but another domain" do
+      user = insert(:user)
+      insert(:user, nickname: "lain@mastodon.social")
+      insert(:user, nickname: "lain")
+      insert(:user, nickname: "lain@pleroma.social")
+
+      assert User.search("lain@localhost", resolve: true, for_user: user)
+             |> Enum.each(fn u -> u.search_rank == 0.5 end)
+    end
+
+    test "localhost is the part of the domain" do
+      user = insert(:user)
+      insert(:user, nickname: "another@somedomain")
+      insert(:user, nickname: "lain")
+      insert(:user, nickname: "lain@examplelocalhost")
+
+      result = User.search("lain@examplelocalhost", resolve: true, for_user: user)
+      assert Enum.each(result, fn u -> u.search_rank == 0.5 end)
+      assert length(result) == 2
+    end
+
+    test "local user search with users" do
+      user = insert(:user)
+      local_user = insert(:user, nickname: "lain")
+      insert(:user, nickname: "another@localhost.com")
+      insert(:user, nickname: "localhost@localhost.com")
+
+      [result] = User.search("lain@localhost", resolve: true, for_user: user)
+      assert Map.put(result, :search_rank, nil) |> Map.put(:search_type, nil) == local_user
+    end
+  end
+end
index 90d2db2eb491ba26aaebb4ee8ebb714b7bc94608..6dec3959ce7addeae35e29f2e837784760a8dd64 100644 (file)
@@ -1012,192 +1012,6 @@ defmodule Pleroma.UserTest do
     end
   end
 
-  describe "User.search" do
-    test "accepts limit parameter" do
-      Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
-      assert length(User.search("john", limit: 3)) == 3
-      assert length(User.search("john")) == 5
-    end
-
-    test "accepts offset parameter" do
-      Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
-      assert length(User.search("john", limit: 3)) == 3
-      assert length(User.search("john", limit: 3, offset: 3)) == 2
-    end
-
-    test "finds a user by full or partial nickname" do
-      user = insert(:user, %{nickname: "john"})
-
-      Enum.each(["john", "jo", "j"], fn query ->
-        assert user ==
-                 User.search(query)
-                 |> List.first()
-                 |> Map.put(:search_rank, nil)
-                 |> Map.put(:search_type, nil)
-      end)
-    end
-
-    test "finds a user by full or partial name" do
-      user = insert(:user, %{name: "John Doe"})
-
-      Enum.each(["John Doe", "JOHN", "doe", "j d", "j", "d"], fn query ->
-        assert user ==
-                 User.search(query)
-                 |> List.first()
-                 |> Map.put(:search_rank, nil)
-                 |> Map.put(:search_type, nil)
-      end)
-    end
-
-    test "finds users, preferring nickname matches over name matches" do
-      u1 = insert(:user, %{name: "lain", nickname: "nick1"})
-      u2 = insert(:user, %{nickname: "lain", name: "nick1"})
-
-      assert [u2.id, u1.id] == Enum.map(User.search("lain"), & &1.id)
-    end
-
-    test "finds users, considering density of matched tokens" do
-      u1 = insert(:user, %{name: "Bar Bar plus Word Word"})
-      u2 = insert(:user, %{name: "Word Word Bar Bar Bar"})
-
-      assert [u2.id, u1.id] == Enum.map(User.search("bar word"), & &1.id)
-    end
-
-    test "finds users, ranking by similarity" do
-      u1 = insert(:user, %{name: "lain"})
-      _u2 = insert(:user, %{name: "ean"})
-      u3 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social"})
-      u4 = insert(:user, %{nickname: "lain@pleroma.soykaf.com"})
-
-      assert [u4.id, u3.id, u1.id] == Enum.map(User.search("lain@ple", for_user: u1), & &1.id)
-    end
-
-    test "finds users, handling misspelled requests" do
-      u1 = insert(:user, %{name: "lain"})
-
-      assert [u1.id] == Enum.map(User.search("laiin"), & &1.id)
-    end
-
-    test "finds users, boosting ranks of friends and followers" do
-      u1 = insert(:user)
-      u2 = insert(:user, %{name: "Doe"})
-      follower = insert(:user, %{name: "Doe"})
-      friend = insert(:user, %{name: "Doe"})
-
-      {:ok, follower} = User.follow(follower, u1)
-      {:ok, u1} = User.follow(u1, friend)
-
-      assert [friend.id, follower.id, u2.id] --
-               Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
-    end
-
-    test "finds followers of user by partial name" do
-      u1 = insert(:user)
-      u2 = insert(:user, %{name: "Jimi"})
-      follower_jimi = insert(:user, %{name: "Jimi Hendrix"})
-      follower_lizz = insert(:user, %{name: "Lizz Wright"})
-      friend = insert(:user, %{name: "Jimi"})
-
-      {:ok, follower_jimi} = User.follow(follower_jimi, u1)
-      {:ok, _follower_lizz} = User.follow(follower_lizz, u2)
-      {:ok, u1} = User.follow(u1, friend)
-
-      assert Enum.map(User.search("jimi", following: true, for_user: u1), & &1.id) == [
-               follower_jimi.id
-             ]
-
-      assert User.search("lizz", following: true, for_user: u1) == []
-    end
-
-    test "find local and remote users for authenticated users" do
-      u1 = insert(:user, %{name: "lain"})
-      u2 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
-      u3 = insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
-
-      results =
-        "lain"
-        |> User.search(for_user: u1)
-        |> Enum.map(& &1.id)
-        |> Enum.sort()
-
-      assert [u1.id, u2.id, u3.id] == results
-    end
-
-    test "find only local users for unauthenticated users" do
-      %{id: id} = insert(:user, %{name: "lain"})
-      insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
-      insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
-
-      assert [%{id: ^id}] = User.search("lain")
-    end
-
-    test "find only local users for authenticated users when `limit_to_local_content` is `:all`" do
-      Pleroma.Config.put([:instance, :limit_to_local_content], :all)
-
-      %{id: id} = insert(:user, %{name: "lain"})
-      insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
-      insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
-
-      assert [%{id: ^id}] = User.search("lain")
-
-      Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
-    end
-
-    test "find all users for unauthenticated users when `limit_to_local_content` is `false`" do
-      Pleroma.Config.put([:instance, :limit_to_local_content], false)
-
-      u1 = insert(:user, %{name: "lain"})
-      u2 = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social", local: false})
-      u3 = insert(:user, %{nickname: "lain@pleroma.soykaf.com", local: false})
-
-      results =
-        "lain"
-        |> User.search()
-        |> Enum.map(& &1.id)
-        |> Enum.sort()
-
-      assert [u1.id, u2.id, u3.id] == results
-
-      Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
-    end
-
-    test "finds a user whose name is nil" do
-      _user = insert(:user, %{name: "notamatch", nickname: "testuser@pleroma.amplifie.red"})
-      user_two = insert(:user, %{name: nil, nickname: "lain@pleroma.soykaf.com"})
-
-      assert user_two ==
-               User.search("lain@pleroma.soykaf.com")
-               |> List.first()
-               |> Map.put(:search_rank, nil)
-               |> Map.put(:search_type, nil)
-    end
-
-    test "does not yield false-positive matches" do
-      insert(:user, %{name: "John Doe"})
-
-      Enum.each(["mary", "a", ""], fn query ->
-        assert [] == User.search(query)
-      end)
-    end
-
-    test "works with URIs" do
-      user = insert(:user)
-
-      [result] =
-        User.search("http://mastodon.example.org/users/admin", resolve: true, for_user: user)
-
-      user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
-
-      expected =
-        result
-        |> Map.put(:search_rank, nil)
-        |> Map.put(:search_type, nil)
-        |> Map.put(:last_digest_emailed_at, nil)
-
-      assert user == expected
-    end
-  end
-
   test "auth_active?/1 works correctly" do
     Pleroma.Config.put([:instance, :account_activation_required], true)
 
@@ -1472,4 +1286,121 @@ defmodule Pleroma.UserTest do
       assert user_two.ap_id in ap_ids
     end
   end
+
+  describe "sync followers count" do
+    setup do
+      user1 = insert(:user, local: false, ap_id: "http://localhost:4001/users/masto_closed")
+      user2 = insert(:user, local: false, ap_id: "http://localhost:4001/users/fuser2")
+      insert(:user, local: true)
+      insert(:user, local: false, info: %{deactivated: true})
+      {:ok, user1: user1, user2: user2}
+    end
+
+    test "external_users/1 external active users with limit", %{user1: user1, user2: user2} do
+      [fdb_user1] = User.external_users(limit: 1)
+
+      assert fdb_user1.ap_id
+      assert fdb_user1.ap_id == user1.ap_id
+      assert fdb_user1.id == user1.id
+
+      [fdb_user2] = User.external_users(max_id: fdb_user1.id, limit: 1)
+
+      assert fdb_user2.ap_id
+      assert fdb_user2.ap_id == user2.ap_id
+      assert fdb_user2.id == user2.id
+
+      assert User.external_users(max_id: fdb_user2.id, limit: 1) == []
+    end
+
+    test "sync_follow_counters/1", %{user1: user1, user2: user2} do
+      {:ok, _pid} = Agent.start_link(fn -> %{} end, name: :domain_errors)
+
+      :ok = User.sync_follow_counters()
+
+      %{follower_count: followers, following_count: following} = User.get_cached_user_info(user1)
+      assert followers == 437
+      assert following == 152
+
+      %{follower_count: followers, following_count: following} = User.get_cached_user_info(user2)
+
+      assert followers == 527
+      assert following == 267
+
+      Agent.stop(:domain_errors)
+    end
+
+    test "sync_follow_counters/1 in separate batches", %{user1: user1, user2: user2} do
+      {:ok, _pid} = Agent.start_link(fn -> %{} end, name: :domain_errors)
+
+      :ok = User.sync_follow_counters(limit: 1)
+
+      %{follower_count: followers, following_count: following} = User.get_cached_user_info(user1)
+      assert followers == 437
+      assert following == 152
+
+      %{follower_count: followers, following_count: following} = User.get_cached_user_info(user2)
+
+      assert followers == 527
+      assert following == 267
+
+      Agent.stop(:domain_errors)
+    end
+
+    test "perform/1 with :sync_follow_counters", %{user1: user1, user2: user2} do
+      :ok = User.perform(:sync_follow_counters)
+      %{follower_count: followers, following_count: following} = User.get_cached_user_info(user1)
+      assert followers == 437
+      assert following == 152
+
+      %{follower_count: followers, following_count: following} = User.get_cached_user_info(user2)
+
+      assert followers == 527
+      assert following == 267
+    end
+  end
+
+  describe "set_info_cache/2" do
+    setup do
+      user = insert(:user)
+      {:ok, user: user}
+    end
+
+    test "update from args", %{user: user} do
+      User.set_info_cache(user, %{following_count: 15, follower_count: 18})
+
+      %{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
+      assert followers == 18
+      assert following == 15
+    end
+
+    test "without args", %{user: user} do
+      User.set_info_cache(user, %{})
+
+      %{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
+      assert followers == 0
+      assert following == 0
+    end
+  end
+
+  describe "user_info/2" do
+    setup do
+      user = insert(:user)
+      {:ok, user: user}
+    end
+
+    test "update from args", %{user: user} do
+      %{follower_count: followers, following_count: following} =
+        User.user_info(user, %{following_count: 15, follower_count: 18})
+
+      assert followers == 18
+      assert following == 15
+    end
+
+    test "without args", %{user: user} do
+      %{follower_count: followers, following_count: following} = User.user_info(user)
+
+      assert followers == 0
+      assert following == 0
+    end
+  end
 end
index 8b323372954bf9be125ac1658631a39c02fe2183..5a8a671552f18cf9bc218e4da47791e613ae49f3 100644 (file)
@@ -15,6 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
   setup_all do
     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+    config_path = [:instance, :federating]
+    initial_setting = Pleroma.Config.get(config_path)
+
+    Pleroma.Config.put(config_path, true)
+    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
     :ok
   end
 
index 68ec03c339616b5a4d2730c0194ca0c0fa2563ba..a914d3c4c77065462384820c36d7bf01003cb2c4 100644 (file)
@@ -11,12 +11,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Transmogrifier
+  alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.OStatus
   alias Pleroma.Web.Websub.WebsubClientSubscription
 
+  import Mock
   import Pleroma.Factory
   import ExUnit.CaptureLog
-  alias Pleroma.Web.CommonAPI
 
   setup_all do
     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -46,12 +47,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
         data["object"]
         |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
 
-      data =
-        data
-        |> Map.put("object", object)
-
+      data = Map.put(data, "object", object)
       {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
-      returned_object = Object.normalize(returned_activity.data["object"])
+
+      returned_object = Object.normalize(returned_activity.data["object"], false)
 
       assert activity =
                Activity.get_create_by_object_ap_id(
@@ -61,6 +60,32 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
     end
 
+    test "it does not fetch replied-to activities beyond max_replies_depth" do
+      data =
+        File.read!("test/fixtures/mastodon-post-activity.json")
+        |> Poison.decode!()
+
+      object =
+        data["object"]
+        |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
+
+      data = Map.put(data, "object", object)
+
+      with_mock Pleroma.Web.Federator,
+        allowed_incoming_reply_depth?: fn _ -> false end do
+        {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
+
+        returned_object = Object.normalize(returned_activity.data["object"], false)
+
+        refute Activity.get_create_by_object_ap_id(
+                 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
+               )
+
+        assert returned_object.data["inReplyToAtomUri"] ==
+                 "https://shitposter.club/notice/2827873"
+      end
+    end
+
     test "it does not crash if the object in inReplyTo can't be fetched" do
       data =
         File.read!("test/fixtures/mastodon-post-activity.json")
index 75d8bb4b5a9daf68b1b711297ee822a4427dcd0f..a00c9c5792d9fc8d3622795f6ebeabc5a0143548 100644 (file)
@@ -111,4 +111,20 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
     refute "<script> alert('hecked :D:D:D:D:D:D:D') </script>" ==
              ReportView.render("show.json", %{report: activity})[:content]
   end
+
+  test "doesn't error out when the user doesn't exists" do
+    user = insert(:user)
+    other_user = insert(:user)
+
+    {:ok, activity} =
+      CommonAPI.report(user, %{
+        "account_id" => other_user.id,
+        "comment" => ""
+      })
+
+    Pleroma.User.delete(other_user)
+    Pleroma.User.invalidate_cache(other_user)
+
+    assert %{} = ReportView.render("show.json", %{report: activity})
+  end
 end
index 0f43bc8f24e6d93c3857a9b265a66b80fcc365fc..69dd4d7473c4df10d78e638bb22537db48f03c0b 100644 (file)
@@ -12,6 +12,13 @@ defmodule Pleroma.Web.FederatorTest do
 
   setup_all do
     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+    config_path = [:instance, :federating]
+    initial_setting = Pleroma.Config.get(config_path)
+
+    Pleroma.Config.put(config_path, true)
+    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
     :ok
   end
 
index b7487c68cc2052a65740c0890d9bd50373b5b65f..64f14f794c275cbdbc2a31794f449086bcbdd2cf 100644 (file)
@@ -24,6 +24,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   import ExUnit.CaptureLog
   import Tesla.Mock
 
+  @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
+
   setup do
     mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
     :ok
@@ -584,6 +586,101 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert expected == json_response(conn, 200)
   end
 
+  test "user avatar can be set", %{conn: conn} do
+    user = insert(:user)
+    avatar_image = File.read!("test/fixtures/avatar_data_uri")
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/accounts/update_avatar", %{img: avatar_image})
+
+    user = refresh_record(user)
+
+    assert %{
+             "name" => _,
+             "type" => _,
+             "url" => [
+               %{
+                 "href" => _,
+                 "mediaType" => _,
+                 "type" => _
+               }
+             ]
+           } = user.avatar
+
+    assert %{"url" => _} = json_response(conn, 200)
+  end
+
+  test "user avatar can be reset", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/accounts/update_avatar", %{img: ""})
+
+    user = User.get_cached_by_id(user.id)
+
+    assert user.avatar == nil
+
+    assert %{"url" => nil} = json_response(conn, 200)
+  end
+
+  test "can set profile banner", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/accounts/update_banner", %{"banner" => @image})
+
+    user = refresh_record(user)
+    assert user.info.banner["type"] == "Image"
+
+    assert %{"url" => _} = json_response(conn, 200)
+  end
+
+  test "can reset profile banner", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/accounts/update_banner", %{"banner" => ""})
+
+    user = refresh_record(user)
+    assert user.info.banner == %{}
+
+    assert %{"url" => nil} = json_response(conn, 200)
+  end
+
+  test "background image can be set", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/accounts/update_background", %{"img" => @image})
+
+    user = refresh_record(user)
+    assert user.info.background["type"] == "Image"
+    assert %{"url" => _} = json_response(conn, 200)
+  end
+
+  test "background image can be reset", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/accounts/update_background", %{"img" => ""})
+
+    user = refresh_record(user)
+    assert user.info.background == %{}
+    assert %{"url" => nil} = json_response(conn, 200)
+  end
+
   test "creates an oauth app", %{conn: conn} do
     user = insert(:user)
     app_attrs = build(:oauth_app)
index ec75150ab88c377099eaa7aefed0cef5a69ad732..73791a95b5f4fcfede475d03a13089ebd2647cc6 100644 (file)
@@ -444,4 +444,39 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
       assert Enum.at(result[:options], 2)[:votes_count] == 1
     end
   end
+
+  test "embeds a relationship in the account" do
+    user = insert(:user)
+    other_user = insert(:user)
+
+    {:ok, activity} =
+      CommonAPI.post(user, %{
+        "status" => "drink more water"
+      })
+
+    result = StatusView.render("status.json", %{activity: activity, for: other_user})
+
+    assert result[:account][:pleroma][:relationship] ==
+             AccountView.render("relationship.json", %{user: other_user, target: user})
+  end
+
+  test "embeds a relationship in the account in reposts" do
+    user = insert(:user)
+    other_user = insert(:user)
+
+    {:ok, activity} =
+      CommonAPI.post(user, %{
+        "status" => "˙˙ɐʎns"
+      })
+
+    {:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user)
+
+    result = StatusView.render("status.json", %{activity: activity, for: user})
+
+    assert result[:account][:pleroma][:relationship] ==
+             AccountView.render("relationship.json", %{user: user, target: other_user})
+
+    assert result[:reblog][:account][:pleroma][:relationship] ==
+             AccountView.render("relationship.json", %{user: user, target: user})
+  end
 end
index 7441e5fce9d5ab87ee10651750c96eed68e4d785..eae44dba5125e734841e96a8f0f865d369c58a74 100644 (file)
@@ -12,6 +12,13 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
 
   setup_all do
     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+    config_path = [:instance, :federating]
+    initial_setting = Pleroma.Config.get(config_path)
+
+    Pleroma.Config.put(config_path, true)
+    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
     :ok
   end
 
index f6be16862e34bb6864b2780390b1fa0625176189..acce330080dc394b05f81fe5785d49105ebaf672 100644 (file)
@@ -11,8 +11,10 @@ defmodule Pleroma.Web.OStatusTest do
   alias Pleroma.User
   alias Pleroma.Web.OStatus
   alias Pleroma.Web.XML
-  import Pleroma.Factory
+
   import ExUnit.CaptureLog
+  import Mock
+  import Pleroma.Factory
 
   setup_all do
     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -266,10 +268,13 @@ defmodule Pleroma.Web.OStatusTest do
     assert favorited_activity.local
   end
 
-  test "handle incoming replies" do
+  test_with_mock "handle incoming replies, fetching replied-to activities if we don't have them",
+                 OStatus,
+                 [:passthrough],
+                 [] do
     incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
-    object = Object.normalize(activity.data["object"])
+    object = Object.normalize(activity.data["object"], false)
 
     assert activity.data["type"] == "Create"
     assert object.data["type"] == "Note"
@@ -282,6 +287,23 @@ defmodule Pleroma.Web.OStatusTest do
     assert object.data["id"] == "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note"
 
     assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
+
+    assert called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_))
+  end
+
+  test_with_mock "handle incoming replies, not fetching replied-to activities beyond max_replies_depth",
+                 OStatus,
+                 [:passthrough],
+                 [] do
+    incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml")
+
+    with_mock Pleroma.Web.Federator,
+      allowed_incoming_reply_depth?: fn _ -> false end do
+      {:ok, [activity]} = OStatus.handle_incoming(incoming)
+      object = Object.normalize(activity.data["object"], false)
+
+      refute called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_))
+    end
   end
 
   test "handle incoming follows" do
index 530562325f7a99664c48d6d2cfc9879448505987..c01e0112460b38397c8c554cae5e50117b694015 100644 (file)
@@ -5,6 +5,15 @@
 defmodule Pleroma.Web.FederatingPlugTest do
   use Pleroma.Web.ConnCase
 
+  setup_all do
+    config_path = [:instance, :federating]
+    initial_setting = Pleroma.Config.get(config_path)
+
+    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
+    :ok
+  end
+
   test "returns and halt the conn when federating is disabled" do
     Pleroma.Config.put([:instance, :federating], false)
 
@@ -14,11 +23,11 @@ defmodule Pleroma.Web.FederatingPlugTest do
 
     assert conn.status == 404
     assert conn.halted
-
-    Pleroma.Config.put([:instance, :federating], true)
   end
 
   test "does nothing when federating is enabled" do
+    Pleroma.Config.put([:instance, :federating], true)
+
     conn =
       build_conn()
       |> Pleroma.Web.FederatingPlug.call(%{})
index 8187ffd0ee1dadde12f542aeeafdee4e50bddf02..3760b44b6c2688c67236a3220e0469cd591c43be 100644 (file)
@@ -40,6 +40,18 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
       user = refresh_record(user)
       assert user.info.banner["type"] == "Image"
     end
+
+    test "profile banner can be reset", %{conn: conn} do
+      user = insert(:user)
+
+      conn
+      |> assign(:user, user)
+      |> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => ""})
+      |> json_response(200)
+
+      user = refresh_record(user)
+      assert user.info.banner == %{}
+    end
   end
 
   describe "POST /api/qvitter/update_background_image" do
@@ -54,6 +66,18 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
       user = refresh_record(user)
       assert user.info.background["type"] == "Image"
     end
+
+    test "background can be reset", %{conn: conn} do
+      user = insert(:user)
+
+      conn
+      |> assign(:user, user)
+      |> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => ""})
+      |> json_response(200)
+
+      user = refresh_record(user)
+      assert user.info.background == %{}
+    end
   end
 
   describe "POST /api/account/verify_credentials" do
@@ -821,6 +845,19 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
       assert json_response(conn, 200) ==
                UserView.render("show.json", %{user: current_user, for: current_user})
     end
+
+    test "user avatar can be reset", %{conn: conn, user: current_user} do
+      conn =
+        conn
+        |> with_credentials(current_user.nickname, "test")
+        |> post("/api/qvitter/update_avatar.json", %{img: ""})
+
+      current_user = User.get_cached_by_id(current_user.id)
+      assert current_user.avatar == nil
+
+      assert json_response(conn, 200) ==
+               UserView.render("show.json", %{user: current_user, for: current_user})
+    end
   end
 
   describe "GET /api/qvitter/mutes.json" do
index 43fccfc7ab5241f948027589d807d9b66c821a94..a14ed3126d43395e298b95df87125ef79574e333 100644 (file)
@@ -10,6 +10,12 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
 
   setup do
     mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+    config_path = [:instance, :federating]
+    initial_setting = Pleroma.Config.get(config_path)
+
+    Pleroma.Config.put(config_path, true)
+    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
     :ok
   end
 
index f79745d58ce340dd0691beafe84bf5f033e05d34..aa7262beb1eb3c6d2a0cec2dabdead769e2523c2 100644 (file)
@@ -9,6 +9,16 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do
   alias Pleroma.Web.Websub
   alias Pleroma.Web.Websub.WebsubClientSubscription
 
+  setup_all do
+    config_path = [:instance, :federating]
+    initial_setting = Pleroma.Config.get(config_path)
+
+    Pleroma.Config.put(config_path, true)
+    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
+
+    :ok
+  end
+
   test "websub subscription request", %{conn: conn} do
     user = insert(:user)