Merge remote-tracking branch 'remotes/origin/develop' into authenticated-api-oauth...
authorIvan Tashkinov <ivantashkinov@gmail.com>
Wed, 15 Apr 2020 16:20:34 +0000 (19:20 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Wed, 15 Apr 2020 16:20:34 +0000 (19:20 +0300)
117 files changed:
CHANGELOG.md
COPYING
benchmarks/load_testing/fetcher.ex
config/config.exs
coveralls.json [new file with mode: 0644]
docs/API/admin_api.md
docs/API/pleroma_api.md
docs/administration/CLI_tasks/emoji.md
lib/mix/pleroma.ex
lib/mix/tasks/pleroma/benchmark.ex
lib/mix/tasks/pleroma/emoji.ex
lib/pleroma/config/transfer_task.ex
lib/pleroma/ecto_enums.ex
lib/pleroma/following_relationship.ex
lib/pleroma/formatter.ex
lib/pleroma/gun/conn.ex
lib/pleroma/object/containment.ex
lib/pleroma/plugs/mapped_signature_to_identity_plug.ex
lib/pleroma/plugs/rate_limiter/rate_limiter.ex
lib/pleroma/plugs/remote_ip.ex
lib/pleroma/plugs/uploaded_media.ex
lib/pleroma/pool/connections.ex
lib/pleroma/user.ex
lib/pleroma/user/query.ex
lib/pleroma/user_relationship.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/builder.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validator.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/common_validations.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/create_validator.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/like_validator.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/note_validator.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/types/date_time.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validators/types/object_id.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/pipeline.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/side_effects.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/views/report_view.ex
lib/pleroma/web/api_spec.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/helpers.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/app_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/domain_block_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/app_create_request.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/app_create_response.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/domain_block_request.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex [new file with mode: 0644]
lib/pleroma/web/common_api/activity_draft.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/controller_helper.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/controllers/app_controller.ex
lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
lib/pleroma/web/mastodon_api/controllers/search_controller.ex
lib/pleroma/web/mastodon_api/controllers/status_controller.ex
lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/mastodon_api/views/notification_view.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
lib/pleroma/web/oauth/scopes.ex
lib/pleroma/web/pleroma_api/controllers/account_controller.ex
lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
lib/pleroma/web/rich_media/helpers.ex
lib/pleroma/web/router.ex
mix.exs
mix.lock
priv/repo/migrations/20190414125034_migrate_old_bookmarks.exs
priv/repo/migrations/20190711042021_create_safe_jsonb_set.exs
priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs [new file with mode: 0644]
priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs [new file with mode: 0644]
priv/repo/migrations/20200402063221_update_oban_jobs_table.exs [new file with mode: 0644]
test/fixtures/emoji/packs/blank.png.zip [new file with mode: 0644]
test/fixtures/emoji/packs/default-manifest.json [new file with mode: 0644]
test/fixtures/emoji/packs/finmoji.json [new file with mode: 0644]
test/fixtures/emoji/packs/manifest.json [new file with mode: 0644]
test/following_relationship_test.exs
test/formatter_test.exs
test/notification_test.exs
test/object_test.exs
test/plugs/rate_limiter_test.exs
test/stat_test.exs
test/tasks/database_test.exs
test/tasks/emoji_test.exs [new file with mode: 0644]
test/tasks/user_test.exs
test/user_test.exs
test/web/activity_pub/activity_pub_controller_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/object_validator_test.exs [new file with mode: 0644]
test/web/activity_pub/object_validators/note_validator_test.exs [new file with mode: 0644]
test/web/activity_pub/object_validators/types/date_time_test.exs [new file with mode: 0644]
test/web/activity_pub/object_validators/types/object_id_test.exs [new file with mode: 0644]
test/web/activity_pub/pipeline_test.exs [new file with mode: 0644]
test/web/activity_pub/side_effects_test.exs [new file with mode: 0644]
test/web/activity_pub/transmogrifier_test.exs
test/web/activity_pub/views/object_view_test.exs
test/web/admin_api/admin_api_controller_test.exs
test/web/api_spec/app_operation_test.exs [new file with mode: 0644]
test/web/common_api/common_api_test.exs
test/web/common_api/common_api_utils_test.exs
test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
test/web/mastodon_api/controllers/account_controller_test.exs
test/web/mastodon_api/controllers/app_controller_test.exs
test/web/mastodon_api/controllers/domain_block_controller_test.exs
test/web/mastodon_api/controllers/follow_request_controller_test.exs
test/web/mastodon_api/controllers/notification_controller_test.exs
test/web/mastodon_api/controllers/status_controller_test.exs
test/web/mastodon_api/controllers/timeline_controller_test.exs
test/web/mastodon_api/views/notification_view_test.exs
test/web/node_info_test.exs
test/web/ostatus/ostatus_controller_test.exs
test/web/pleroma_api/controllers/account_controller_test.exs
test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
test/web/push/impl_test.exs
test/web/streamer/streamer_test.exs
test/web/twitter_api/twitter_api_test.exs

index 52e6c33f81ffa9e6e29cfdb39722b2bd982a4cd4..56b235f6dccc1878b101087b61120b63d946aba9 100644 (file)
@@ -4,24 +4,72 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## [unreleased]
-### Changed
-- **Breaking:** BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
-
 ### Removed
 - **Breaking:** removed `with_move` parameter from notifications timeline.
 
 ### Added
 - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
+- NodeInfo: `pleroma_emoji_reactions` to the `features` list.
 - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
 - New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
 <details>
   <summary>API Changes</summary>
 - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
+- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
 </details>
 
+### Fixed
+- Support pagination in conversations API
+
+## [unreleased-patch]
+### Fixed
+- Logger configuration through AdminFE
+
+## [2.0.2] - 2020-04-08
+### Added
+- Support for Funkwhale's `Audio` activity
+- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials`
+
+### Fixed
+- Blocked/muted users still generating push notifications
+- Input textbox for bio ignoring newlines
+- OTP: Inability to use PostgreSQL databases with SSL
+- `user delete_activities` breaking when trying to delete already deleted posts
+- Incorrect URL for Funkwhale channels
+
+### Upgrade notes
+1. Restart Pleroma
+
+## [2.0.1] - 2020-03-15
+### Security
+- Static-FE: Fix remote posts not being sanitized
+
+### Fixed
+- 500 errors when no `Accept` header is present if Static-FE is enabled
+- Instance panel not being updated immediately due to wrong `Cache-Control` headers
+- Statuses posted with BBCode/Markdown having unncessary newlines in Pleroma-FE
+- OTP: Fix some settings not being migrated to in-database config properly
+- No `Cache-Control` headers on attachment/media proxy requests
+- Character limit enforcement being off by 1
+- Mastodon Streaming API: hashtag timelines not working
+
+### Changed
+- BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
+- Mastodon API: Allow registration without email if email verification is not enabled
+
+### Upgrade notes
+#### Nginx only
+1. Remove `proxy_ignore_headers Cache-Control;` and `proxy_hide_header  Cache-Control;` from your config.
+
+#### Everyone
+1. Run database migrations (inside Pleroma directory):
+  - OTP: `./bin/pleroma_ctl migrate`
+  - From Source: `mix ecto.migrate`
+2. Restart Pleroma
+
 ## [2.0.0] - 2019-03-08
 ### Security
-- Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
+- Mastodon API: Fix being able to request enormous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
 
 ### Removed
 - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
diff --git a/COPYING b/COPYING
index 0aede0fbaf27be6332059f9c50f4a0e436f796ac..3140c8038e6401ca9e11a972621672554c87c03f 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -1,4 +1,4 @@
-Unless otherwise stated this repository is copyright Â© 2017-2019
+Unless otherwise stated this repository is copyright Â© 2017-2020
 Pleroma Authors <https://pleroma.social/>, and is distributed under
 The GNU Affero General Public License Version 3, you should have received a
 copy of the license file as AGPL-3.
@@ -23,7 +23,7 @@ priv/static/images/pleroma-fox-tan-shy.png
 
 ---
 
-The following files are copyright Â© 2017-2019 Pleroma Authors
+The following files are copyright Â© 2017-2020 Pleroma Authors
 <https://pleroma.social/>, and are distributed under the Creative Commons
 Attribution-ShareAlike 4.0 International license, you should have received
 a copy of the license file as CC-BY-SA-4.0.
index bd65ac84fcbc548ac8a880da07222ec040d10091..786929ace9faa1989e96e2896f554b7a6c533116 100644 (file)
@@ -386,47 +386,56 @@ defmodule Pleroma.LoadTesting.Fetcher do
 
     favourites = ActivityPub.fetch_favourites(user)
 
+    output_relationships =
+      !!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default])
+
     Benchee.run(
       %{
         "Rendering home timeline" => fn ->
           StatusView.render("index.json", %{
             activities: home_activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering direct timeline" => fn ->
           StatusView.render("index.json", %{
             activities: direct_activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering public timeline" => fn ->
           StatusView.render("index.json", %{
             activities: public_activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering tag timeline" => fn ->
           StatusView.render("index.json", %{
             activities: tag_activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering notifications" => fn ->
           Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
             notifications: notifications,
-            for: user
+            for: user,
+            skip_relationships: !output_relationships
           })
         end,
         "Rendering favourites timeline" => fn ->
           StatusView.render("index.json", %{
             activities: favourites,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: !output_relationships
           })
         end
       },
index 232a91bf132c8063f8f0f730121bb68eade6ad6f..7f013aaad71f65d279b7f1bdb027370f17c6c0c4 100644 (file)
@@ -240,6 +240,8 @@ config :pleroma, :instance,
   extended_nickname_format: true,
   cleanup_attachments: false
 
+config :pleroma, :extensions, output_relationships_in_statuses_by_default: true
+
 config :pleroma, :feed,
   post_title: %{
     max_length: 100,
diff --git a/coveralls.json b/coveralls.json
new file mode 100644 (file)
index 0000000..75e845a
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "skip_files": [
+    "test/support",
+    "lib/mix/tasks/pleroma/benchmark.ex"
+  ]
+}
\ No newline at end of file
index 58d7023472d0202aa023b7e40790423346d48761..57fb6bc6a9c7a273ab2f741dad0ddfe66ccc20ed 100644 (file)
@@ -392,6 +392,19 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
   - `email`
   - `name`, optional
 
+- Response:
+  - On success: `204`, empty response
+  - On failure:
+    - 400 Bad Request, JSON:
+
+    ```json
+      [
+        {
+          "error": "Appropriate error message here"
+        }
+      ]
+    ```
+
 ## `GET /api/pleroma/admin/users/:nickname/password_reset`
 
 ### Get a password reset token for a given nickname
index 12e63ef9f75e8c96bdf38789b18a601a41c31d2d..90c43c356be09d2706ca00377535846feffcb5a6 100644 (file)
@@ -431,7 +431,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
 
 # Emoji Reactions
 
-Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character.
+Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character. To detect the presence of this feature, you can check `pleroma_emoji_reactions` entry in the features list of nodeinfo.
 
 ## `PUT /api/v1/pleroma/statuses/:id/reactions/:emoji`
 ### React to a post with a unicode emoji
index efec8222cb532475caca9d77a1f0b644e37f0848..3d524a52b190b16e1f26ec8f4c139a9b506b462b 100644 (file)
@@ -39,8 +39,8 @@ mix pleroma.emoji get-packs [option ...] <pack ...>
 mix pleroma.emoji gen-pack PACK-URL
 ```
 
-Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you. 
+Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you.
 
-  The manifest entry will either be written to a newly created `index.json` file or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
+  The manifest entry will either be written to a newly created `pack_name.json` file (pack name is asked in questions) or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
 
   The file list will be written to the file specified previously, *replacing* that file. You _should_ check that the file list doesn't contain anything you don't need in the pack, that is, anything that is not an emoji (the whole pack is downloaded, but only emoji files are extracted).
index 4dfcc32e78cbf2a4c7c09b618435675949ff01ac..3ad6edbfbb659d5830d4abb2ce83d0e82b038b16 100644 (file)
@@ -5,7 +5,6 @@
 defmodule Mix.Pleroma do
   @doc "Common functions to be reused in mix tasks"
   def start_pleroma do
-    Mix.Task.run("app.start")
     Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
 
     if Pleroma.Config.get(:env) != :test do
index dd2b9c8f278b26d28506b14a413d341a704c6417..6ab7fe8ef6aaac665ab330b4c3b3fbaa75671140 100644 (file)
@@ -67,7 +67,8 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
           Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
             activities: activities,
             for: user,
-            as: :activity
+            as: :activity,
+            skip_relationships: true
           })
         end
       },
index 429d763c7b386ac0d45201262c998da6dce0d55f..cdffa88b28c827af1863e1d8b5e245b7bac629c7 100644 (file)
@@ -14,8 +14,8 @@ defmodule Mix.Tasks.Pleroma.Emoji do
 
     {options, [], []} = parse_global_opts(args)
 
-    manifest =
-      fetch_manifest(if options[:manifest], do: options[:manifest], else: default_manifest())
+    url_or_path = options[:manifest] || default_manifest()
+    manifest = fetch_manifest(url_or_path)
 
     Enum.each(manifest, fn {name, info} ->
       to_print = [
@@ -40,9 +40,9 @@ defmodule Mix.Tasks.Pleroma.Emoji do
 
     {options, pack_names, []} = parse_global_opts(args)
 
-    manifest_url = if options[:manifest], do: options[:manifest], else: default_manifest()
+    url_or_path = options[:manifest] || default_manifest()
 
-    manifest = fetch_manifest(manifest_url)
+    manifest = fetch_manifest(url_or_path)
 
     for pack_name <- pack_names do
       if Map.has_key?(manifest, pack_name) do
@@ -75,7 +75,10 @@ defmodule Mix.Tasks.Pleroma.Emoji do
         end
 
         # The url specified in files should be in the same directory
-        files_url = Path.join(Path.dirname(manifest_url), pack["files"])
+        files_url =
+          url_or_path
+          |> Path.dirname()
+          |> Path.join(pack["files"])
 
         IO.puts(
           IO.ANSI.format([
@@ -133,38 +136,51 @@ defmodule Mix.Tasks.Pleroma.Emoji do
     end
   end
 
-  def run(["gen-pack", src]) do
+  def run(["gen-pack" | args]) do
     start_pleroma()
 
-    proposed_name = Path.basename(src) |> Path.rootname()
-    name = String.trim(IO.gets("Pack name [#{proposed_name}]: "))
-    # If there's no name, use the default one
-    name = if String.length(name) > 0, do: name, else: proposed_name
+    {opts, [src], []} =
+      OptionParser.parse(
+        args,
+        strict: [
+          name: :string,
+          license: :string,
+          homepage: :string,
+          description: :string,
+          files: :string,
+          extensions: :string
+        ]
+      )
 
-    license = String.trim(IO.gets("License: "))
-    homepage = String.trim(IO.gets("Homepage: "))
-    description = String.trim(IO.gets("Description: "))
+    proposed_name = Path.basename(src) |> Path.rootname()
+    name = get_option(opts, :name, "Pack name:", proposed_name)
+    license = get_option(opts, :license, "License:")
+    homepage = get_option(opts, :homepage, "Homepage:")
+    description = get_option(opts, :description, "Description:")
 
-    proposed_files_name = "#{name}.json"
-    files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: "))
-    files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name
+    proposed_files_name = "#{name}_files.json"
+    files_name = get_option(opts, :files, "Save file list to:", proposed_files_name)
 
     default_exts = [".png", ".gif"]
-    default_exts_str = Enum.join(default_exts, " ")
 
-    exts =
-      String.trim(
-        IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ")
+    custom_exts =
+      get_option(
+        opts,
+        :extensions,
+        "Emoji file extensions (separated with spaces):",
+        Enum.join(default_exts, " ")
       )
+      |> String.split(" ", trim: true)
 
     exts =
-      if String.length(exts) > 0 do
-        String.split(exts, " ")
-        |> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end)
-      else
+      if MapSet.equal?(MapSet.new(default_exts), MapSet.new(custom_exts)) do
         default_exts
+      else
+        custom_exts
       end
 
+    IO.puts("Using #{Enum.join(exts, " ")} extensions")
+
     IO.puts("Downloading the pack and generating SHA256")
 
     binary_archive = Tesla.get!(client(), src).body
@@ -194,14 +210,16 @@ defmodule Mix.Tasks.Pleroma.Emoji do
     IO.puts("""
 
     #{files_name} has been created and contains the list of all found emojis in the pack.
-    Please review the files in the remove those not needed.
+    Please review the files in the pack and remove those not needed.
     """)
 
-    if File.exists?("index.json") do
-      existing_data = File.read!("index.json") |> Jason.decode!()
+    pack_file = "#{name}.json"
+
+    if File.exists?(pack_file) do
+      existing_data = File.read!(pack_file) |> Jason.decode!()
 
       File.write!(
-        "index.json",
+        pack_file,
         Jason.encode!(
           Map.merge(
             existing_data,
@@ -211,11 +229,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do
         )
       )
 
-      IO.puts("index.json file has been update with the #{name} pack")
+      IO.puts("#{pack_file} has been updated with the #{name} pack")
     else
-      File.write!("index.json", Jason.encode!(pack_json, pretty: true))
+      File.write!(pack_file, Jason.encode!(pack_json, pretty: true))
 
-      IO.puts("index.json has been created with the #{name} pack")
+      IO.puts("#{pack_file} has been created with the #{name} pack")
     end
   end
 
index 936bc9ab1d1cb7d4b45ef442a675b73f368c9a2d..3871e1cbb3fa075d619bebadf45a082ef050ee01 100644 (file)
@@ -54,10 +54,19 @@ defmodule Pleroma.Config.TransferTask do
           [:pleroma, nil, :prometheus]
         end
 
+      {logger, other} =
+        (Repo.all(ConfigDB) ++ deleted_settings)
+        |> Enum.map(&transform_and_merge/1)
+        |> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end)
+
+      logger
+      |> Enum.sort()
+      |> Enum.each(&configure/1)
+
       started_applications = Application.started_applications()
 
-      (Repo.all(ConfigDB) ++ deleted_settings)
-      |> Enum.map(&merge_and_update/1)
+      other
+      |> Enum.map(&update/1)
       |> Enum.uniq()
       |> Enum.reject(&(&1 in reject_restart))
       |> maybe_set_pleroma_last()
@@ -81,51 +90,66 @@ defmodule Pleroma.Config.TransferTask do
     end
   end
 
-  defp group_for_restart(:logger, key, _, merged_value) do
-    # change logger configuration in runtime, without restart
-    if Keyword.keyword?(merged_value) and
-         key not in [:compile_time_application, :backends, :compile_time_purge_matching] do
-      Logger.configure_backend(key, merged_value)
-    else
-      Logger.configure([{key, merged_value}])
-    end
+  defp transform_and_merge(%{group: group, key: key, value: value} = setting) do
+    group = ConfigDB.from_string(group)
+    key = ConfigDB.from_string(key)
+    value = ConfigDB.from_binary(value)
 
-    nil
-  end
+    default = Config.Holder.default_config(group, key)
 
-  defp group_for_restart(group, _, _, _) when group != :pleroma, do: group
+    merged =
+      cond do
+        Ecto.get_meta(setting, :state) == :deleted -> default
+        can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value)
+        true -> value
+      end
 
-  defp group_for_restart(group, key, value, _) do
-    if pleroma_need_restart?(group, key, value), do: group
+    {group, key, value, merged}
   end
 
-  defp merge_and_update(setting) do
-    try do
-      key = ConfigDB.from_string(setting.key)
-      group = ConfigDB.from_string(setting.group)
+  # change logger configuration in runtime, without restart
+  defp configure({:quack, key, _, merged}) do
+    Logger.configure_backend(Quack.Logger, [{key, merged}])
+    :ok = update_env(:quack, key, merged)
+  end
 
-      default = Config.Holder.default_config(group, key)
-      value = ConfigDB.from_binary(setting.value)
+  defp configure({_, :backends, _, merged}) do
+    # removing current backends
+    Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
 
-      merged_value =
-        cond do
-          Ecto.get_meta(setting, :state) == :deleted -> default
-          can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value)
-          true -> value
-        end
+    Enum.each(merged, &Logger.add_backend/1)
 
-      :ok = update_env(group, key, merged_value)
+    :ok = update_env(:logger, :backends, merged)
+  end
 
-      group_for_restart(group, key, value, merged_value)
+  defp configure({group, key, _, merged}) do
+    merged =
+      if key == :console do
+        put_in(merged[:format], merged[:format] <> "\n")
+      else
+        merged
+      end
+
+    backend =
+      if key == :ex_syslogger,
+        do: {ExSyslogger, :ex_syslogger},
+        else: key
+
+    Logger.configure_backend(backend, merged)
+    :ok = update_env(:logger, group, merged)
+  end
+
+  defp update({group, key, value, merged}) do
+    try do
+      :ok = update_env(group, key, merged)
+
+      if group != :pleroma or pleroma_need_restart?(group, key, value), do: group
     rescue
       error ->
         error_msg =
-          "updating env causes error, group: " <>
-            inspect(setting.group) <>
-            " key: " <>
-            inspect(setting.key) <>
-            " value: " <>
-            inspect(ConfigDB.from_binary(setting.value)) <> " error: " <> inspect(error)
+          "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
+            inspect(value)
+          } error: #{inspect(error)}"
 
         Logger.warn(error_msg)
 
@@ -133,6 +157,9 @@ defmodule Pleroma.Config.TransferTask do
     end
   end
 
+  defp update_env(group, key, nil), do: Application.delete_env(group, key)
+  defp update_env(group, key, value), do: Application.put_env(group, key, value)
+
   @spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
   def pleroma_need_restart?(group, key, value) do
     group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
@@ -150,9 +177,6 @@ defmodule Pleroma.Config.TransferTask do
       end)
   end
 
-  defp update_env(group, key, nil), do: Application.delete_env(group, key)
-  defp update_env(group, key, value), do: Application.put_env(group, key, value)
-
   defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
 
   defp restart(started_applications, app, _) do
index d9b60122376ec708ee95165d027b27984aced627..6fc47620c7760c74d85a4b0be04656920a924550 100644 (file)
@@ -4,10 +4,16 @@
 
 import EctoEnum
 
-defenum(UserRelationshipTypeEnum,
+defenum(Pleroma.UserRelationship.Type,
   block: 1,
   mute: 2,
   reblog_mute: 3,
   notification_mute: 4,
   inverse_subscription: 5
 )
+
+defenum(Pleroma.FollowingRelationship.State,
+  follow_pending: 1,
+  follow_accept: 2,
+  follow_reject: 3
+)
index a9538ea4e4d613b8020b835781268890ca229b5a..9ccf4049571f4303f8e952874dd4e940572f2eff 100644 (file)
@@ -8,12 +8,13 @@ defmodule Pleroma.FollowingRelationship do
   import Ecto.Changeset
   import Ecto.Query
 
+  alias Ecto.Changeset
   alias FlakeId.Ecto.CompatType
   alias Pleroma.Repo
   alias Pleroma.User
 
   schema "following_relationships" do
-    field(:state, :string, default: "accept")
+    field(:state, Pleroma.FollowingRelationship.State, default: :follow_pending)
 
     belongs_to(:follower, User, type: CompatType)
     belongs_to(:following, User, type: CompatType)
@@ -27,6 +28,18 @@ defmodule Pleroma.FollowingRelationship do
     |> put_assoc(:follower, attrs.follower)
     |> put_assoc(:following, attrs.following)
     |> validate_required([:state, :follower, :following])
+    |> unique_constraint(:follower_id,
+      name: :following_relationships_follower_id_following_id_index
+    )
+    |> validate_not_self_relationship()
+  end
+
+  def state_to_enum(state) when state in ["pending", "accept", "reject"] do
+    String.to_existing_atom("follow_#{state}")
+  end
+
+  def state_to_enum(state) do
+    raise "State is not convertible to Pleroma.FollowingRelationship.State: #{state}"
   end
 
   def get(%User{} = follower, %User{} = following) do
@@ -35,7 +48,7 @@ defmodule Pleroma.FollowingRelationship do
     |> Repo.one()
   end
 
-  def update(follower, following, "reject"), do: unfollow(follower, following)
+  def update(follower, following, :follow_reject), do: unfollow(follower, following)
 
   def update(%User{} = follower, %User{} = following, state) do
     case get(follower, following) do
@@ -50,7 +63,7 @@ defmodule Pleroma.FollowingRelationship do
     end
   end
 
-  def follow(%User{} = follower, %User{} = following, state \\ "accept") do
+  def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do
     %__MODULE__{}
     |> changeset(%{follower: follower, following: following, state: state})
     |> Repo.insert(on_conflict: :nothing)
@@ -80,7 +93,7 @@ defmodule Pleroma.FollowingRelationship do
   def get_follow_requests(%User{id: id}) do
     __MODULE__
     |> join(:inner, [r], f in assoc(r, :follower))
-    |> where([r], r.state == "pending")
+    |> where([r], r.state == ^:follow_pending)
     |> where([r], r.following_id == ^id)
     |> select([r, f], f)
     |> Repo.all()
@@ -88,7 +101,7 @@ defmodule Pleroma.FollowingRelationship do
 
   def following?(%User{id: follower_id}, %User{id: followed_id}) do
     __MODULE__
-    |> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
+    |> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept)
     |> Repo.exists?()
   end
 
@@ -97,7 +110,7 @@ defmodule Pleroma.FollowingRelationship do
       __MODULE__
       |> join(:inner, [r], u in User, on: r.following_id == u.id)
       |> where([r], r.follower_id == ^user.id)
-      |> where([r], r.state == "accept")
+      |> where([r], r.state == ^:follow_accept)
       |> select([r, u], u.follower_address)
       |> Repo.all()
 
@@ -157,4 +170,30 @@ defmodule Pleroma.FollowingRelationship do
       fr -> fr.follower_id == follower.id and fr.following_id == following.id
     end)
   end
+
+  defp validate_not_self_relationship(%Changeset{} = changeset) do
+    changeset
+    |> validate_follower_id_following_id_inequality()
+    |> validate_following_id_follower_id_inequality()
+  end
+
+  defp validate_follower_id_following_id_inequality(%Changeset{} = changeset) do
+    validate_change(changeset, :follower_id, fn _, follower_id ->
+      if follower_id == get_field(changeset, :following_id) do
+        [source_id: "can't be equal to following_id"]
+      else
+        []
+      end
+    end)
+  end
+
+  defp validate_following_id_follower_id_inequality(%Changeset{} = changeset) do
+    validate_change(changeset, :following_id, fn _, following_id ->
+      if following_id == get_field(changeset, :follower_id) do
+        [target_id: "can't be equal to follower_id"]
+      else
+        []
+      end
+    end)
+  end
 end
index e2a658cb3c8ee7a76a47473929746f2ed51a2169..c44e7fc8bbe90b27c6b77a2bfad7f6c209377ada 100644 (file)
@@ -35,9 +35,19 @@ defmodule Pleroma.Formatter do
         nickname_text = get_nickname_text(nickname, opts)
 
         link =
-          ~s(<span class="h-card"><a data-user="#{id}" class="u-url mention" href="#{ap_id}" rel="ugc">@<span>#{
-            nickname_text
-          }</span></a></span>)
+          Phoenix.HTML.Tag.content_tag(
+            :span,
+            Phoenix.HTML.Tag.content_tag(
+              :a,
+              ["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
+              "data-user": id,
+              class: "u-url mention",
+              href: ap_id,
+              rel: "ugc"
+            ),
+            class: "h-card"
+          )
+          |> Phoenix.HTML.safe_to_string()
 
         {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
 
@@ -49,7 +59,15 @@ defmodule Pleroma.Formatter do
   def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
     tag = String.downcase(tag)
     url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
-    link = ~s(<a class="hashtag" data-tag="#{tag}" href="#{url}" rel="tag ugc">#{tag_text}</a>)
+
+    link =
+      Phoenix.HTML.Tag.content_tag(:a, tag_text,
+        class: "hashtag",
+        "data-tag": tag,
+        href: url,
+        rel: "tag ugc"
+      )
+      |> Phoenix.HTML.safe_to_string()
 
     {link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
   end
index 20823a7658daa7c926887eda807966737ce20a04..cd25a2e746d1889f9b263daff6a3200917177780 100644 (file)
@@ -49,8 +49,10 @@ defmodule Pleroma.Gun.Conn do
 
     key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
 
+    max_connections = pool_opts[:max_connections] || 250
+
     conn_pid =
-      if Connections.count(name) < opts[:max_connection] do
+      if Connections.count(name) < max_connections do
         do_open(uri, opts)
       else
         close_least_used_and_do_open(name, uri, opts)
index 9ae6a5600ca1cfc09253244a6a6bb1378c0f33d5..99608b8a5540c68e367158e89590bc0c123ffc1b 100644 (file)
@@ -32,6 +32,18 @@ defmodule Pleroma.Object.Containment do
     get_actor(%{"actor" => actor})
   end
 
+  def get_object(%{"object" => id}) when is_binary(id) do
+    id
+  end
+
+  def get_object(%{"object" => %{"id" => id}}) when is_binary(id) do
+    id
+  end
+
+  def get_object(_) do
+    nil
+  end
+
   # TODO: We explicitly allow 'tag' URIs through, due to references to legacy OStatus
   # objects being present in the test suite environment.  Once these objects are
   # removed, please also remove this.
index 4f124ed4dd6cc3ec2b0bcb7390671dea77daf98d..84b7c5d83b1e84e9c6b865efa33171335fbd95e4 100644 (file)
@@ -42,13 +42,13 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
     else
       {:user_match, false} ->
         Logger.debug("Failed to map identity from signature (payload actor mismatch)")
-        Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
+        Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
         assign(conn, :valid_signature, false)
 
       # remove me once testsuite uses mapped capabilities instead of what we do now
       {:user, nil} ->
         Logger.debug("Failed to map identity from signature (lookup failure)")
-        Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
+        Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
         conn
     end
   end
@@ -60,7 +60,7 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
     else
       _ ->
         Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
-        Logger.debug("key_id=#{key_id_from_conn(conn)}")
+        Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
         assign(conn, :valid_signature, false)
     end
   end
index 1529da7174a4cec57d3ab32b28a2641bf53acb7a..c51e2c6349e19b35d2ba11c9c2b45309a6a7d908 100644 (file)
@@ -110,20 +110,9 @@ defmodule Pleroma.Plugs.RateLimiter do
   end
 
   def disabled?(conn) do
-    localhost_or_socket =
-      case Config.get([Pleroma.Web.Endpoint, :http, :ip]) do
-        {127, 0, 0, 1} -> true
-        {0, 0, 0, 0, 0, 0, 0, 1} -> true
-        {:local, _} -> true
-        _ -> false
-      end
-
-    remote_ip_not_found =
-      if Map.has_key?(conn.assigns, :remote_ip_found),
-        do: !conn.assigns.remote_ip_found,
-        else: false
-
-    localhost_or_socket and remote_ip_not_found
+    if Map.has_key?(conn.assigns, :remote_ip_found),
+      do: !conn.assigns.remote_ip_found,
+      else: false
   end
 
   @inspect_bucket_not_found {:error, :not_found}
index 0ac9050d0764149e2361811016179f1282b20c82..2eca4f8f669789491870a8d473e79edfabb10a1f 100644 (file)
@@ -7,8 +7,6 @@ defmodule Pleroma.Plugs.RemoteIp do
   This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
   """
 
-  import Plug.Conn
-
   @behaviour Plug
 
   @headers ~w[
@@ -28,12 +26,11 @@ defmodule Pleroma.Plugs.RemoteIp do
 
   def init(_), do: nil
 
-  def call(%{remote_ip: original_remote_ip} = conn, _) do
+  def call(conn, _) do
     config = Pleroma.Config.get(__MODULE__, [])
 
     if Keyword.get(config, :enabled, false) do
-      %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config))
-      assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip)
+      RemoteIp.call(conn, remote_ip_opts(config))
     else
       conn
     end
index 36ff024a7d6e562e2f90c0bf0025a373cd725e04..94147e0c42250c647984a3955dd98100208bc04f 100644 (file)
@@ -41,6 +41,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
         conn ->
           conn
       end
+      |> merge_resp_headers([{"content-security-policy", "sandbox"}])
 
     config = Pleroma.Config.get(Pleroma.Upload)
 
index 4d4ba913c72fe4d9a2f1ca67de2f943716317e54..acafe1beabe7c698810172683dd6ad530ba00047 100644 (file)
@@ -243,7 +243,7 @@ defmodule Pleroma.Pool.Connections do
 
   @impl true
   def handle_info({:DOWN, _ref, :process, conn_pid, reason}, state) do
-    Logger.debug("received DOWM message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")
+    Logger.debug("received DOWN message for #{inspect(conn_pid)} reason -> #{inspect(reason)}")
 
     state =
       with {key, conn} <- find_conn(state.conns, conn_pid) do
index ff828aa17f33478cd3759ea1f6e9d48d7093397e..670ce397bfc972f0be0a4781c386280bd635d74f 100644 (file)
@@ -16,6 +16,7 @@ defmodule Pleroma.User do
   alias Pleroma.Conversation.Participation
   alias Pleroma.Delivery
   alias Pleroma.FollowingRelationship
+  alias Pleroma.Formatter
   alias Pleroma.HTML
   alias Pleroma.Keys
   alias Pleroma.Notification
@@ -452,7 +453,7 @@ defmodule Pleroma.User do
 
       fields =
         raw_fields
-        |> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
+        |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
 
       changeset
       |> put_change(:raw_fields, raw_fields)
@@ -462,6 +463,12 @@ defmodule Pleroma.User do
     end
   end
 
+  defp parse_fields(value) do
+    value
+    |> Formatter.linkify(mentions_format: :full)
+    |> elem(0)
+  end
+
   defp put_change_if_present(changeset, map_field, value_function) do
     if value = get_change(changeset, map_field) do
       with {:ok, new_value} <- value_function.(value) do
@@ -693,7 +700,7 @@ defmodule Pleroma.User do
 
   @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
   def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
-    follow(follower, followed, "pending")
+    follow(follower, followed, :follow_pending)
   end
 
   def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
@@ -713,14 +720,14 @@ defmodule Pleroma.User do
   def follow_all(follower, followeds) do
     followeds
     |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
-    |> Enum.each(&follow(follower, &1, "accept"))
+    |> Enum.each(&follow(follower, &1, :follow_accept))
 
     set_cache(follower)
   end
 
   defdelegate following(user), to: FollowingRelationship
 
-  def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
+  def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
     deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
 
     cond do
@@ -747,7 +754,7 @@ defmodule Pleroma.User do
 
   def unfollow(%User{} = follower, %User{} = followed) do
     case get_follow_state(follower, followed) do
-      state when state in ["accept", "pending"] ->
+      state when state in [:follow_pending, :follow_accept] ->
         FollowingRelationship.unfollow(follower, followed)
         {:ok, followed} = update_follower_count(followed)
 
@@ -765,6 +772,7 @@ defmodule Pleroma.User do
 
   defdelegate following?(follower, followed), to: FollowingRelationship
 
+  @doc "Returns follow state as Pleroma.FollowingRelationship.State value"
   def get_follow_state(%User{} = follower, %User{} = following) do
     following_relationship = FollowingRelationship.get(follower, following)
     get_follow_state(follower, following, following_relationship)
@@ -778,8 +786,11 @@ defmodule Pleroma.User do
     case {following_relationship, following.local} do
       {nil, false} ->
         case Utils.fetch_latest_follow(follower, following) do
-          %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
-          _ -> nil
+          %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
+            FollowingRelationship.state_to_enum(state)
+
+          _ ->
+            nil
         end
 
       {%{state: state}, _} ->
@@ -1278,7 +1289,7 @@ defmodule Pleroma.User do
 
   def blocks?(%User{} = user, %User{} = target) do
     blocks_user?(user, target) ||
-      (!User.following?(user, target) && blocks_domain?(user, target))
+      (blocks_domain?(user, target) and not User.following?(user, target))
   end
 
   def blocks_user?(%User{} = user, %User{} = target) do
@@ -1979,17 +1990,6 @@ defmodule Pleroma.User do
 
   def fields(%{fields: fields}), do: fields
 
-  def sanitized_fields(%User{} = user) do
-    user
-    |> User.fields()
-    |> Enum.map(fn %{"name" => name, "value" => value} ->
-      %{
-        "name" => name,
-        "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
-      }
-    end)
-  end
-
   def validate_fields(changeset, remote? \\ false) do
     limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
     limit = Pleroma.Config.get([:instance, limit_name], 0)
index 884e33039d7453caae0166fef206df27798bc107..ec88088cf7459e8bf35a99b718bde76308de7e9c 100644 (file)
@@ -148,7 +148,7 @@ defmodule Pleroma.User.Query do
       as: :relationships,
       on: r.following_id == ^id and r.follower_id == u.id
     )
-    |> where([relationships: r], r.state == "accept")
+    |> where([relationships: r], r.state == ^:follow_accept)
   end
 
   defp compose_query({:friends, %User{id: id}}, query) do
@@ -158,7 +158,7 @@ defmodule Pleroma.User.Query do
       as: :relationships,
       on: r.following_id == u.id and r.follower_id == ^id
     )
-    |> where([relationships: r], r.state == "accept")
+    |> where([relationships: r], r.state == ^:follow_accept)
   end
 
   defp compose_query({:recipients_from_activity, to}, query) do
@@ -173,7 +173,7 @@ defmodule Pleroma.User.Query do
     )
     |> where(
       [u, following: f, relationships: r],
-      u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept")
+      u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept)
     )
     |> distinct(true)
   end
index 18a5eec7262bc1816753dea928b64025ebe08deb..235ad427ce8887df7903f31c8d7cabef3810332d 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do
   import Ecto.Changeset
   import Ecto.Query
 
+  alias Ecto.Changeset
   alias Pleroma.FollowingRelationship
   alias Pleroma.Repo
   alias Pleroma.User
@@ -16,12 +17,12 @@ defmodule Pleroma.UserRelationship do
   schema "user_relationships" do
     belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
     belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
-    field(:relationship_type, UserRelationshipTypeEnum)
+    field(:relationship_type, Pleroma.UserRelationship.Type)
 
     timestamps(updated_at: false)
   end
 
-  for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
+  for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
     # `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
     #   `def create_notification_mute/2`, `def create_inverse_subscription/2`
     def unquote(:"create_#{relationship_type}")(source, target),
@@ -40,7 +41,7 @@ defmodule Pleroma.UserRelationship do
 
   def user_relationship_types, do: Keyword.keys(user_relationship_mappings())
 
-  def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__()
+  def user_relationship_mappings, do: Pleroma.UserRelationship.Type.__enum_map__()
 
   def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
     user_relationship
@@ -129,17 +130,27 @@ defmodule Pleroma.UserRelationship do
   end
 
   @doc ":relationships option for StatusView / AccountView / NotificationView"
-  def view_relationships_option(nil = _reading_user, _actors) do
+  def view_relationships_option(reading_user, actors, opts \\ [])
+
+  def view_relationships_option(nil = _reading_user, _actors, _opts) do
     %{user_relationships: [], following_relationships: []}
   end
 
-  def view_relationships_option(%User{} = reading_user, actors) do
+  def view_relationships_option(%User{} = reading_user, actors, opts) do
+    {source_to_target_rel_types, target_to_source_rel_types} =
+      if opts[:source_mutes_only] do
+        # This option is used for rendering statuses (FE needs `muted` flag for each one anyways)
+        {[:mute], []}
+      else
+        {[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
+      end
+
     user_relationships =
       UserRelationship.dictionary(
         [reading_user],
         actors,
-        [:block, :mute, :notification_mute, :reblog_mute],
-        [:block, :inverse_subscription]
+        source_to_target_rel_types,
+        target_to_source_rel_types
       )
 
     following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
@@ -147,18 +158,26 @@ defmodule Pleroma.UserRelationship do
     %{user_relationships: user_relationships, following_relationships: following_relationships}
   end
 
-  defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
+  defp validate_not_self_relationship(%Changeset{} = changeset) do
     changeset
-    |> validate_change(:target_id, fn _, target_id ->
-      if target_id == get_field(changeset, :source_id) do
-        [target_id: "can't be equal to source_id"]
+    |> validate_source_id_target_id_inequality()
+    |> validate_target_id_source_id_inequality()
+  end
+
+  defp validate_source_id_target_id_inequality(%Changeset{} = changeset) do
+    validate_change(changeset, :source_id, fn _, source_id ->
+      if source_id == get_field(changeset, :target_id) do
+        [source_id: "can't be equal to target_id"]
       else
         []
       end
     end)
-    |> validate_change(:source_id, fn _, source_id ->
-      if source_id == get_field(changeset, :target_id) do
-        [source_id: "can't be equal to target_id"]
+  end
+
+  defp validate_target_id_source_id_inequality(%Changeset{} = changeset) do
+    validate_change(changeset, :target_id, fn _, target_id ->
+      if target_id == get_field(changeset, :source_id) do
+        [target_id: "can't be equal to source_id"]
       else
         []
       end
index 53b6ad654b692d652ebf64ce81e59d30de653ad4..86b105b7fe8610f3ac1f0257be4fc5c845768080 100644 (file)
@@ -125,6 +125,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   def increase_poll_votes_if_vote(_create_data), do: :noop
 
+  @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
+  def persist(object, meta) do
+    with local <- Keyword.fetch!(meta, :local),
+         {recipients, _, _} <- get_recipients(object),
+         {:ok, activity} <-
+           Repo.insert(%Activity{
+             data: object,
+             local: local,
+             recipients: recipients,
+             actor: object["actor"]
+           }) do
+      {:ok, activity, meta}
+    end
+  end
+
   @spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()}
   def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
     with nil <- Activity.normalize(map),
@@ -706,7 +721,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  defp fetch_activities_for_context_query(context, opts) do
+  def fetch_activities_for_context_query(context, opts) do
     public = [Constants.as_public()]
 
     recipients =
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
new file mode 100644 (file)
index 0000000..429a510
--- /dev/null
@@ -0,0 +1,43 @@
+defmodule Pleroma.Web.ActivityPub.Builder do
+  @moduledoc """
+  This module builds the objects. Meant to be used for creating local objects.
+
+  This module encodes our addressing policies and general shape of our objects.
+  """
+
+  alias Pleroma.Object
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.Utils
+  alias Pleroma.Web.ActivityPub.Visibility
+
+  @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
+  def like(actor, object) do
+    object_actor = User.get_cached_by_ap_id(object.data["actor"])
+
+    # Address the actor of the object, and our actor's follower collection if the post is public.
+    to =
+      if Visibility.is_public?(object) do
+        [actor.follower_address, object.data["actor"]]
+      else
+        [object.data["actor"]]
+      end
+
+    # CC everyone who's been addressed in the object, except ourself and the object actor's
+    # follower collection
+    cc =
+      (object.data["to"] ++ (object.data["cc"] || []))
+      |> List.delete(actor.ap_id)
+      |> List.delete(object_actor.follower_address)
+
+    {:ok,
+     %{
+       "id" => Utils.generate_activity_id(),
+       "actor" => actor.ap_id,
+       "type" => "Like",
+       "object" => object.data["id"],
+       "to" => to,
+       "cc" => cc,
+       "context" => object.data["context"]
+     }, []}
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
new file mode 100644 (file)
index 0000000..dc4bce0
--- /dev/null
@@ -0,0 +1,37 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidator do
+  @moduledoc """
+  This module is responsible for validating an object (which can be an activity)
+  and checking if it is both well formed and also compatible with our view of
+  the system.
+  """
+
+  alias Pleroma.Object
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+
+  @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
+  def validate(object, meta)
+
+  def validate(%{"type" => "Like"} = object, meta) do
+    with {:ok, object} <-
+           object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
+      object = stringify_keys(object |> Map.from_struct())
+      {:ok, object, meta}
+    end
+  end
+
+  def stringify_keys(object) do
+    object
+    |> Map.new(fn {key, val} -> {to_string(key), val} end)
+  end
+
+  def fetch_actor_and_object(object) do
+    User.get_or_fetch_by_ap_id(object["actor"])
+    Object.normalize(object["object"])
+    :ok
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
new file mode 100644 (file)
index 0000000..b479c39
--- /dev/null
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
+  import Ecto.Changeset
+
+  alias Pleroma.Object
+  alias Pleroma.User
+
+  def validate_actor_presence(cng, field_name \\ :actor) do
+    cng
+    |> validate_change(field_name, fn field_name, actor ->
+      if User.get_cached_by_ap_id(actor) do
+        []
+      else
+        [{field_name, "can't find user"}]
+      end
+    end)
+  end
+
+  def validate_object_presence(cng, field_name \\ :object) do
+    cng
+    |> validate_change(field_name, fn field_name, object ->
+      if Object.get_cached_by_ap_id(object) do
+        []
+      else
+        [{field_name, "can't find object"}]
+      end
+    end)
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_validator.ex
new file mode 100644 (file)
index 0000000..926804c
--- /dev/null
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
+  use Ecto.Schema
+
+  alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+
+  import Ecto.Changeset
+
+  @primary_key false
+
+  embedded_schema do
+    field(:id, Types.ObjectID, primary_key: true)
+    field(:actor, Types.ObjectID)
+    field(:type, :string)
+    field(:to, {:array, :string})
+    field(:cc, {:array, :string})
+    field(:bto, {:array, :string}, default: [])
+    field(:bcc, {:array, :string}, default: [])
+
+    embeds_one(:object, NoteValidator)
+  end
+
+  def cast_data(data) do
+    cast(%__MODULE__{}, data, __schema__(:fields))
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
new file mode 100644 (file)
index 0000000..49546ce
--- /dev/null
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
+  use Ecto.Schema
+
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+  alias Pleroma.Web.ActivityPub.Utils
+
+  import Ecto.Changeset
+  import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+  @primary_key false
+
+  embedded_schema do
+    field(:id, Types.ObjectID, primary_key: true)
+    field(:type, :string)
+    field(:object, Types.ObjectID)
+    field(:actor, Types.ObjectID)
+    field(:context, :string)
+    field(:to, {:array, :string})
+    field(:cc, {:array, :string})
+  end
+
+  def cast_and_validate(data) do
+    data
+    |> cast_data()
+    |> validate_data()
+  end
+
+  def cast_data(data) do
+    %__MODULE__{}
+    |> cast(data, [:id, :type, :object, :actor, :context, :to, :cc])
+  end
+
+  def validate_data(data_cng) do
+    data_cng
+    |> validate_inclusion(:type, ["Like"])
+    |> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
+    |> validate_actor_presence()
+    |> validate_object_presence()
+    |> validate_existing_like()
+  end
+
+  def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
+    if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do
+      cng
+      |> add_error(:actor, "already liked this object")
+      |> add_error(:object, "already liked by this actor")
+    else
+      cng
+    end
+  end
+
+  def validate_existing_like(cng), do: cng
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
new file mode 100644 (file)
index 0000000..c95b622
--- /dev/null
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
+  use Ecto.Schema
+
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+
+  import Ecto.Changeset
+
+  @primary_key false
+
+  embedded_schema do
+    field(:id, Types.ObjectID, primary_key: true)
+    field(:to, {:array, :string}, default: [])
+    field(:cc, {:array, :string}, default: [])
+    field(:bto, {:array, :string}, default: [])
+    field(:bcc, {:array, :string}, default: [])
+    # TODO: Write type
+    field(:tag, {:array, :map}, default: [])
+    field(:type, :string)
+    field(:content, :string)
+    field(:context, :string)
+    field(:actor, Types.ObjectID)
+    field(:attributedTo, Types.ObjectID)
+    field(:summary, :string)
+    field(:published, Types.DateTime)
+    # TODO: Write type
+    field(:emoji, :map, default: %{})
+    field(:sensitive, :boolean, default: false)
+    # TODO: Write type
+    field(:attachment, {:array, :map}, default: [])
+    field(:replies_count, :integer, default: 0)
+    field(:like_count, :integer, default: 0)
+    field(:announcement_count, :integer, default: 0)
+    field(:inRepyTo, :string)
+
+    field(:likes, {:array, :string}, default: [])
+    field(:announcements, {:array, :string}, default: [])
+
+    # see if needed
+    field(:conversation, :string)
+    field(:context_id, :string)
+  end
+
+  def cast_and_validate(data) do
+    data
+    |> cast_data()
+    |> validate_data()
+  end
+
+  def cast_data(data) do
+    %__MODULE__{}
+    |> cast(data, __schema__(:fields))
+  end
+
+  def validate_data(data_cng) do
+    data_cng
+    |> validate_inclusion(:type, ["Note"])
+    |> validate_required([:id, :actor, :to, :cc, :type, :content, :context])
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex b/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex
new file mode 100644 (file)
index 0000000..4f412fc
--- /dev/null
@@ -0,0 +1,34 @@
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime do
+  @moduledoc """
+  The AP standard defines the date fields in AP as xsd:DateTime. Elixir's
+  DateTime can't parse this, but it can parse the related iso8601. This
+  module punches the date until it looks like iso8601 and normalizes to
+  it.
+
+  DateTimes without a timezone offset are treated as UTC.
+
+  Reference: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published
+  """
+  use Ecto.Type
+
+  def type, do: :string
+
+  def cast(datetime) when is_binary(datetime) do
+    with {:ok, datetime, _} <- DateTime.from_iso8601(datetime) do
+      {:ok, DateTime.to_iso8601(datetime)}
+    else
+      {:error, :missing_offset} -> cast("#{datetime}Z")
+      _e -> :error
+    end
+  end
+
+  def cast(_), do: :error
+
+  def dump(data) do
+    {:ok, data}
+  end
+
+  def load(data) do
+    {:ok, data}
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex
new file mode 100644 (file)
index 0000000..f6e749b
--- /dev/null
@@ -0,0 +1,29 @@
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
+  use Ecto.Type
+
+  def type, do: :string
+
+  def cast(object) when is_binary(object) do
+    # Host has to be present and scheme has to be an http scheme (for now)
+    case URI.parse(object) do
+      %URI{host: nil} -> :error
+      %URI{host: ""} -> :error
+      %URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object}
+      _ -> :error
+    end
+  end
+
+  def cast(%{"id" => object}), do: cast(object)
+
+  def cast(_) do
+    :error
+  end
+
+  def dump(data) do
+    {:ok, data}
+  end
+
+  def load(data) do
+    {:ok, data}
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
new file mode 100644 (file)
index 0000000..7ccee54
--- /dev/null
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.Pipeline do
+  alias Pleroma.Activity
+  alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.MRF
+  alias Pleroma.Web.ActivityPub.ObjectValidator
+  alias Pleroma.Web.ActivityPub.SideEffects
+  alias Pleroma.Web.Federator
+
+  @spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()}
+  def common_pipeline(object, meta) do
+    with {_, {:ok, validated_object, meta}} <-
+           {:validate_object, ObjectValidator.validate(object, meta)},
+         {_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)},
+         {_, {:ok, %Activity{} = activity, meta}} <-
+           {:persist_object, ActivityPub.persist(mrfd_object, meta)},
+         {_, {:ok, %Activity{} = activity, meta}} <-
+           {:execute_side_effects, SideEffects.handle(activity, meta)},
+         {_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
+      {:ok, activity, meta}
+    else
+      {:mrf_object, {:reject, _}} -> {:ok, nil, meta}
+      e -> {:error, e}
+    end
+  end
+
+  defp maybe_federate(activity, meta) do
+    with {:ok, local} <- Keyword.fetch(meta, :local) do
+      if local do
+        Federator.publish(activity)
+        {:ok, :federated}
+      else
+        {:ok, :not_federated}
+      end
+    else
+      _e -> {:error, :badarg}
+    end
+  end
+end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
new file mode 100644 (file)
index 0000000..666a4e3
--- /dev/null
@@ -0,0 +1,28 @@
+defmodule Pleroma.Web.ActivityPub.SideEffects do
+  @moduledoc """
+  This module looks at an inserted object and executes the side effects that it
+  implies. For example, a `Like` activity will increase the like count on the
+  liked object, a `Follow` activity will add the user to the follower
+  collection, and so on.
+  """
+  alias Pleroma.Notification
+  alias Pleroma.Object
+  alias Pleroma.Web.ActivityPub.Utils
+
+  def handle(object, meta \\ [])
+
+  # Tasks this handles:
+  # - Add like to object
+  # - Set up notification
+  def handle(%{data: %{"type" => "Like"}} = object, meta) do
+    liked_object = Object.get_by_ap_id(object.data["object"])
+    Utils.add_like_to_object(object, liked_object)
+    Notification.create_notifications(object)
+    {:ok, object, meta}
+  end
+
+  # Nothing to do
+  def handle(object, meta) do
+    {:ok, object, meta}
+  end
+end
index 09bd9a44290679477778ffa6548e290632daa43a..39feae285593400e358116a99fd9ffcd17de8840 100644 (file)
@@ -13,6 +13,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.ObjectValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+  alias Pleroma.Web.ActivityPub.Pipeline
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.Federator
@@ -202,16 +205,46 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> Map.put("conversation", context)
   end
 
+  defp add_if_present(map, _key, nil), do: map
+
+  defp add_if_present(map, key, value) do
+    Map.put(map, key, value)
+  end
+
   def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
     attachments =
       Enum.map(attachment, fn data ->
-        media_type = data["mediaType"] || data["mimeType"]
-        href = data["url"] || data["href"]
-        url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]
+        url =
+          cond do
+            is_list(data["url"]) -> List.first(data["url"])
+            is_map(data["url"]) -> data["url"]
+            true -> nil
+          end
 
-        data
-        |> Map.put("mediaType", media_type)
-        |> Map.put("url", url)
+        media_type =
+          cond do
+            is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"]
+            is_binary(data["mediaType"]) -> data["mediaType"]
+            is_binary(data["mimeType"]) -> data["mimeType"]
+            true -> nil
+          end
+
+        href =
+          cond do
+            is_map(url) && is_binary(url["href"]) -> url["href"]
+            is_binary(data["url"]) -> data["url"]
+            is_binary(data["href"]) -> data["href"]
+          end
+
+        attachment_url =
+          %{"href" => href}
+          |> add_if_present("mediaType", media_type)
+          |> add_if_present("type", Map.get(url || %{}, "type"))
+
+        %{"url" => [attachment_url]}
+        |> add_if_present("mediaType", media_type)
+        |> add_if_present("type", data["type"])
+        |> add_if_present("name", data["name"])
       end)
 
     Map.put(object, "attachment", attachments)
@@ -491,7 +524,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
            {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
            {_, {:ok, _}} <-
              {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
-           {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
+           {:ok, _relationship} <-
+             FollowingRelationship.update(follower, followed, :follow_accept) do
         ActivityPub.accept(%{
           to: [follower.ap_id],
           actor: followed,
@@ -501,7 +535,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       else
         {:user_blocked, true} ->
           {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
-          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
+          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
 
           ActivityPub.reject(%{
             to: [follower.ap_id],
@@ -512,7 +546,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
         {:follow, {:error, _}} ->
           {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
-          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
+          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
 
           ActivityPub.reject(%{
             to: [follower.ap_id],
@@ -522,7 +556,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           })
 
         {:user_locked, true} ->
-          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending")
+          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
           :noop
       end
 
@@ -542,7 +576,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
          %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
       ActivityPub.accept(%{
         to: follow_activity.data["to"],
         type: "Accept",
@@ -565,7 +599,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
          %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
          {:ok, activity} <-
            ActivityPub.reject(%{
              to: follow_activity.data["to"],
@@ -609,17 +643,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> handle_incoming(options)
   end
 
-  def handle_incoming(
-        %{"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),
-         {:ok, object} <- get_obj_helper(object_id),
-         {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
+  def handle_incoming(%{"type" => "Like"} = data, _options) do
+    with {_, {:ok, cast_data_sym}} <-
+           {:casting_data,
+            data |> LikeValidator.cast_data() |> Ecto.Changeset.apply_action(:insert)},
+         cast_data = ObjectValidator.stringify_keys(Map.from_struct(cast_data_sym)),
+         :ok <- ObjectValidator.fetch_actor_and_object(cast_data),
+         {_, {:ok, cast_data}} <- {:ensure_context_presence, ensure_context_presence(cast_data)},
+         {_, {:ok, cast_data}} <-
+           {:ensure_recipients_presence, ensure_recipients_presence(cast_data)},
+         {_, {:ok, activity, _meta}} <-
+           {:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do
       {:ok, activity}
     else
-      _e -> :error
+      e -> {:error, e}
     end
   end
 
@@ -1243,4 +1280,45 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def maybe_fix_user_url(data), do: data
 
   def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
+
+  defp ensure_context_presence(%{"context" => context} = data) when is_binary(context),
+    do: {:ok, data}
+
+  defp ensure_context_presence(%{"object" => object} = data) when is_binary(object) do
+    with %{data: %{"context" => context}} when is_binary(context) <- Object.normalize(object) do
+      {:ok, Map.put(data, "context", context)}
+    else
+      _ ->
+        {:error, :no_context}
+    end
+  end
+
+  defp ensure_context_presence(_) do
+    {:error, :no_context}
+  end
+
+  defp ensure_recipients_presence(%{"to" => [_ | _], "cc" => [_ | _]} = data),
+    do: {:ok, data}
+
+  defp ensure_recipients_presence(%{"object" => object} = data) do
+    case Object.normalize(object) do
+      %{data: %{"actor" => actor}} ->
+        data =
+          data
+          |> Map.put("to", [actor])
+          |> Map.put("cc", data["cc"] || [])
+
+        {:ok, data}
+
+      nil ->
+        {:error, :no_object}
+
+      _ ->
+        {:error, :no_actor}
+    end
+  end
+
+  defp ensure_recipients_presence(_) do
+    {:error, :no_object}
+  end
 end
index ca54399204c0357141788433ad004ab1b0e72322..831c3bd0276c17e5a3946936fb2c0a4ed0d7bfa3 100644 (file)
@@ -258,7 +258,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
     conn
     |> put_view(Pleroma.Web.AdminAPI.StatusView)
-    |> render("index.json", %{activities: activities, as: :activity})
+    |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
   end
 
   def list_user_statuses(conn, %{"nickname" => nickname} = params) do
@@ -277,7 +277,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
       conn
       |> put_view(StatusView)
-      |> render("index.json", %{activities: activities, as: :activity})
+      |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
     else
       _ -> {:error, :not_found}
     end
@@ -576,9 +576,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
   @doc "Sends registration invite via email"
   def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
-    with true <-
-           Config.get([:instance, :invites_enabled]) &&
-             !Config.get([:instance, :registrations_open]),
+    with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
+         {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
          {:ok, invite_token} <- UserInviteToken.create_invite(),
          email <-
            Pleroma.Emails.UserEmail.user_invitation_email(
@@ -589,6 +588,18 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
            ),
          {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
       json_response(conn, :no_content, "")
+    else
+      {:registrations_open, _} ->
+        errors(
+          conn,
+          {:error, "To send invites you need to set the `registrations_open` option to false."}
+        )
+
+      {:invites_enabled, _} ->
+        errors(
+          conn,
+          {:error, "To send invites you need to set the `invites_enabled` option to true."}
+        )
     end
   end
 
@@ -801,7 +812,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
     conn
     |> put_view(Pleroma.Web.AdminAPI.StatusView)
-    |> render("index.json", %{activities: activities, as: :activity})
+    |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
   end
 
   def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
index ca0bcebc751a138aa66dca1888a5fa0e0c111d57..d50969b2a7a4712e63c619d42dcbb57950cd6006 100644 (file)
@@ -38,7 +38,12 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
       actor: merge_account_views(user),
       content: content,
       created_at: created_at,
-      statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
+      statuses:
+        StatusView.render("index.json", %{
+          activities: statuses,
+          as: :activity,
+          skip_relationships: false
+        }),
       state: report.data["state"],
       notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
     }
diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
new file mode 100644 (file)
index 0000000..3890489
--- /dev/null
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec do
+  alias OpenApiSpex.OpenApi
+  alias Pleroma.Web.Endpoint
+  alias Pleroma.Web.Router
+
+  @behaviour OpenApi
+
+  @impl OpenApi
+  def spec do
+    %OpenApi{
+      servers: [
+        # Populate the Server info from a phoenix endpoint
+        OpenApiSpex.Server.from_endpoint(Endpoint)
+      ],
+      info: %OpenApiSpex.Info{
+        title: "Pleroma",
+        description: Application.spec(:pleroma, :description) |> to_string(),
+        version: Application.spec(:pleroma, :vsn) |> to_string()
+      },
+      # populate the paths from a phoenix router
+      paths: OpenApiSpex.Paths.from_router(Router),
+      components: %OpenApiSpex.Components{
+        securitySchemes: %{
+          "oAuth" => %OpenApiSpex.SecurityScheme{
+            type: "oauth2",
+            flows: %OpenApiSpex.OAuthFlows{
+              password: %OpenApiSpex.OAuthFlow{
+                authorizationUrl: "/oauth/authorize",
+                tokenUrl: "/oauth/token",
+                scopes: %{"read" => "read", "write" => "write", "follow" => "follow"}
+              }
+            }
+          }
+        }
+      }
+    }
+    # discover request/response schemas from path specs
+    |> OpenApiSpex.resolve_schema_modules()
+  end
+end
diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
new file mode 100644 (file)
index 0000000..7348dcb
--- /dev/null
@@ -0,0 +1,27 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Helpers do
+  def request_body(description, schema_ref, opts \\ []) do
+    media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
+
+    content =
+      media_types
+      |> Enum.map(fn type ->
+        {type,
+         %OpenApiSpex.MediaType{
+           schema: schema_ref,
+           example: opts[:example],
+           examples: opts[:examples]
+         }}
+      end)
+      |> Enum.into(%{})
+
+    %OpenApiSpex.RequestBody{
+      description: description,
+      content: content,
+      required: opts[:required] || false
+    }
+  end
+end
diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex
new file mode 100644 (file)
index 0000000..26d8dbd
--- /dev/null
@@ -0,0 +1,96 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.AppOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Helpers
+  alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
+  alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
+
+  @spec open_api_operation(atom) :: Operation.t()
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  @spec create_operation() :: Operation.t()
+  def create_operation do
+    %Operation{
+      tags: ["apps"],
+      summary: "Create an application",
+      description: "Create a new application to obtain OAuth2 credentials",
+      operationId: "AppController.create",
+      requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
+      responses: %{
+        200 => Operation.response("App", "application/json", AppCreateResponse),
+        422 =>
+          Operation.response(
+            "Unprocessable Entity",
+            "application/json",
+            %Schema{
+              type: :object,
+              description:
+                "If a required parameter is missing or improperly formatted, the request will fail.",
+              properties: %{
+                error: %Schema{type: :string}
+              },
+              example: %{
+                "error" => "Validation failed: Redirect URI must be an absolute URI."
+              }
+            }
+          )
+      }
+    }
+  end
+
+  def verify_credentials_operation do
+    %Operation{
+      tags: ["apps"],
+      summary: "Verify your app works",
+      description: "Confirm that the app's OAuth2 credentials work.",
+      operationId: "AppController.verify_credentials",
+      security: [
+        %{
+          "oAuth" => ["read"]
+        }
+      ],
+      responses: %{
+        200 =>
+          Operation.response("App", "application/json", %Schema{
+            type: :object,
+            description:
+              "If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.",
+            properties: %{
+              name: %Schema{type: :string},
+              vapid_key: %Schema{type: :string},
+              website: %Schema{type: :string, nullable: true}
+            },
+            example: %{
+              "name" => "My App",
+              "vapid_key" =>
+                "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
+              "website" => "https://myapp.com/"
+            }
+          }),
+        422 =>
+          Operation.response(
+            "Unauthorized",
+            "application/json",
+            %Schema{
+              type: :object,
+              description:
+                "If the Authorization header contains an invalid token, is malformed, or is not present, an error will be returned indicating an authorization failure.",
+              properties: %{
+                error: %Schema{type: :string}
+              },
+              example: %{
+                "error" => "The access token is invalid."
+              }
+            }
+          )
+      }
+    }
+  end
+end
diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
new file mode 100644 (file)
index 0000000..dd14837
--- /dev/null
@@ -0,0 +1,64 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Helpers
+  alias Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest
+  alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def index_operation do
+    %Operation{
+      tags: ["domain_blocks"],
+      summary: "Fetch domain blocks",
+      description: "View domains the user has blocked.",
+      security: [%{"oAuth" => ["follow", "read:blocks"]}],
+      operationId: "DomainBlockController.index",
+      responses: %{
+        200 => Operation.response("Domain blocks", "application/json", DomainBlocksResponse)
+      }
+    }
+  end
+
+  def create_operation do
+    %Operation{
+      tags: ["domain_blocks"],
+      summary: "Block a domain",
+      description: """
+      Block a domain to:
+
+      - hide all public posts from it
+      - hide all notifications from it
+      - remove all followers from it
+      - prevent following new users from it (but does not remove existing follows)
+      """,
+      operationId: "DomainBlockController.create",
+      requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
+      security: [%{"oAuth" => ["follow", "write:blocks"]}],
+      responses: %{
+        200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
+      }
+    }
+  end
+
+  def delete_operation do
+    %Operation{
+      tags: ["domain_blocks"],
+      summary: "Unblock a domain",
+      description: "Remove a domain block, if it exists in the user's array of blocked domains.",
+      operationId: "DomainBlockController.delete",
+      requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
+      security: [%{"oAuth" => ["follow", "write:blocks"]}],
+      responses: %{
+        200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
+      }
+    }
+  end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_request.ex b/lib/pleroma/web/api_spec/schemas/app_create_request.ex
new file mode 100644 (file)
index 0000000..8a83abe
--- /dev/null
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateRequest do
+  alias OpenApiSpex.Schema
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AppCreateRequest",
+    description: "POST body for creating an app",
+    type: :object,
+    properties: %{
+      client_name: %Schema{type: :string, description: "A name for your application."},
+      redirect_uris: %Schema{
+        type: :string,
+        description:
+          "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
+      },
+      scopes: %Schema{
+        type: :string,
+        description: "Space separated list of scopes. If none is provided, defaults to `read`."
+      },
+      website: %Schema{type: :string, description: "A URL to the homepage of your app"}
+    },
+    required: [:client_name, :redirect_uris],
+    example: %{
+      "client_name" => "My App",
+      "redirect_uris" => "https://myapp.com/auth/callback",
+      "website" => "https://myapp.com/"
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_response.ex b/lib/pleroma/web/api_spec/schemas/app_create_response.ex
new file mode 100644 (file)
index 0000000..f290fb0
--- /dev/null
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateResponse do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AppCreateResponse",
+    description: "Response schema for an app",
+    type: :object,
+    properties: %{
+      id: %Schema{type: :string},
+      name: %Schema{type: :string},
+      client_id: %Schema{type: :string},
+      client_secret: %Schema{type: :string},
+      redirect_uri: %Schema{type: :string},
+      vapid_key: %Schema{type: :string},
+      website: %Schema{type: :string, nullable: true}
+    },
+    example: %{
+      "id" => "123",
+      "name" => "My App",
+      "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
+      "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
+      "vapid_key" =>
+        "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
+      "website" => "https://myapp.com/"
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/domain_block_request.ex b/lib/pleroma/web/api_spec/schemas/domain_block_request.ex
new file mode 100644 (file)
index 0000000..ee92383
--- /dev/null
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest do
+  alias OpenApiSpex.Schema
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "DomainBlockRequest",
+    type: :object,
+    properties: %{
+      domain: %Schema{type: :string}
+    },
+    required: [:domain],
+    example: %{
+      "domain" => "facebook.com"
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex b/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex
new file mode 100644 (file)
index 0000000..d895aca
--- /dev/null
@@ -0,0 +1,16 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse do
+  require OpenApiSpex
+  alias OpenApiSpex.Schema
+
+  OpenApiSpex.schema(%{
+    title: "DomainBlocksResponse",
+    description: "Response schema for domain blocks",
+    type: :array,
+    items: %Schema{type: :string},
+    example: ["google.com", "facebook.com"]
+  })
+end
index c4356f93bd341f08f657fc45ebc2462625826447..c1cd15bb2a37ba98e923f50ab50dc7083995fc9c 100644 (file)
@@ -187,7 +187,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
   end
 
   defp preview?(draft) do
-    preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false
+    preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"])
     %__MODULE__{draft | preview?: preview?}
   end
 
index 2646b9f7b8887c31cc685e54c4c3a68be15da0c6..c56756a3dd7e00e12c9d42320da1367b953784de 100644 (file)
@@ -12,6 +12,8 @@ defmodule Pleroma.Web.CommonAPI do
   alias Pleroma.User
   alias Pleroma.UserRelationship
   alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.Builder
+  alias Pleroma.Web.ActivityPub.Pipeline
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
 
@@ -19,6 +21,7 @@ defmodule Pleroma.Web.CommonAPI do
   import Pleroma.Web.CommonAPI.Utils
 
   require Pleroma.Constants
+  require Logger
 
   def follow(follower, followed) do
     timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
@@ -42,7 +45,7 @@ defmodule Pleroma.Web.CommonAPI do
     with {:ok, follower} <- User.follow(follower, followed),
          %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),
          {:ok, _activity} <-
            ActivityPub.accept(%{
              to: [follower.ap_id],
@@ -57,7 +60,7 @@ defmodule Pleroma.Web.CommonAPI do
   def reject_follow_request(follower, followed) do
     with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
          {:ok, _activity} <-
            ActivityPub.reject(%{
              to: [follower.ap_id],
@@ -109,18 +112,51 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
-  def favorite(id_or_ap_id, user) do
-    with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)},
-         object <- Object.normalize(activity),
-         like_activity <- Utils.get_existing_like(user.ap_id, object) do
-      if like_activity do
-        {:ok, like_activity, object}
-      else
-        ActivityPub.like(user, object)
-      end
+  @spec favorite(User.t(), binary()) :: {:ok, Activity.t() | :already_liked} | {:error, any()}
+  def favorite(%User{} = user, id) do
+    case favorite_helper(user, id) do
+      {:ok, _} = res ->
+        res
+
+      {:error, :not_found} = res ->
+        res
+
+      {:error, e} ->
+        Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}")
+        {:error, dgettext("errors", "Could not favorite")}
+    end
+  end
+
+  def favorite_helper(user, id) do
+    with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)},
+         {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
+         {_, {:ok, %Activity{} = activity, _meta}} <-
+           {:common_pipeline,
+            Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
+      {:ok, activity}
     else
-      {:find_activity, _} -> {:error, :not_found}
-      _ -> {:error, dgettext("errors", "Could not favorite")}
+      {:find_object, _} ->
+        {:error, :not_found}
+
+      {:common_pipeline,
+       {
+         :error,
+         {
+           :validate_object,
+           {
+             :error,
+             changeset
+           }
+         }
+       }} = e ->
+        if {:object, {"already liked by this actor", []}} in changeset.errors do
+          {:ok, :already_liked}
+        else
+          {:error, e}
+        end
+
+      e ->
+        {:error, e}
     end
   end
 
index b49523ec38513455a20f88c5daea3ab2be812f94..4780081b26868bb3ba08f6b508ed14782fbe3999 100644 (file)
@@ -5,10 +5,18 @@
 defmodule Pleroma.Web.ControllerHelper do
   use Pleroma.Web, :controller
 
-  # As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
+  alias Pleroma.Config
+
+  # As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
   @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
-  def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
-  def truthy_param?(value), do: value not in @falsy_param_values
+
+  def explicitly_falsy_param?(value), do: value in @falsy_param_values
+
+  # Note: `nil` and `""` are considered falsy values in Pleroma
+  def falsy_param?(value),
+    do: explicitly_falsy_param?(value) or value in [nil, ""]
+
+  def truthy_param?(value), do: not falsy_param?(value)
 
   def json_response(conn, status, json) do
     conn
@@ -96,4 +104,14 @@ defmodule Pleroma.Web.ControllerHelper do
   def put_if_exist(map, _key, nil), do: map
 
   def put_if_exist(map, key, value), do: Map.put(map, key, value)
+
+  @doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications"
+  def skip_relationships?(params) do
+    if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do
+      false
+    else
+      # BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships.
+      not truthy_param?(params["with_relationships"])
+    end
+  end
 end
index bd6853d121dc18626b4a8543835823c824d0c687..fe425c2f40167371214f6f75f4f09bf16457df7b 100644 (file)
@@ -6,7 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   use Pleroma.Web, :controller
 
   import Pleroma.Web.ControllerHelper,
-    only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
+    only: [
+      add_link_headers: 2,
+      truthy_param?: 1,
+      assign_account_by_id: 2,
+      json_response: 3,
+      skip_relationships?: 1
+    ]
 
   alias Pleroma.Plugs.OAuthScopesPlug
   alias Pleroma.Plugs.RateLimiter
@@ -240,7 +246,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
       conn
       |> add_link_headers(activities)
       |> put_view(StatusView)
-      |> render("index.json", activities: activities, for: reading_user, as: :activity)
+      |> render("index.json",
+        activities: activities,
+        for: reading_user,
+        as: :activity,
+        skip_relationships: skip_relationships?(params)
+      )
     else
       _e -> render_error(conn, :not_found, "Can't find user")
     end
index 5e2871f185ea7fb2d0248141dee4ef10e01cf811..005c604447e3999cf756ec2dd1b842930d46df5e 100644 (file)
@@ -14,17 +14,20 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
   plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
+  plug(OpenApiSpex.Plug.CastAndValidate)
 
   @local_mastodon_name "Mastodon-Local"
 
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
+
   @doc "POST /api/v1/apps"
-  def create(conn, params) do
+  def create(%{body_params: params} = conn, _params) do
     scopes = Scopes.fetch_scopes(params, ["read"])
 
     app_attrs =
       params
-      |> Map.drop(["scope", "scopes"])
-      |> Map.put("scopes", scopes)
+      |> Map.take([:client_name, :redirect_uris, :website])
+      |> Map.put(:scopes, scopes)
 
     with cs <- App.register_changeset(%App{}, app_attrs),
          false <- cs.changes[:client_name] == @local_mastodon_name,
index e4156cbe6f5661f4e665900e8eb44a6a65213eb3..84de794136fee87c2c8b73818c8b8e5e886db669 100644 (file)
@@ -8,6 +8,9 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
   alias Pleroma.Plugs.OAuthScopesPlug
   alias Pleroma.User
 
+  plug(OpenApiSpex.Plug.CastAndValidate)
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation
+
   plug(
     OAuthScopesPlug,
     %{scopes: ["follow", "read:blocks"]} when action == :index
@@ -26,13 +29,13 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
   end
 
   @doc "POST /api/v1/domain_blocks"
-  def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
+  def create(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do
     User.block_domain(blocker, domain)
     json(conn, %{})
   end
 
   @doc "DELETE /api/v1/domain_blocks"
-  def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
+  def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do
     User.unblock_domain(blocker, domain)
     json(conn, %{})
   end
index 0c9218454454768fee8f9335e1cd10c5e9f5d57a..7fb536b0935e7e9c69590b3724487f66700c45ce 100644 (file)
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.MastodonAPI.NotificationController do
   use Pleroma.Web, :controller
 
-  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
 
   alias Pleroma.Notification
   alias Pleroma.Plugs.OAuthScopesPlug
@@ -45,7 +45,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
 
     conn
     |> add_link_headers(notifications)
-    |> render("index.json", notifications: notifications, for: user)
+    |> render("index.json",
+      notifications: notifications,
+      for: user,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   # GET /api/v1/notifications/:id
@@ -66,7 +70,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
     json(conn, %{})
   end
 
-  # POST /api/v1/notifications/dismiss
+  # POST /api/v1/notifications/:id/dismiss
+  # POST /api/v1/notifications/dismiss (deprecated)
   def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
     with {:ok, _notif} <- Notification.dismiss(user, id) do
       json(conn, %{})
index fcab4ef63e22fa2c5e4944f18b94fd40bf46f727..c258742dd80eacb710dac953650dbe9d73b0298c 100644 (file)
@@ -5,13 +5,14 @@
 defmodule Pleroma.Web.MastodonAPI.SearchController do
   use Pleroma.Web, :controller
 
+  import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1]
+
   alias Pleroma.Activity
   alias Pleroma.Plugs.OAuthScopesPlug
   alias Pleroma.Plugs.RateLimiter
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web
-  alias Pleroma.Web.ControllerHelper
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.MastodonAPI.StatusView
 
@@ -66,10 +67,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
 
   defp search_options(params, user) do
     [
+      skip_relationships: skip_relationships?(params),
       resolve: params["resolve"] == "true",
       following: params["following"] == "true",
-      limit: ControllerHelper.fetch_integer_param(params, "limit"),
-      offset: ControllerHelper.fetch_integer_param(params, "offset"),
+      limit: fetch_integer_param(params, "limit"),
+      offset: fetch_integer_param(params, "offset"),
       type: params["type"],
       author: get_author(params),
       for_user: user
@@ -79,12 +81,24 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
 
   defp resource_search(_, "accounts", query, options) do
     accounts = with_fallback(fn -> User.search(query, options) end)
-    AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user)
+
+    AccountView.render("index.json",
+      users: accounts,
+      for: options[:for_user],
+      as: :user,
+      skip_relationships: false
+    )
   end
 
   defp resource_search(_, "statuses", query, options) do
     statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
-    StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
+
+    StatusView.render("index.json",
+      activities: statuses,
+      for: options[:for_user],
+      as: :activity,
+      skip_relationships: options[:skip_relationships]
+    )
   end
 
   defp resource_search(:v2, "hashtags", query, _options) do
index 37afe6949f29f1e116beb5f6c1d88fdf3c19852f..397dd10e3462c7744ef6a3d78066149302577db7 100644 (file)
@@ -5,7 +5,8 @@
 defmodule Pleroma.Web.MastodonAPI.StatusController do
   use Pleroma.Web, :controller
 
-  import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2]
+  import Pleroma.Web.ControllerHelper,
+    only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]
 
   require Ecto.Query
 
@@ -101,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
   `ids` query param is required
   """
-  def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
+  def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
     limit = 100
 
     activities =
@@ -110,7 +111,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
       |> Activity.all_by_ids_with_object()
       |> Enum.filter(&Visibility.visible_for_user?(&1, user))
 
-    render(conn, "index.json", activities: activities, for: user, as: :activity)
+    render(conn, "index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   @doc """
@@ -207,9 +213,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "POST /api/v1/statuses/:id/favourite"
-  def favourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
-         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
+  def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
+    with {:ok, _fav} <- CommonAPI.favorite(user, activity_id),
+         %Activity{} = activity <- Activity.get_by_id(activity_id) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
     end
   end
@@ -360,7 +366,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
     conn
     |> add_link_headers(activities)
-    |> render("index.json", activities: activities, for: user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   @doc "GET /api/v1/bookmarks"
@@ -378,6 +389,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
 
     conn
     |> add_link_headers(bookmarks)
-    |> render("index.json", %{activities: activities, for: user, as: :activity})
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 end
index 91f41416d4aad5381a1ee80c9989e0af34dfc905..b3c58005eb170e6a2cbebef154eb63449ce2732f 100644 (file)
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
   use Pleroma.Web, :controller
 
   import Pleroma.Web.ControllerHelper,
-    only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
+    only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1]
 
   alias Pleroma.Pagination
   alias Pleroma.Plugs.OAuthScopesPlug
@@ -14,9 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
 
-  # TODO: Replace with a macro when there is a Phoenix release with
+  # TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
   # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
-  # in it
 
   plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)
   plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public)
@@ -49,7 +48,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
     conn
     |> add_link_headers(activities)
-    |> render("index.json", activities: activities, for: user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   # GET /api/v1/timelines/direct
@@ -68,7 +72,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
     conn
     |> add_link_headers(activities)
-    |> render("index.json", activities: activities, for: user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   # GET /api/v1/timelines/public
@@ -95,7 +104,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
       conn
       |> add_link_headers(activities, %{"local" => local_only})
-      |> render("index.json", activities: activities, for: user, as: :activity)
+      |> render("index.json",
+        activities: activities,
+        for: user,
+        as: :activity,
+        skip_relationships: skip_relationships?(params)
+      )
     else
       render_error(conn, :unauthorized, "authorization required for timeline view")
     end
@@ -140,7 +154,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
     conn
     |> add_link_headers(activities, %{"local" => local_only})
-    |> render("index.json", activities: activities, for: user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   # GET /api/v1/timelines/list/:list_id
@@ -164,7 +183,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
         |> ActivityPub.fetch_activities_bounded(following, params)
         |> Enum.reverse()
 
-      render(conn, "index.json", activities: activities, for: user, as: :activity)
+      render(conn, "index.json",
+        activities: activities,
+        for: user,
+        as: :activity,
+        skip_relationships: skip_relationships?(params)
+      )
     else
       _e -> render_error(conn, :forbidden, "Error.")
     end
index 99e62f580c4f613a1ae32e3198363285fc93810f..8fb96a22ab482494b115f1a54284bf9f193ba379 100644 (file)
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
   def render("index.json", %{users: users} = opts) do
     reading_user = opts[:for]
 
+    # Note: :skip_relationships option is currently intentionally not supported for accounts
     relationships_opt =
       cond do
         Map.has_key?(opts, :relationships) ->
@@ -73,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     followed_by =
       if following_relationships do
         case FollowingRelationship.find(following_relationships, target, reading_user) do
-          %{state: "accept"} -> true
+          %{state: :follow_accept} -> true
           _ -> false
         end
       else
@@ -83,7 +84,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
     %{
       id: to_string(target.id),
-      following: follow_state == "accept",
+      following: follow_state == :follow_accept,
       followed_by: followed_by,
       blocking:
         UserRelationship.exists?(
@@ -125,7 +126,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
           reading_user,
           &User.subscribed_to?(&2, &1)
         ),
-      requested: follow_state == "pending",
+      requested: follow_state == :follow_pending,
       domain_blocking: User.blocks_domain?(reading_user, target),
       showing_reblogs:
         not UserRelationship.exists?(
@@ -192,11 +193,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       end)
 
     relationship =
-      render("relationship.json", %{
-        user: opts[:for],
-        target: user,
-        relationships: opts[:relationships]
-      })
+      if opts[:skip_relationships] do
+        %{}
+      else
+        render("relationship.json", %{
+          user: opts[:for],
+          target: user,
+          relationships: opts[:relationships]
+        })
+      end
 
     %{
       id: to_string(user.id),
index ae87d47016cd031dab3fd73d7fc36d9058dcc38b..734ffbf39fd909a1837b93d489088fb2087c8dd1 100644 (file)
@@ -51,14 +51,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
             |> Enum.filter(& &1)
             |> Kernel.++(move_activities_targets)
 
-          UserRelationship.view_relationships_option(reading_user, actors)
+          UserRelationship.view_relationships_option(reading_user, actors,
+            source_mutes_only: opts[:skip_relationships]
+          )
       end
 
-    opts = %{
-      for: reading_user,
-      parent_activities: parent_activities,
-      relationships: relationships_opt
-    }
+    opts =
+      opts
+      |> Map.put(:parent_activities, parent_activities)
+      |> Map.put(:relationships, relationships_opt)
 
     safe_render_many(notifications, NotificationView, "show.json", opts)
   end
@@ -82,12 +83,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
 
     mastodon_type = Activity.mastodon_notification_type(activity)
 
+    render_opts = %{
+      relationships: opts[:relationships],
+      skip_relationships: opts[:skip_relationships]
+    }
+
     with %{id: _} = account <-
-           AccountView.render("show.json", %{
-             user: actor,
-             for: reading_user,
-             relationships: opts[:relationships]
-           }) do
+           AccountView.render(
+             "show.json",
+             Map.merge(render_opts, %{user: actor, for: reading_user})
+           ) do
       response = %{
         id: to_string(notification.id),
         type: mastodon_type,
@@ -98,8 +103,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
         }
       }
 
-      render_opts = %{relationships: opts[:relationships]}
-
       case mastodon_type do
         "mention" ->
           put_status(response, activity, reading_user, render_opts)
@@ -111,6 +114,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
           put_status(response, parent_activity_fn.(), reading_user, render_opts)
 
         "move" ->
+          # Note: :skip_relationships option being applied to _account_ rendering (here)
           put_target(response, activity, reading_user, render_opts)
 
         "follow" ->
index cea76e735b0d3040ac33b63d49f6c3a5804c7f1c..b5850e1ae8aa1a9a1320faf22e876c4cc63001b5 100644 (file)
@@ -99,7 +99,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         true ->
           actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
 
-          UserRelationship.view_relationships_option(reading_user, actors)
+          UserRelationship.view_relationships_option(reading_user, actors,
+            source_mutes_only: opts[:skip_relationships]
+          )
       end
 
     opts =
@@ -153,7 +155,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         AccountView.render("show.json", %{
           user: user,
           for: opts[:for],
-          relationships: opts[:relationships]
+          relationships: opts[:relationships],
+          skip_relationships: opts[:skip_relationships]
         }),
       in_reply_to_id: nil,
       in_reply_to_account_id: nil,
@@ -301,6 +304,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         _ -> []
       end
 
+    # Status muted state (would do 1 request per status unless user mutes are preloaded)
     muted =
       thread_muted? ||
         UserRelationship.exists?(
@@ -319,7 +323,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         AccountView.render("show.json", %{
           user: user,
           for: opts[:for],
-          relationships: opts[:relationships]
+          relationships: opts[:relationships],
+          skip_relationships: opts[:skip_relationships]
         }),
       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),
index 30838b1eb76d467845144f1437c49a42eb38a6ea..f9a5ddcc00e79814e5931289bb5ac8615acca3ac 100644 (file)
@@ -75,7 +75,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
         end,
         if Config.get([:instance, :safe_dm_mentions]) do
           "safe_dm_mentions"
-        end
+        end,
+        "pleroma_emoji_reactions"
       ]
       |> Enum.filter(& &1)
 
index 8ecf901f3085112f008a8c3142c7d61637f241e4..1023f16d4911cb0fc3c4b8334d1b6cf9cd742300 100644 (file)
@@ -15,7 +15,12 @@ defmodule Pleroma.Web.OAuth.Scopes do
   Note: `scopes` is used by Mastodon â€” supporting it but sticking to
   OAuth's standard `scope` wherever we control it
   """
-  @spec fetch_scopes(map(), list()) :: list()
+  @spec fetch_scopes(map() | struct(), list()) :: list()
+
+  def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do
+    parse_scopes(scopes, default)
+  end
+
   def fetch_scopes(params, default) do
     parse_scopes(params["scope"] || params["scopes"], default)
   end
index dcba67d038bfcff8fdecb49d9660542a0f19c536..9d0b3b1e45cf0620904ef6f7067fa4bf261bcdd0 100644 (file)
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
   use Pleroma.Web, :controller
 
   import Pleroma.Web.ControllerHelper,
-    only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
+    only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1]
 
   alias Ecto.Changeset
   alias Pleroma.Plugs.OAuthScopesPlug
@@ -139,7 +139,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
     conn
     |> add_link_headers(activities)
     |> put_view(StatusView)
-    |> render("index.json", activities: activities, for: for_user, as: :activity)
+    |> render("index.json",
+      activities: activities,
+      for: for_user,
+      as: :activity,
+      skip_relationships: skip_relationships?(params)
+    )
   end
 
   @doc "POST /api/v1/pleroma/accounts/:id/subscribe"
index 75f61b675a15db1eee3d3110190278aa45b83540..fe1b97a208c9738b81bbc75d7e2c07cf91b5169a 100644 (file)
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
   use Pleroma.Web, :controller
 
-  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
 
   alias Pleroma.Activity
   alias Pleroma.Conversation.Participation
@@ -110,12 +110,11 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
   end
 
   def conversation_statuses(
-        %{assigns: %{user: user}} = conn,
+        %{assigns: %{user: %{id: user_id} = user}} = conn,
         %{"id" => participation_id} = params
       ) do
-    with %Participation{} = participation <-
-           Participation.get(participation_id, preload: [:conversation]),
-         true <- user.id == participation.user_id do
+    with %Participation{user_id: ^user_id} = participation <-
+           Participation.get(participation_id, preload: [:conversation]) do
       params =
         params
         |> Map.put("blocking_user", user)
@@ -124,13 +123,19 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
 
       activities =
         participation.conversation.ap_id
-        |> ActivityPub.fetch_activities_for_context(params)
+        |> ActivityPub.fetch_activities_for_context_query(params)
+        |> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
         |> Enum.reverse()
 
       conn
       |> add_link_headers(activities)
       |> put_view(StatusView)
-      |> render("index.json", %{activities: activities, for: user, as: :activity})
+      |> render("index.json",
+        activities: activities,
+        for: user,
+        as: :activity,
+        skip_relationships: skip_relationships?(params)
+      )
     else
       _error ->
         conn
@@ -184,13 +189,17 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
     end
   end
 
-  def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
+  def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
     with notifications <- Notification.set_read_up_to(user, max_id) do
       notifications = Enum.take(notifications, 80)
 
       conn
       |> put_view(NotificationView)
-      |> render("index.json", %{notifications: notifications, for: user})
+      |> render("index.json",
+        notifications: notifications,
+        for: user,
+        skip_relationships: skip_relationships?(params)
+      )
     end
   end
 end
index 0314535d27d8a3dd4805f88ae602a56a78b2d22f..9d3d7f978b10b48c9afc3960a222fb85bcfb4061 100644 (file)
@@ -64,5 +64,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do
 
   def fetch_data_for_activity(_), do: %{}
 
-  def perform(:fetch, %Activity{} = activity), do: fetch_data_for_activity(activity)
+  def perform(:fetch, %Activity{} = activity) do
+    fetch_data_for_activity(activity)
+    :ok
+  end
 end
index 3d57073d0f533f356ea703d7c00210abb4889ee2..8d13cd6c9333d9fbfbbef0f389c452211c7096c8 100644 (file)
@@ -29,6 +29,7 @@ defmodule Pleroma.Web.Router do
     plug(Pleroma.Plugs.SetUserSessionIdPlug)
     plug(Pleroma.Plugs.EnsureUserKeyPlug)
     plug(Pleroma.Plugs.IdempotencyPlug)
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :authenticated_api do
@@ -45,6 +46,7 @@ defmodule Pleroma.Web.Router do
     plug(Pleroma.Plugs.SetUserSessionIdPlug)
     plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
     plug(Pleroma.Plugs.IdempotencyPlug)
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :admin_api do
@@ -62,6 +64,7 @@ defmodule Pleroma.Web.Router do
     plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
     plug(Pleroma.Plugs.UserIsAdminPlug)
     plug(Pleroma.Plugs.IdempotencyPlug)
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :mastodon_html do
@@ -95,10 +98,12 @@ defmodule Pleroma.Web.Router do
 
   pipeline :config do
     plug(:accepts, ["json", "xml"])
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :pleroma_api do
     plug(:accepts, ["html", "json"])
+    plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
   end
 
   pipeline :mailbox_preview do
@@ -348,9 +353,11 @@ defmodule Pleroma.Web.Router do
 
     get("/notifications", NotificationController, :index)
     get("/notifications/:id", NotificationController, :show)
+    post("/notifications/:id/dismiss", NotificationController, :dismiss)
     post("/notifications/clear", NotificationController, :clear)
-    post("/notifications/dismiss", NotificationController, :dismiss)
     delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
+    # Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
+    post("/notifications/dismiss", NotificationController, :dismiss)
 
     get("/scheduled_statuses", ScheduledActivityController, :index)
     get("/scheduled_statuses/:id", ScheduledActivityController, :show)
@@ -501,6 +508,12 @@ defmodule Pleroma.Web.Router do
     )
   end
 
+  scope "/api" do
+    pipe_through(:api)
+
+    get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
+  end
+
   scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
     pipe_through(:authenticated_api)
 
diff --git a/mix.exs b/mix.exs
index 87c025d89c3a6ff7cbd298fb9c7e3c3687a0fe6e..c5e5fd432dc4c5c8fd96645bfa3a30e1209ca241 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -37,12 +37,21 @@ defmodule Pleroma.Mixfile do
         pleroma: [
           include_executables_for: [:unix],
           applications: [ex_syslogger: :load, syslog: :load],
-          steps: [:assemble, &copy_files/1, &copy_nginx_config/1]
+          steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1]
         ]
       ]
     ]
   end
 
+  def put_otp_version(%{path: target_path} = release) do
+    File.write!(
+      Path.join([target_path, "OTP_VERSION"]),
+      Pleroma.OTPVersion.version()
+    )
+
+    release
+  end
+
   def copy_files(%{path: target_path} = release) do
     File.cp_r!("./rel/files", target_path)
     release
@@ -108,7 +117,7 @@ defmodule Pleroma.Mixfile do
       {:ecto_enum, "~> 1.4"},
       {:ecto_sql, "~> 3.3.2"},
       {:postgrex, ">= 0.13.5"},
-      {:oban, "~> 0.12.1"},
+      {:oban, "~> 1.2"},
       {:gettext, "~> 0.15"},
       {:comeonin, "~> 4.1.1"},
       {:pbkdf2_elixir, "~> 0.12.3"},
@@ -174,12 +183,13 @@ defmodule Pleroma.Mixfile do
       {:flake_id, "~> 0.1.0"},
       {:remote_ip,
        git: "https://git.pleroma.social/pleroma/remote_ip.git",
-       ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"},
+       ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},
       {:captcha,
        git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
        ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
       {:mox, "~> 0.5", only: :test},
-      {:restarter, path: "./restarter"}
+      {:restarter, path: "./restarter"},
+      {:open_api_spex, "~> 3.6"}
     ] ++ oauth_deps()
   end
 
index 6cca578d60e4eab45c672cda8e4fa50b179d7d70..2b9c545486cbf2d8a60ec9512bf72865292ae629 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -26,7 +26,7 @@
   "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
   "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
   "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
-  "ecto": {:hex, :ecto, "3.3.3", "0830bf3aebcbf3d8c1a1811cd581773b6866886c012f52c0f027031fa96a0b53", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "12e368e3c2a2938d7776defaabdae40e82900fc4d8d66120ec1e01dfd8b93c3a"},
+  "ecto": {:hex, :ecto, "3.4.0", "a7a83ab8359bf816ce729e5e65981ce25b9fc5adfc89c2ea3980f4fed0bfd7c1", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5eed18252f5b5bbadec56a24112b531343507dbe046273133176b12190ce19cc"},
   "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
   "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
   "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
@@ -55,7 +55,7 @@
   "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
   "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
   "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
-  "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
+  "jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"},
   "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
   "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
   "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
@@ -73,7 +73,8 @@
   "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
   "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
-  "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"},
+  "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
+  "open_api_spex": {:hex, :open_api_spex, "3.6.0", "64205aba9f2607f71b08fd43e3351b9c5e9898ec5ef49fc0ae35890da502ade9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "126ba3473966277132079cb1d5bf1e3df9e36fe2acd00166e75fd125cecb59c5"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"},
   "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
@@ -96,7 +97,7 @@
   "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", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"},
   "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
   "recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"},
-  "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "825dc00aaba5a1b7c4202a532b696b595dd3bcb3", [ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"]},
+  "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
   "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
   "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
index c618ea3817dae9890a51fd04200e55df8a0c7796..b6f0ac66b3dde98f6a1e95d9b38edeceed381788 100644 (file)
@@ -3,7 +3,6 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
   import Ecto.Query
   alias Pleroma.Activity
   alias Pleroma.Bookmark
-  alias Pleroma.User
   alias Pleroma.Repo
 
   def up do
index 2f336a5e84659b98d04dea60ce43b62cbb2ada28..43d61670555ad019d4fe1b7659d12d23f508b79d 100644 (file)
@@ -1,6 +1,5 @@
 defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do
   use Ecto.Migration
-  alias Pleroma.User
 
   def change do
     execute("""
diff --git a/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs b/priv/repo/migrations/20200328124805_change_following_relationships_state_to_integer.exs
new file mode 100644 (file)
index 0000000..2b0820f
--- /dev/null
@@ -0,0 +1,29 @@
+defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do
+  use Ecto.Migration
+
+  @alter_following_relationship_state "ALTER TABLE following_relationships ALTER COLUMN state"
+
+  def up do
+    execute("""
+    #{@alter_following_relationship_state} TYPE integer USING
+    CASE
+      WHEN state = 'pending' THEN 1
+      WHEN state = 'accept' THEN 2
+      WHEN state = 'reject' THEN 3
+      ELSE 0
+    END;
+    """)
+  end
+
+  def down do
+    execute("""
+    #{@alter_following_relationship_state} TYPE varchar(255) USING
+    CASE
+      WHEN state = 1 THEN 'pending'
+      WHEN state = 2 THEN 'accept'
+      WHEN state = 3 THEN 'reject'
+      ELSE ''
+    END;
+    """)
+  end
+end
diff --git a/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs b/priv/repo/migrations/20200328130139_add_following_relationships_following_id_index.exs
new file mode 100644 (file)
index 0000000..884832f
--- /dev/null
@@ -0,0 +1,11 @@
+defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do
+  use Ecto.Migration
+
+  # [:follower_index] index is useless because of [:follower_id, :following_id] index
+  # [:following_id] index makes sense because of user's followers-targeted queries
+  def change do
+    drop_if_exists(index(:following_relationships, [:follower_id]))
+
+    create_if_not_exists(index(:following_relationships, [:following_id]))
+  end
+end
diff --git a/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs b/priv/repo/migrations/20200402063221_update_oban_jobs_table.exs
new file mode 100644 (file)
index 0000000..e7ff040
--- /dev/null
@@ -0,0 +1,11 @@
+defmodule Pleroma.Repo.Migrations.UpdateObanJobsTable do
+  use Ecto.Migration
+
+  def up do
+    Oban.Migrations.up(version: 8)
+  end
+
+  def down do
+    Oban.Migrations.down(version: 7)
+  end
+end
diff --git a/test/fixtures/emoji/packs/blank.png.zip b/test/fixtures/emoji/packs/blank.png.zip
new file mode 100644 (file)
index 0000000..651daf1
Binary files /dev/null and b/test/fixtures/emoji/packs/blank.png.zip differ
diff --git a/test/fixtures/emoji/packs/default-manifest.json b/test/fixtures/emoji/packs/default-manifest.json
new file mode 100644 (file)
index 0000000..c843380
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "finmoji": {
+    "license": "CC BY-NC-ND 4.0",
+    "homepage": "https://finland.fi/emoji/",
+    "description": "Finland is the first country in the world to publish its own set of country themed emojis. The Finland emoji collection contains 56 tongue-in-cheek emotions, which were created to explain some hard-to-describe Finnish emotions, Finnish words and customs.",
+    "src": "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip",
+    "src_sha256": "384025A1AC6314473863A11AC7AB38A12C01B851A3F82359B89B4D4211D3291D",
+    "files": "finmoji.json"
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/emoji/packs/finmoji.json b/test/fixtures/emoji/packs/finmoji.json
new file mode 100644 (file)
index 0000000..2797709
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "blank": "blank.png"
+}
\ No newline at end of file
diff --git a/test/fixtures/emoji/packs/manifest.json b/test/fixtures/emoji/packs/manifest.json
new file mode 100644 (file)
index 0000000..2d51a45
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "blobs.gg": {
+    "src_sha256": "3a12f3a181678d5b3584a62095411b0d60a335118135910d879920f8ade5a57f",
+    "src": "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip",
+    "license": "Apache 2.0",
+    "homepage": "https://blobs.gg",
+    "files": "blobs_gg.json",
+    "description": "Blob Emoji from blobs.gg repacked as apng"
+  }
+}
\ No newline at end of file
index 865bb383808d2ef6cde9474516fbafd3e0aae817..17a468abb48a12725bbd2a620efbd464584bc682 100644 (file)
@@ -15,28 +15,28 @@ defmodule Pleroma.FollowingRelationshipTest do
     test "returns following addresses without internal.fetch" do
       user = insert(:user)
       fetch_actor = InternalFetchActor.get_actor()
-      FollowingRelationship.follow(fetch_actor, user, "accept")
+      FollowingRelationship.follow(fetch_actor, user, :follow_accept)
       assert FollowingRelationship.following(fetch_actor) == [user.follower_address]
     end
 
     test "returns following addresses without relay" do
       user = insert(:user)
       relay_actor = Relay.get_actor()
-      FollowingRelationship.follow(relay_actor, user, "accept")
+      FollowingRelationship.follow(relay_actor, user, :follow_accept)
       assert FollowingRelationship.following(relay_actor) == [user.follower_address]
     end
 
     test "returns following addresses without remote user" do
       user = insert(:user)
       actor = insert(:user, local: false)
-      FollowingRelationship.follow(actor, user, "accept")
+      FollowingRelationship.follow(actor, user, :follow_accept)
       assert FollowingRelationship.following(actor) == [user.follower_address]
     end
 
     test "returns following addresses with local user" do
       user = insert(:user)
       actor = insert(:user, local: true)
-      FollowingRelationship.follow(actor, user, "accept")
+      FollowingRelationship.follow(actor, user, :follow_accept)
 
       assert FollowingRelationship.following(actor) == [
                actor.follower_address,
index cf8441cf68906e7919ab4f8d5eea8adb6d05f6f2..93fd8eab78eefdbb57e00b324941b56db4b1f458 100644 (file)
@@ -150,13 +150,13 @@ defmodule Pleroma.FormatterTest do
       assert length(mentions) == 3
 
       expected_text =
-        ~s(<span class="h-card"><a data-user="#{gsimg.id}" class="u-url mention" href="#{
+        ~s(<span class="h-card"><a class="u-url mention" data-user="#{gsimg.id}" href="#{
           gsimg.ap_id
-        }" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a data-user="#{
+        }" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a class="u-url mention" data-user="#{
           archaeme.id
-        }" class="u-url mention" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a data-user="#{
+        }" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a class="u-url mention" data-user="#{
           archaeme_remote.id
-        }" class="u-url mention" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>)
+        }" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>)
 
       assert expected_text == text
     end
@@ -171,7 +171,7 @@ defmodule Pleroma.FormatterTest do
       assert length(mentions) == 1
 
       expected_text =
-        ~s(<span class="h-card"><a data-user="#{mike.id}" class="u-url mention" href="#{
+        ~s(<span class="h-card"><a class="u-url mention" data-user="#{mike.id}" href="#{
           mike.ap_id
         }" rel="ugc">@<span>mike</span></a></span> test)
 
@@ -187,7 +187,7 @@ defmodule Pleroma.FormatterTest do
       assert length(mentions) == 1
 
       expected_text =
-        ~s(<span class="h-card"><a data-user="#{o.id}" class="u-url mention" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi)
+        ~s(<span class="h-card"><a class="u-url mention" data-user="#{o.id}" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi)
 
       assert expected_text == text
     end
@@ -209,17 +209,13 @@ defmodule Pleroma.FormatterTest do
       assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}]
 
       assert expected_text ==
-               ~s(<span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{
+               ~s(<span class="h-card"><a class="u-url mention" data-user="#{user.id}" href="#{
                  user.ap_id
-               }" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a data-user="#{
+               }" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
                  other_user.id
-               }" class="u-url mention" href="#{other_user.ap_id}" rel="ugc">@<span>#{
-                 other_user.nickname
-               }</span></a></span> hey dudes i hate <span class="h-card"><a data-user="#{
+               }" href="#{other_user.ap_id}" rel="ugc">@<span>#{other_user.nickname}</span></a></span> hey dudes i hate <span class="h-card"><a class="u-url mention" data-user="#{
                  third_user.id
-               }" class="u-url mention" href="#{third_user.ap_id}" rel="ugc">@<span>#{
-                 third_user.nickname
-               }</span></a></span>)
+               }" href="#{third_user.ap_id}" rel="ugc">@<span>#{third_user.nickname}</span></a></span>)
     end
 
     test "given the 'safe_mention' option, it will still work without any mention" do
index 7cfa40c5178d5e4798b4628ac25ebb83493b4de6..837a9dacd17a9ea854878580817ebd4a39515430 100644 (file)
@@ -537,7 +537,7 @@ defmodule Pleroma.NotificationTest do
           "status" => "hey @#{other_user.nickname}!"
         })
 
-      {:ok, activity_two, _} = CommonAPI.favorite(activity_one.id, third_user)
+      {:ok, activity_two} = CommonAPI.favorite(third_user, activity_one.id)
 
       {enabled_receivers, _disabled_receivers} =
         Notification.get_notified_from_activity(activity_two)
@@ -620,7 +620,7 @@ defmodule Pleroma.NotificationTest do
 
       assert Enum.empty?(Notification.for_user(user))
 
-      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+      {:ok, _} = CommonAPI.favorite(other_user, activity.id)
 
       assert length(Notification.for_user(user)) == 1
 
@@ -637,7 +637,7 @@ defmodule Pleroma.NotificationTest do
 
       assert Enum.empty?(Notification.for_user(user))
 
-      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+      {:ok, _} = CommonAPI.favorite(other_user, activity.id)
 
       assert length(Notification.for_user(user)) == 1
 
@@ -692,7 +692,7 @@ defmodule Pleroma.NotificationTest do
 
       assert Enum.empty?(Notification.for_user(user))
 
-      {:error, _} = CommonAPI.favorite(activity.id, other_user)
+      {:error, :not_found} = CommonAPI.favorite(other_user, activity.id)
 
       assert Enum.empty?(Notification.for_user(user))
     end
index fe583decd7ed1d591cc8975af6d95619b46d015e..198d3b1cf1ac969016e0ca5effc2f0000ee9210b 100644 (file)
@@ -380,7 +380,8 @@ defmodule Pleroma.ObjectTest do
 
       user = insert(:user)
       activity = Activity.get_create_by_object_ap_id(object.data["id"])
-      {:ok, _activity, object} = CommonAPI.favorite(activity.id, user)
+      {:ok, activity} = CommonAPI.favorite(user, activity.id)
+      object = Object.get_by_ap_id(activity.data["object"])
 
       assert object.data["like_count"] == 1
 
index 0ce9f3a0aed9c64cac93180fc904ce59230f8a52..4d3d694f41d995c9008150fab1d8077580fb41bd 100644 (file)
@@ -5,8 +5,10 @@
 defmodule Pleroma.Plugs.RateLimiterTest do
   use Pleroma.Web.ConnCase
 
+  alias Phoenix.ConnTest
   alias Pleroma.Config
   alias Pleroma.Plugs.RateLimiter
+  alias Plug.Conn
 
   import Pleroma.Factory
   import Pleroma.Tests.Helpers, only: [clear_config: 1, clear_config: 2]
@@ -36,8 +38,15 @@ defmodule Pleroma.Plugs.RateLimiterTest do
   end
 
   test "it is disabled if it remote ip plug is enabled but no remote ip is found" do
-    Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1})
-    assert RateLimiter.disabled?(Plug.Conn.assign(build_conn(), :remote_ip_found, false))
+    assert RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, false))
+  end
+
+  test "it is enabled if remote ip found" do
+    refute RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, true))
+  end
+
+  test "it is enabled if remote_ip_found flag doesn't exist" do
+    refute RateLimiter.disabled?(build_conn())
   end
 
   test "it restricts based on config values" do
@@ -58,7 +67,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do
     end
 
     conn = RateLimiter.call(conn, plug_opts)
-    assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
+    assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
     assert conn.halted
 
     Process.sleep(50)
@@ -68,7 +77,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do
     conn = RateLimiter.call(conn, plug_opts)
     assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
 
-    refute conn.status == Plug.Conn.Status.code(:too_many_requests)
+    refute conn.status == Conn.Status.code(:too_many_requests)
     refute conn.resp_body
     refute conn.halted
   end
@@ -98,7 +107,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do
       plug_opts = RateLimiter.init(name: limiter_name, params: ["id"])
 
       conn = build_conn(:get, "/?id=1")
-      conn = Plug.Conn.fetch_query_params(conn)
+      conn = Conn.fetch_query_params(conn)
       conn_2 = build_conn(:get, "/?id=2")
 
       RateLimiter.call(conn, plug_opts)
@@ -119,7 +128,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do
       id = "100"
 
       conn = build_conn(:get, "/?id=#{id}")
-      conn = Plug.Conn.fetch_query_params(conn)
+      conn = Conn.fetch_query_params(conn)
       conn_2 = build_conn(:get, "/?id=#{101}")
 
       RateLimiter.call(conn, plug_opts)
@@ -147,13 +156,13 @@ defmodule Pleroma.Plugs.RateLimiterTest do
 
       conn = RateLimiter.call(conn, plug_opts)
 
-      assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
+      assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
       assert conn.halted
 
       conn_2 = RateLimiter.call(conn_2, plug_opts)
       assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
 
-      refute conn_2.status == Plug.Conn.Status.code(:too_many_requests)
+      refute conn_2.status == Conn.Status.code(:too_many_requests)
       refute conn_2.resp_body
       refute conn_2.halted
     end
@@ -187,7 +196,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do
 
       conn = RateLimiter.call(conn, plug_opts)
 
-      assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
+      assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
       assert conn.halted
     end
 
@@ -210,12 +219,12 @@ defmodule Pleroma.Plugs.RateLimiterTest do
       end
 
       conn = RateLimiter.call(conn, plug_opts)
-      assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
+      assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
       assert conn.halted
 
       conn_2 = RateLimiter.call(conn_2, plug_opts)
       assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
-      refute conn_2.status == Plug.Conn.Status.code(:too_many_requests)
+      refute conn_2.status == Conn.Status.code(:too_many_requests)
       refute conn_2.resp_body
       refute conn_2.halted
     end
index 33b77e7e72559454f89dbf019f1121aa64a48486..bccc1c8d07a435bb5d2df6c07d4507a2deb80f5c 100644 (file)
@@ -60,7 +60,7 @@ defmodule Pleroma.StateTest do
       other_user = insert(:user)
       {:ok, activity} = CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"})
       _ = CommonAPI.follow(user, other_user)
-      CommonAPI.favorite(activity.id, other_user)
+      CommonAPI.favorite(other_user, activity.id)
       CommonAPI.repeat(activity.id, other_user)
 
       assert %{direct: 0, private: 0, public: 1, unlisted: 0} =
index ed1c31d9c47b9678463c8532624c8ee47d084ca3..7b05993d318ff19be76206ef89263b60e778cdf2 100644 (file)
@@ -102,7 +102,7 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
       {:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"})
       {:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"})
 
-      CommonAPI.favorite(id, user2)
+      CommonAPI.favorite(user2, id)
 
       likes = %{
         "first" =>
diff --git a/test/tasks/emoji_test.exs b/test/tasks/emoji_test.exs
new file mode 100644 (file)
index 0000000..f5de3ef
--- /dev/null
@@ -0,0 +1,226 @@
+defmodule Mix.Tasks.Pleroma.EmojiTest do
+  use ExUnit.Case, async: true
+
+  import ExUnit.CaptureIO
+  import Tesla.Mock
+
+  alias Mix.Tasks.Pleroma.Emoji
+
+  describe "ls-packs" do
+    test "with default manifest as url" do
+      mock(fn
+        %{
+          method: :get,
+          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: File.read!("test/fixtures/emoji/packs/default-manifest.json")
+          }
+      end)
+
+      capture_io(fn -> Emoji.run(["ls-packs"]) end) =~
+        "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip"
+    end
+
+    test "with passed manifest as file" do
+      capture_io(fn ->
+        Emoji.run(["ls-packs", "-m", "test/fixtures/emoji/packs/manifest.json"])
+      end) =~ "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip"
+    end
+  end
+
+  describe "get-packs" do
+    test "download pack from default manifest" do
+      mock(fn
+        %{
+          method: :get,
+          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: File.read!("test/fixtures/emoji/packs/default-manifest.json")
+          }
+
+        %{
+          method: :get,
+          url: "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip"
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: File.read!("test/fixtures/emoji/packs/blank.png.zip")
+          }
+
+        %{
+          method: :get,
+          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/finmoji.json"
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: File.read!("test/fixtures/emoji/packs/finmoji.json")
+          }
+      end)
+
+      assert capture_io(fn -> Emoji.run(["get-packs", "finmoji"]) end) =~ "Writing pack.json for"
+
+      emoji_path =
+        Path.join(
+          Pleroma.Config.get!([:instance, :static_dir]),
+          "emoji"
+        )
+
+      assert File.exists?(Path.join([emoji_path, "finmoji", "pack.json"]))
+      on_exit(fn -> File.rm_rf!("test/instance_static/emoji/finmoji") end)
+    end
+
+    test "pack not found" do
+      mock(fn
+        %{
+          method: :get,
+          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: File.read!("test/fixtures/emoji/packs/default-manifest.json")
+          }
+      end)
+
+      assert capture_io(fn -> Emoji.run(["get-packs", "not_found"]) end) =~
+               "No pack named \"not_found\" found"
+    end
+
+    test "raise on bad sha256" do
+      mock(fn
+        %{
+          method: :get,
+          url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip"
+        } ->
+          %Tesla.Env{
+            status: 200,
+            body: File.read!("test/fixtures/emoji/packs/blank.png.zip")
+          }
+      end)
+
+      assert_raise RuntimeError, ~r/^Bad SHA256 for blobs.gg/, fn ->
+        capture_io(fn ->
+          Emoji.run(["get-packs", "blobs.gg", "-m", "test/fixtures/emoji/packs/manifest.json"])
+        end)
+      end
+    end
+  end
+
+  describe "gen-pack" do
+    setup do
+      url = "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip"
+
+      mock(fn %{
+                method: :get,
+                url: ^url
+              } ->
+        %Tesla.Env{status: 200, body: File.read!("test/fixtures/emoji/packs/blank.png.zip")}
+      end)
+
+      {:ok, url: url}
+    end
+
+    test "with default extensions", %{url: url} do
+      name = "pack1"
+      pack_json = "#{name}.json"
+      files_json = "#{name}_file.json"
+      refute File.exists?(pack_json)
+      refute File.exists?(files_json)
+
+      captured =
+        capture_io(fn ->
+          Emoji.run([
+            "gen-pack",
+            url,
+            "--name",
+            name,
+            "--license",
+            "license",
+            "--homepage",
+            "homepage",
+            "--description",
+            "description",
+            "--files",
+            files_json,
+            "--extensions",
+            ".png .gif"
+          ])
+        end)
+
+      assert captured =~ "#{pack_json} has been created with the pack1 pack"
+      assert captured =~ "Using .png .gif extensions"
+
+      assert File.exists?(pack_json)
+      assert File.exists?(files_json)
+
+      on_exit(fn ->
+        File.rm!(pack_json)
+        File.rm!(files_json)
+      end)
+    end
+
+    test "with custom extensions and update existing files", %{url: url} do
+      name = "pack2"
+      pack_json = "#{name}.json"
+      files_json = "#{name}_file.json"
+      refute File.exists?(pack_json)
+      refute File.exists?(files_json)
+
+      captured =
+        capture_io(fn ->
+          Emoji.run([
+            "gen-pack",
+            url,
+            "--name",
+            name,
+            "--license",
+            "license",
+            "--homepage",
+            "homepage",
+            "--description",
+            "description",
+            "--files",
+            files_json,
+            "--extensions",
+            " .png   .gif    .jpeg "
+          ])
+        end)
+
+      assert captured =~ "#{pack_json} has been created with the pack2 pack"
+      assert captured =~ "Using .png .gif .jpeg extensions"
+
+      assert File.exists?(pack_json)
+      assert File.exists?(files_json)
+
+      captured =
+        capture_io(fn ->
+          Emoji.run([
+            "gen-pack",
+            url,
+            "--name",
+            name,
+            "--license",
+            "license",
+            "--homepage",
+            "homepage",
+            "--description",
+            "description",
+            "--files",
+            files_json,
+            "--extensions",
+            " .png   .gif    .jpeg "
+          ])
+        end)
+
+      assert captured =~ "#{pack_json} has been updated with the pack2 pack"
+
+      on_exit(fn ->
+        File.rm!(pack_json)
+        File.rm!(files_json)
+      end)
+    end
+  end
+end
index b45f372630fa6594911b3df16efab20fcd9af1d5..8df835b56d6dba14f2076bdcd949dcde417a5868 100644 (file)
@@ -140,7 +140,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do
     test "user is unsubscribed" do
       followed = insert(:user)
       user = insert(:user)
-      User.follow(user, followed, "accept")
+      User.follow(user, followed, :follow_accept)
 
       Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname])
 
index 8055ebd08ebb24edffd582f64802b043cd2a5fda..a00b1b5e21ef19330077cf0423ecdd2e70682d51 100644 (file)
@@ -194,7 +194,8 @@ defmodule Pleroma.UserTest do
     CommonAPI.follow(pending_follower, locked)
     CommonAPI.follow(pending_follower, locked)
     CommonAPI.follow(accepted_follower, locked)
-    Pleroma.FollowingRelationship.update(accepted_follower, locked, "accept")
+
+    Pleroma.FollowingRelationship.update(accepted_follower, locked, :follow_accept)
 
     assert [^pending_follower] = User.get_follow_requests(locked)
   end
@@ -319,7 +320,7 @@ defmodule Pleroma.UserTest do
           following_address: "http://localhost:4001/users/fuser2/following"
         })
 
-      {:ok, user} = User.follow(user, followed, "accept")
+      {:ok, user} = User.follow(user, followed, :follow_accept)
 
       {:ok, user, _activity} = User.unfollow(user, followed)
 
@@ -332,7 +333,7 @@ defmodule Pleroma.UserTest do
       followed = insert(:user)
       user = insert(:user)
 
-      {:ok, user} = User.follow(user, followed, "accept")
+      {:ok, user} = User.follow(user, followed, :follow_accept)
 
       assert User.following(user) == [user.follower_address, followed.follower_address]
 
@@ -353,7 +354,7 @@ defmodule Pleroma.UserTest do
   test "test if a user is following another user" do
     followed = insert(:user)
     user = insert(:user)
-    User.follow(user, followed, "accept")
+    User.follow(user, followed, :follow_accept)
 
     assert User.following?(user, followed)
     refute User.following?(followed, user)
@@ -1141,8 +1142,8 @@ defmodule Pleroma.UserTest do
       object_two = insert(:note, user: follower)
       activity_two = insert(:note_activity, user: follower, note: object_two)
 
-      {:ok, like, _} = CommonAPI.favorite(activity_two.id, user)
-      {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower)
+      {:ok, like} = CommonAPI.favorite(user, activity_two.id)
+      {:ok, like_two} = CommonAPI.favorite(follower, activity.id)
       {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user)
 
       {:ok, job} = User.delete(user)
@@ -1404,7 +1405,7 @@ defmodule Pleroma.UserTest do
       bio = "A.k.a. @nick@domain.com"
 
       expected_text =
-        ~s(A.k.a. <span class="h-card"><a data-user="#{remote_user.id}" class="u-url mention" href="#{
+        ~s(A.k.a. <span class="h-card"><a class="u-url mention" data-user="#{remote_user.id}" href="#{
           remote_user.ap_id
         }" rel="ugc">@<span>nick@domain.com</span></a></span>)
 
index 573853afaea529ad417c1e8e125bcb8472f39575..fbacb399335cd5bc56e4e198504175a98f8776b5 100644 (file)
@@ -1239,16 +1239,56 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
         filename: "an_image.jpg"
       }
 
-      conn =
+      object =
         conn
         |> assign(:user, user)
         |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
+        |> json_response(:created)
 
-      assert object = json_response(conn, :created)
       assert object["name"] == desc
       assert object["type"] == "Document"
       assert object["actor"] == user.ap_id
+      assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
+      assert is_binary(object_href)
+      assert object_mediatype == "image/jpeg"
+
+      activity_request = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "type" => "Create",
+        "object" => %{
+          "type" => "Note",
+          "content" => "AP C2S test, attachment",
+          "attachment" => [object]
+        },
+        "to" => "https://www.w3.org/ns/activitystreams#Public",
+        "cc" => []
+      }
+
+      activity_response =
+        conn
+        |> assign(:user, user)
+        |> post("/users/#{user.nickname}/outbox", activity_request)
+        |> json_response(:created)
+
+      assert activity_response["id"]
+      assert activity_response["object"]
+      assert activity_response["actor"] == user.ap_id
+
+      assert %Object{data: %{"attachment" => [attachment]}} =
+               Object.normalize(activity_response["object"])
+
+      assert attachment["type"] == "Document"
+      assert attachment["name"] == desc
+
+      assert [
+               %{
+                 "href" => ^object_href,
+                 "type" => "Link",
+                 "mediaType" => ^object_mediatype
+               }
+             ] = attachment["url"]
 
+      # Fails if unauthenticated
       conn
       |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
       |> json_response(403)
index 049b14498ec6bd69c40e50160451bc96b9aaf249..17e7b97deffcbfe93c7eeeabd7550e4dea6c1e2b 100644 (file)
@@ -1900,14 +1900,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "})
       {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "})
 
-      {:ok, _, _} = CommonAPI.favorite(a4.id, user)
-      {:ok, _, _} = CommonAPI.favorite(a3.id, other_user)
-      {:ok, _, _} = CommonAPI.favorite(a3.id, user)
-      {:ok, _, _} = CommonAPI.favorite(a5.id, other_user)
-      {:ok, _, _} = CommonAPI.favorite(a5.id, user)
-      {:ok, _, _} = CommonAPI.favorite(a4.id, other_user)
-      {:ok, _, _} = CommonAPI.favorite(a1.id, user)
-      {:ok, _, _} = CommonAPI.favorite(a1.id, other_user)
+      {:ok, _} = CommonAPI.favorite(user, a4.id)
+      {:ok, _} = CommonAPI.favorite(other_user, a3.id)
+      {:ok, _} = CommonAPI.favorite(user, a3.id)
+      {:ok, _} = CommonAPI.favorite(other_user, a5.id)
+      {:ok, _} = CommonAPI.favorite(user, a5.id)
+      {:ok, _} = CommonAPI.favorite(other_user, a4.id)
+      {:ok, _} = CommonAPI.favorite(user, a1.id)
+      {:ok, _} = CommonAPI.favorite(other_user, a1.id)
       result = ActivityPub.fetch_favourites(user)
 
       assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
new file mode 100644 (file)
index 0000000..3c5c369
--- /dev/null
@@ -0,0 +1,83 @@
+defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.Web.ActivityPub.ObjectValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+  alias Pleroma.Web.ActivityPub.Utils
+  alias Pleroma.Web.CommonAPI
+
+  import Pleroma.Factory
+
+  describe "likes" do
+    setup do
+      user = insert(:user)
+      {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"})
+
+      valid_like = %{
+        "to" => [user.ap_id],
+        "cc" => [],
+        "type" => "Like",
+        "id" => Utils.generate_activity_id(),
+        "object" => post_activity.data["object"],
+        "actor" => user.ap_id,
+        "context" => "a context"
+      }
+
+      %{valid_like: valid_like, user: user, post_activity: post_activity}
+    end
+
+    test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do
+      {:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
+
+      assert "id" in Map.keys(object)
+    end
+
+    test "is valid for a valid object", %{valid_like: valid_like} do
+      assert LikeValidator.cast_and_validate(valid_like).valid?
+    end
+
+    test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
+      without_actor = Map.delete(valid_like, "actor")
+
+      refute LikeValidator.cast_and_validate(without_actor).valid?
+
+      with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
+
+      refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
+    end
+
+    test "it errors when the object is missing or not known", %{valid_like: valid_like} do
+      without_object = Map.delete(valid_like, "object")
+
+      refute LikeValidator.cast_and_validate(without_object).valid?
+
+      with_invalid_object = Map.put(valid_like, "object", "invalidobject")
+
+      refute LikeValidator.cast_and_validate(with_invalid_object).valid?
+    end
+
+    test "it errors when the actor has already like the object", %{
+      valid_like: valid_like,
+      user: user,
+      post_activity: post_activity
+    } do
+      _like = CommonAPI.favorite(user, post_activity.id)
+
+      refute LikeValidator.cast_and_validate(valid_like).valid?
+    end
+
+    test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do
+      wrapped_like =
+        valid_like
+        |> Map.put("actor", %{"id" => valid_like["actor"]})
+        |> Map.put("object", %{"id" => valid_like["object"]})
+
+      validated = LikeValidator.cast_and_validate(wrapped_like)
+
+      assert validated.valid?
+
+      assert {:actor, valid_like["actor"]} in validated.changes
+      assert {:object, valid_like["object"]} in validated.changes
+    end
+  end
+end
diff --git a/test/web/activity_pub/object_validators/note_validator_test.exs b/test/web/activity_pub/object_validators/note_validator_test.exs
new file mode 100644 (file)
index 0000000..30c481f
--- /dev/null
@@ -0,0 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
+  alias Pleroma.Web.ActivityPub.Utils
+
+  import Pleroma.Factory
+
+  describe "Notes" do
+    setup do
+      user = insert(:user)
+
+      note = %{
+        "id" => Utils.generate_activity_id(),
+        "type" => "Note",
+        "actor" => user.ap_id,
+        "to" => [user.follower_address],
+        "cc" => [],
+        "content" => "Hellow this is content.",
+        "context" => "xxx",
+        "summary" => "a post"
+      }
+
+      %{user: user, note: note}
+    end
+
+    test "a basic note validates", %{note: note} do
+      %{valid?: true} = NoteValidator.cast_and_validate(note)
+    end
+  end
+end
diff --git a/test/web/activity_pub/object_validators/types/date_time_test.exs b/test/web/activity_pub/object_validators/types/date_time_test.exs
new file mode 100644 (file)
index 0000000..3e17a94
--- /dev/null
@@ -0,0 +1,32 @@
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTimeTest do
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime
+  use Pleroma.DataCase
+
+  test "it validates an xsd:Datetime" do
+    valid_strings = [
+      "2004-04-12T13:20:00",
+      "2004-04-12T13:20:15.5",
+      "2004-04-12T13:20:00-05:00",
+      "2004-04-12T13:20:00Z"
+    ]
+
+    invalid_strings = [
+      "2004-04-12T13:00",
+      "2004-04-1213:20:00",
+      "99-04-12T13:00",
+      "2004-04-12"
+    ]
+
+    assert {:ok, "2004-04-01T12:00:00Z"} == DateTime.cast("2004-04-01T12:00:00Z")
+
+    Enum.each(valid_strings, fn date_time ->
+      result = DateTime.cast(date_time)
+      assert {:ok, _} = result
+    end)
+
+    Enum.each(invalid_strings, fn date_time ->
+      result = DateTime.cast(date_time)
+      assert :error == result
+    end)
+  end
+end
diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/web/activity_pub/object_validators/types/object_id_test.exs
new file mode 100644 (file)
index 0000000..8342131
--- /dev/null
@@ -0,0 +1,37 @@
+defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
+  use Pleroma.DataCase
+
+  @uris [
+    "http://lain.com/users/lain",
+    "http://lain.com",
+    "https://lain.com/object/1"
+  ]
+
+  @non_uris [
+    "https://",
+    "rin",
+    1,
+    :x,
+    %{"1" => 2}
+  ]
+
+  test "it accepts http uris" do
+    Enum.each(@uris, fn uri ->
+      assert {:ok, uri} == ObjectID.cast(uri)
+    end)
+  end
+
+  test "it accepts an object with a nested uri id" do
+    Enum.each(@uris, fn uri ->
+      assert {:ok, uri} == ObjectID.cast(%{"id" => uri})
+    end)
+  end
+
+  test "it rejects non-uri strings" do
+    Enum.each(@non_uris, fn non_uri ->
+      assert :error == ObjectID.cast(non_uri)
+      assert :error == ObjectID.cast(%{"id" => non_uri})
+    end)
+  end
+end
diff --git a/test/web/activity_pub/pipeline_test.exs b/test/web/activity_pub/pipeline_test.exs
new file mode 100644 (file)
index 0000000..f3c4374
--- /dev/null
@@ -0,0 +1,87 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.PipelineTest do
+  use Pleroma.DataCase
+
+  import Mock
+  import Pleroma.Factory
+
+  describe "common_pipeline/2" do
+    test "it goes through validation, filtering, persisting, side effects and federation for local activities" do
+      activity = insert(:note_activity)
+      meta = [local: true]
+
+      with_mocks([
+        {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
+        {
+          Pleroma.Web.ActivityPub.MRF,
+          [],
+          [filter: fn o -> {:ok, o} end]
+        },
+        {
+          Pleroma.Web.ActivityPub.ActivityPub,
+          [],
+          [persist: fn o, m -> {:ok, o, m} end]
+        },
+        {
+          Pleroma.Web.ActivityPub.SideEffects,
+          [],
+          [handle: fn o, m -> {:ok, o, m} end]
+        },
+        {
+          Pleroma.Web.Federator,
+          [],
+          [publish: fn _o -> :ok end]
+        }
+      ]) do
+        assert {:ok, ^activity, ^meta} =
+                 Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+
+        assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
+        assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
+        assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
+        assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
+        assert_called(Pleroma.Web.Federator.publish(activity))
+      end
+    end
+
+    test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do
+      activity = insert(:note_activity)
+      meta = [local: false]
+
+      with_mocks([
+        {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
+        {
+          Pleroma.Web.ActivityPub.MRF,
+          [],
+          [filter: fn o -> {:ok, o} end]
+        },
+        {
+          Pleroma.Web.ActivityPub.ActivityPub,
+          [],
+          [persist: fn o, m -> {:ok, o, m} end]
+        },
+        {
+          Pleroma.Web.ActivityPub.SideEffects,
+          [],
+          [handle: fn o, m -> {:ok, o, m} end]
+        },
+        {
+          Pleroma.Web.Federator,
+          [],
+          []
+        }
+      ]) do
+        assert {:ok, ^activity, ^meta} =
+                 Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+
+        assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
+        assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
+        assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
+        assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
+      end
+    end
+  end
+end
diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs
new file mode 100644 (file)
index 0000000..b67bd14
--- /dev/null
@@ -0,0 +1,34 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.Object
+  alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.Builder
+  alias Pleroma.Web.ActivityPub.SideEffects
+  alias Pleroma.Web.CommonAPI
+
+  import Pleroma.Factory
+
+  describe "like objects" do
+    setup do
+      user = insert(:user)
+      {:ok, post} = CommonAPI.post(user, %{"status" => "hey"})
+
+      {:ok, like_data, _meta} = Builder.like(user, post.object)
+      {:ok, like, _meta} = ActivityPub.persist(like_data, local: true)
+
+      %{like: like, user: user}
+    end
+
+    test "add the like to the original object", %{like: like, user: user} do
+      {:ok, like, _} = SideEffects.handle(like)
+      object = Object.get_by_ap_id(like.data["object"])
+      assert object.data["like_count"] == 1
+      assert user.ap_id in object.data["likes"]
+    end
+  end
+end
index b2cabbd300745ff5b1dce17f980c7624d4213947..2332029e59a6fda93209bf1a099759ec2b26b5d8 100644 (file)
@@ -334,7 +334,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
         |> Poison.decode!()
         |> Map.put("object", activity.data["object"])
 
-      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+      {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+      refute Enum.empty?(activity.recipients)
 
       assert data["actor"] == "http://mastodon.example.org/users/admin"
       assert data["type"] == "Like"
@@ -1228,19 +1230,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       attachment = %{
         "type" => "Link",
         "mediaType" => "video/mp4",
-        "href" =>
-          "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
-        "mimeType" => "video/mp4",
-        "size" => 5_015_880,
         "url" => [
           %{
             "href" =>
               "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
-            "mediaType" => "video/mp4",
-            "type" => "Link"
+            "mediaType" => "video/mp4"
           }
-        ],
-        "width" => 480
+        ]
       }
 
       assert object.data["url"] ==
@@ -1622,7 +1618,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
         })
 
       user_two = insert(:user)
-      Pleroma.FollowingRelationship.follow(user_two, user, "accept")
+      Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
 
       {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
       {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
@@ -2061,11 +2057,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
                  %{
                    "mediaType" => "video/mp4",
                    "url" => [
-                     %{
-                       "href" => "https://peertube.moe/stat-480.mp4",
-                       "mediaType" => "video/mp4",
-                       "type" => "Link"
-                     }
+                     %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
                    ]
                  }
                ]
@@ -2083,23 +2075,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
                  %{
                    "mediaType" => "video/mp4",
                    "url" => [
-                     %{
-                       "href" => "https://pe.er/stat-480.mp4",
-                       "mediaType" => "video/mp4",
-                       "type" => "Link"
-                     }
+                     %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
                    ]
                  },
                  %{
-                   "href" => "https://pe.er/stat-480.mp4",
                    "mediaType" => "video/mp4",
-                   "mimeType" => "video/mp4",
                    "url" => [
-                     %{
-                       "href" => "https://pe.er/stat-480.mp4",
-                       "mediaType" => "video/mp4",
-                       "type" => "Link"
-                     }
+                     %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
                    ]
                  }
                ]
index de5ffc5b3bae5831cde19f897bf7602f253a879b..6c006206bbad7094869ffb13878690f5381b5657 100644 (file)
@@ -59,7 +59,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
     object = Object.normalize(note)
     user = insert(:user)
 
-    {:ok, like_activity, _} = CommonAPI.favorite(note.id, user)
+    {:ok, like_activity} = CommonAPI.favorite(user, note.id)
 
     result = ObjectView.render("object.json", %{object: like_activity})
 
index fe8a086d8c8449a20eda629d257210bd618b3f35..60ec895f5bc9af9d0b6a9db72d93c30d4639dea5 100644 (file)
@@ -625,6 +625,39 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       assert json_response(conn, :forbidden)
     end
+
+    test "email with +", %{conn: conn, admin: admin} do
+      recipient_email = "foo+bar@baz.com"
+
+      conn
+      |> put_req_header("content-type", "application/json;charset=utf-8")
+      |> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email})
+      |> json_response(:no_content)
+
+      token_record =
+        Pleroma.UserInviteToken
+        |> Repo.all()
+        |> List.last()
+
+      assert token_record
+      refute token_record.used
+
+      notify_email = Config.get([:instance, :notify_email])
+      instance_name = Config.get([:instance, :name])
+
+      email =
+        Pleroma.Emails.UserEmail.user_invitation_email(
+          admin,
+          token_record,
+          recipient_email
+        )
+
+      Swoosh.TestAssertions.assert_email_sent(
+        from: {instance_name, notify_email},
+        to: recipient_email,
+        html_body: email.html_body
+      )
+    end
   end
 
   describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
@@ -637,7 +670,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
-      assert json_response(conn, :internal_server_error)
+      assert json_response(conn, :bad_request) ==
+               "To send invites you need to set the `invites_enabled` option to true."
     end
 
     test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
@@ -646,7 +680,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
-      assert json_response(conn, :internal_server_error)
+      assert json_response(conn, :bad_request) ==
+               "To send invites you need to set the `registrations_open` option to false."
     end
   end
 
@@ -2238,13 +2273,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
           value: :erlang.term_to_binary([])
         )
 
+      Pleroma.Config.TransferTask.load_and_update_env([], false)
+
+      assert Application.get_env(:logger, :backends) == []
+
       conn =
         post(conn, "/api/pleroma/admin/config", %{
           configs: [
             %{
               group: config.group,
               key: config.key,
-              value: [":console", %{"tuple" => ["ExSyslogger", ":ex_syslogger"]}]
+              value: [":console"]
             }
           ]
         })
@@ -2255,8 +2294,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "group" => ":logger",
                    "key" => ":backends",
                    "value" => [
-                     ":console",
-                     %{"tuple" => ["ExSyslogger", ":ex_syslogger"]}
+                     ":console"
                    ],
                    "db" => [":backends"]
                  }
@@ -2264,14 +2302,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
              }
 
       assert Application.get_env(:logger, :backends) == [
-               :console,
-               {ExSyslogger, :ex_syslogger}
+               :console
              ]
-
-      capture_log(fn ->
-        require Logger
-        Logger.warn("Ooops...")
-      end) =~ "Ooops..."
     end
 
     test "saving full setting if value is not keyword", %{conn: conn} do
diff --git a/test/web/api_spec/app_operation_test.exs b/test/web/api_spec/app_operation_test.exs
new file mode 100644 (file)
index 0000000..5b96abb
--- /dev/null
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright Â© 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.AppOperationTest do
+  use Pleroma.Web.ConnCase, async: true
+
+  alias Pleroma.Web.ApiSpec
+  alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
+  alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
+
+  import OpenApiSpex.TestAssertions
+  import Pleroma.Factory
+
+  test "AppCreateRequest example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = AppCreateRequest.schema()
+    assert_schema(schema.example, "AppCreateRequest", api_spec)
+  end
+
+  test "AppCreateResponse example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = AppCreateResponse.schema()
+    assert_schema(schema.example, "AppCreateResponse", api_spec)
+  end
+
+  test "AppController produces a AppCreateResponse", %{conn: conn} do
+    api_spec = ApiSpec.spec()
+    app_attrs = build(:oauth_app)
+
+    json =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post(
+        "/api/v1/apps",
+        Jason.encode!(%{
+          client_name: app_attrs.client_name,
+          redirect_uris: app_attrs.redirect_uris
+        })
+      )
+      |> json_response(200)
+
+    assert_schema(json, "AppCreateResponse", api_spec)
+  end
+end
index 0da0bd2e2903fc83d2133a9f378ae9eb707aaa3b..b12be973f41de23ca7a78b3dbd346b3974050af4 100644 (file)
@@ -284,9 +284,12 @@ defmodule Pleroma.Web.CommonAPITest do
       user = insert(:user)
       other_user = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+      {:ok, post_activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
 
-      {:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)
+      {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
+      assert data["type"] == "Like"
+      assert data["actor"] == user.ap_id
+      assert data["object"] == post_activity.data["object"]
     end
 
     test "retweeting a status twice returns the status" do
@@ -298,13 +301,13 @@ defmodule Pleroma.Web.CommonAPITest do
       {:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)
     end
 
-    test "favoriting a status twice returns the status" do
+    test "favoriting a status twice returns ok, but without the like activity" do
       user = insert(:user)
       other_user = insert(:user)
 
       {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
-      {:ok, %Activity{} = activity, object} = CommonAPI.favorite(activity.id, user)
-      {:ok, ^activity, ^object} = CommonAPI.favorite(activity.id, user)
+      {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
+      assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
     end
   end
 
@@ -562,7 +565,7 @@ defmodule Pleroma.Web.CommonAPITest do
       assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
                CommonAPI.follow(follower, followed)
 
-      assert User.get_follow_state(follower, followed) == "pending"
+      assert User.get_follow_state(follower, followed) == :follow_pending
       assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
       assert User.get_follow_state(follower, followed) == nil
 
@@ -584,7 +587,7 @@ defmodule Pleroma.Web.CommonAPITest do
       assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
                CommonAPI.follow(follower, followed)
 
-      assert User.get_follow_state(follower, followed) == "pending"
+      assert User.get_follow_state(follower, followed) == :follow_pending
       assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
       assert User.get_follow_state(follower, followed) == nil
 
index d383d1714e172a9ef5125dd320a6d28330134137..98cf02d495b59090454525bd9e02e45288f5516c 100644 (file)
@@ -159,11 +159,11 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
       {output, _, _} = Utils.format_input(text, "text/markdown")
 
       assert output ==
-               ~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a data-user="#{
+               ~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a class="u-url mention" data-user="#{
                  user.id
-               }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a data-user="#{
+               }" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a class="u-url mention" data-user="#{
                  user.id
-               }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)
+               }" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)
     end
   end
 
index b693c1a47603401240fee96c0d7a89191c13aaf8..2d256f63c1f3e5b2284cad219a7f0c181da87f03 100644 (file)
@@ -82,9 +82,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert user_data = json_response(conn, 200)
 
       assert user_data["note"] ==
-               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{
+               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
                  user2.id
-               }" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
+               }" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
     end
 
     test "updates the user's locking status", %{conn: conn} do
@@ -273,7 +273,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
     test "update fields", %{conn: conn} do
       fields = [
         %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"},
-        %{"name" => "link", "value" => "cofe.io"}
+        %{"name" => "link.io", "value" => "cofe.io"}
       ]
 
       account_data =
@@ -283,7 +283,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
       assert account_data["fields"] == [
                %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
-               %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
+               %{
+                 "name" => "link.io",
+                 "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)
+               }
              ]
 
       assert account_data["source"]["fields"] == [
@@ -291,14 +294,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
                  "name" => "<a href=\"http://google.com\">foo</a>",
                  "value" => "<script>bar</script>"
                },
-               %{"name" => "link", "value" => "cofe.io"}
+               %{"name" => "link.io", "value" => "cofe.io"}
              ]
+    end
 
+    test "update fields via x-www-form-urlencoded", %{conn: conn} do
       fields =
         [
           "fields_attributes[1][name]=link",
-          "fields_attributes[1][value]=cofe.io",
-          "fields_attributes[0][name]=<a href=\"http://google.com\">foo</a>",
+          "fields_attributes[1][value]=http://cofe.io",
+          "fields_attributes[0][name]=foo",
           "fields_attributes[0][value]=bar"
         ]
         |> Enum.join("&")
@@ -310,32 +315,49 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
         |> json_response(200)
 
       assert account["fields"] == [
-               %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
-               %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
+               %{"name" => "foo", "value" => "bar"},
+               %{
+                 "name" => "link",
+                 "value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>)
+               }
              ]
 
       assert account["source"]["fields"] == [
-               %{
-                 "name" => "<a href=\"http://google.com\">foo</a>",
-                 "value" => "bar"
-               },
-               %{"name" => "link", "value" => "cofe.io"}
+               %{"name" => "foo", "value" => "bar"},
+               %{"name" => "link", "value" => "http://cofe.io"}
              ]
+    end
 
+    test "update fields with empty name", %{conn: conn} do
+      fields = [
+        %{"name" => "foo", "value" => ""},
+        %{"name" => "", "value" => "bar"}
+      ]
+
+      account =
+        conn
+        |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+        |> json_response(200)
+
+      assert account["fields"] == [
+               %{"name" => "foo", "value" => ""}
+             ]
+    end
+
+    test "update fields when invalid request", %{conn: conn} do
       name_limit = Pleroma.Config.get([:instance, :account_field_name_length])
       value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
 
+      long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
       long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
 
-      fields = [%{"name" => "<b>foo<b>", "value" => long_value}]
+      fields = [%{"name" => "foo", "value" => long_value}]
 
       assert %{"error" => "Invalid request"} ==
                conn
                |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
                |> json_response(403)
 
-      long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
-
       fields = [%{"name" => long_name, "value" => "bar"}]
 
       assert %{"error" => "Invalid request"} ==
@@ -346,7 +368,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       Pleroma.Config.put([:instance, :max_account_fields], 1)
 
       fields = [
-        %{"name" => "<b>foo<b>", "value" => "<i>bar</i>"},
+        %{"name" => "foo", "value" => "bar"},
         %{"name" => "link", "value" => "cofe.io"}
       ]
 
@@ -354,20 +376,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
                conn
                |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
                |> json_response(403)
-
-      fields = [
-        %{"name" => "foo", "value" => ""},
-        %{"name" => "", "value" => "bar"}
-      ]
-
-      account =
-        conn
-        |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-        |> json_response(200)
-
-      assert account["fields"] == [
-               %{"name" => "foo", "value" => ""}
-             ]
     end
   end
 end
index a9fa0ce48c40f1f6c127aa928676e35f0b862a3b..a450a732c8bcadf12116757318e0bb28196cf35d 100644 (file)
@@ -794,7 +794,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "Account registration via Application", %{conn: conn} do
       conn =
-        post(conn, "/api/v1/apps", %{
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/v1/apps", %{
           client_name: "client_name",
           redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
           scopes: "read, write, follow"
index 77d234d67eea6675b3e7e9b90c501f175ccc734f..e7b11d14e1461fa910f72ddd73c8b5359a4f5b21 100644 (file)
@@ -16,8 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
 
     conn =
       conn
-      |> assign(:user, token.user)
-      |> assign(:token, token)
+      |> put_req_header("authorization", "Bearer #{token.token}")
       |> get("/api/v1/apps/verify_credentials")
 
     app = Repo.preload(token, :app).app
@@ -37,6 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
 
     conn =
       conn
+      |> put_req_header("content-type", "application/json")
       |> assign(:user, user)
       |> post("/api/v1/apps", %{
         client_name: app_attrs.client_name,
index 8d24b3b882dbfba98ac2e3ede22053744c935162..d66190c90040d6460d886e4ec9e3b6c79843ed56 100644 (file)
@@ -6,20 +6,29 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do
   use Pleroma.Web.ConnCase
 
   alias Pleroma.User
+  alias Pleroma.Web.ApiSpec
+  alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
 
   import Pleroma.Factory
+  import OpenApiSpex.TestAssertions
 
   test "blocking / unblocking a domain" do
     %{user: user, conn: conn} = oauth_access(["write:blocks"])
     other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
 
-    ret_conn = post(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
+    ret_conn =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
 
     assert %{} = json_response(ret_conn, 200)
     user = User.get_cached_by_ap_id(user.ap_id)
     assert User.blocks?(user, other_user)
 
-    ret_conn = delete(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
+    ret_conn =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
 
     assert %{} = json_response(ret_conn, 200)
     user = User.get_cached_by_ap_id(user.ap_id)
@@ -41,5 +50,12 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do
 
     assert "bad.site" in domain_blocks
     assert "even.worse.site" in domain_blocks
+    assert_schema(domain_blocks, "DomainBlocksResponse", ApiSpec.spec())
+  end
+
+  test "DomainBlocksResponse example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = DomainBlocksResponse.schema()
+    assert_schema(schema.example, "DomainBlocksResponse", api_spec)
   end
 end
index dd848821a606b11c5976eb62dcc5ff3824843713..d8dbe4800a51b031f3d789ef1a1bbc9b7392ce6b 100644 (file)
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
-      {:ok, other_user} = User.follow(other_user, user, "pending")
+      {:ok, other_user} = User.follow(other_user, user, :follow_pending)
 
       assert User.following?(other_user, user) == false
 
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
-      {:ok, other_user} = User.follow(other_user, user, "pending")
+      {:ok, other_user} = User.follow(other_user, user, :follow_pending)
 
       user = User.get_cached_by_id(user.id)
       other_user = User.get_cached_by_id(other_user.id)
index 23f94e3a609ada410da2636ec0fa51edf0ad2a90..8c815b415eff8eb0d4c61b1838b1535dfb101a1a 100644 (file)
@@ -12,6 +12,26 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
 
   import Pleroma.Factory
 
+  test "does NOT render account/pleroma/relationship if this is disabled by default" do
+    clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
+
+    %{user: user, conn: conn} = oauth_access(["read:notifications"])
+    other_user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+    {:ok, [_notification]} = Notification.create_notifications(activity)
+
+    response =
+      conn
+      |> assign(:user, user)
+      |> get("/api/v1/notifications")
+      |> json_response(200)
+
+    assert Enum.all?(response, fn n ->
+             get_in(n, ["account", "pleroma", "relationship"]) == %{}
+           end)
+  end
+
   test "list of notifications" do
     %{user: user, conn: conn} = oauth_access(["read:notifications"])
     other_user = insert(:user)
@@ -26,7 +46,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
       |> get("/api/v1/notifications")
 
     expected_response =
-      "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
+      "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
         user.ap_id
       }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
 
@@ -45,7 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
     conn = get(conn, "/api/v1/notifications/#{notification.id}")
 
     expected_response =
-      "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
+      "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
         user.ap_id
       }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
 
@@ -53,7 +73,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
     assert response == expected_response
   end
 
-  test "dismissing a single notification" do
+  test "dismissing a single notification (deprecated endpoint)" do
     %{user: user, conn: conn} = oauth_access(["write:notifications"])
     other_user = insert(:user)
 
@@ -69,6 +89,22 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
     assert %{} = json_response(conn, 200)
   end
 
+  test "dismissing a single notification" do
+    %{user: user, conn: conn} = oauth_access(["write:notifications"])
+    other_user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+    {:ok, [notification]} = Notification.create_notifications(activity)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> post("/api/v1/notifications/#{notification.id}/dismiss")
+
+    assert %{} = json_response(conn, 200)
+  end
+
   test "clearing all notifications" do
     %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"])
     other_user = insert(:user)
@@ -194,10 +230,10 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
       {:ok, private_activity} =
         CommonAPI.post(other_user, %{"status" => ".", "visibility" => "private"})
 
-      {:ok, _, _} = CommonAPI.favorite(public_activity.id, user)
-      {:ok, _, _} = CommonAPI.favorite(direct_activity.id, user)
-      {:ok, _, _} = CommonAPI.favorite(unlisted_activity.id, user)
-      {:ok, _, _} = CommonAPI.favorite(private_activity.id, user)
+      {:ok, _} = CommonAPI.favorite(user, public_activity.id)
+      {:ok, _} = CommonAPI.favorite(user, direct_activity.id)
+      {:ok, _} = CommonAPI.favorite(user, unlisted_activity.id)
+      {:ok, _} = CommonAPI.favorite(user, private_activity.id)
 
       activity_ids =
         conn
@@ -274,7 +310,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
 
     {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
     {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
-    {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
+    {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
     {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
     {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
 
@@ -310,7 +346,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
 
     {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
     {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
-    {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
+    {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
     {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
     {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
 
index d59974d50bf479dc236ac2a0d954e917a36b6a86..162f7b1b2ccdb36d01ec87c35b24cec2ccbd39b5 100644 (file)
@@ -775,7 +775,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
       user1 = insert(:user)
       user2 = insert(:user)
       user3 = insert(:user)
-      CommonAPI.favorite(activity.id, user2)
+      {:ok, _} = CommonAPI.favorite(user2, activity.id)
       {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
       {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
       {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
@@ -850,11 +850,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
       activity = insert(:note_activity)
 
       post(conn, "/api/v1/statuses/#{activity.id}/favourite")
-      assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") |> json_response(200)
+
+      assert post(conn, "/api/v1/statuses/#{activity.id}/favourite")
+             |> json_response(200)
     end
 
     test "returns 404 error for a wrong id", %{conn: conn} do
-      conn = post(conn, "/api/v1/statuses/1/favourite")
+      conn =
+        conn
+        |> post("/api/v1/statuses/1/favourite")
 
       assert json_response(conn, 404) == %{"error" => "Record not found"}
     end
@@ -866,7 +870,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
     test "unfavorites a status and returns it", %{user: user, conn: conn} do
       activity = insert(:note_activity)
 
-      {:ok, _, _} = CommonAPI.favorite(activity.id, user)
+      {:ok, _} = CommonAPI.favorite(user, activity.id)
 
       conn = post(conn, "/api/v1/statuses/#{activity.id}/unfavourite")
 
@@ -1043,6 +1047,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
   end
 
   test "bookmarks" do
+    bookmarks_uri = "/api/v1/bookmarks?with_relationships=true"
+
     %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
     author = insert(:user)
 
@@ -1064,7 +1070,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
 
     assert json_response(response2, 200)["bookmarked"] == true
 
-    bookmarks = get(conn, "/api/v1/bookmarks")
+    bookmarks = get(conn, bookmarks_uri)
 
     assert [json_response(response2, 200), json_response(response1, 200)] ==
              json_response(bookmarks, 200)
@@ -1073,7 +1079,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
 
     assert json_response(response1, 200)["bookmarked"] == false
 
-    bookmarks = get(conn, "/api/v1/bookmarks")
+    bookmarks = get(conn, bookmarks_uri)
 
     assert [json_response(response2, 200)] == json_response(bookmarks, 200)
   end
@@ -1176,7 +1182,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
 
     test "returns users who have favorited the status", %{conn: conn, activity: activity} do
       other_user = insert(:user)
-      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+      {:ok, _} = CommonAPI.favorite(other_user, activity.id)
 
       response =
         conn
@@ -1207,7 +1213,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
       other_user = insert(:user)
       {:ok, _user_relationship} = User.block(user, other_user)
 
-      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+      {:ok, _} = CommonAPI.favorite(other_user, activity.id)
 
       response =
         conn
@@ -1219,7 +1225,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
 
     test "does not fail on an unauthenticated request", %{activity: activity} do
       other_user = insert(:user)
-      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+      {:ok, _} = CommonAPI.favorite(other_user, activity.id)
 
       response =
         build_conn()
@@ -1239,7 +1245,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
           "visibility" => "direct"
         })
 
-      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+      {:ok, _} = CommonAPI.favorite(other_user, activity.id)
 
       favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
 
@@ -1399,7 +1405,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
     {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
     {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
 
-    {:ok, _, _} = CommonAPI.favorite(activity.id, user)
+    {:ok, _} = CommonAPI.favorite(user, activity.id)
 
     first_conn = get(conn, "/api/v1/favourites")
 
@@ -1416,7 +1422,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
           "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
       })
 
-    {:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
+    {:ok, _} = CommonAPI.favorite(user, second_activity.id)
 
     last_like = status["id"]
 
index 97b1c3e66c35b06d82d1488d1b3b89b0a6f19f6f..06efdc901aa763ae6dd58a7628b544f5a06c0a43 100644 (file)
@@ -20,7 +20,30 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
   describe "home" do
     setup do: oauth_access(["read:statuses"])
 
+    test "does NOT render account/pleroma/relationship if this is disabled by default", %{
+      user: user,
+      conn: conn
+    } do
+      clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
+
+      other_user = insert(:user)
+
+      {:ok, _} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> get("/api/v1/timelines/home")
+        |> json_response(200)
+
+      assert Enum.all?(response, fn n ->
+               get_in(n, ["account", "pleroma", "relationship"]) == %{}
+             end)
+    end
+
     test "the home timeline", %{user: user, conn: conn} do
+      uri = "/api/v1/timelines/home?with_relationships=true"
+
       following = insert(:user, nickname: "followed")
       third_user = insert(:user, nickname: "repeated")
 
@@ -28,13 +51,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       {:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"})
       {:ok, _, _} = CommonAPI.repeat(activity.id, following)
 
-      ret_conn = get(conn, "/api/v1/timelines/home")
+      ret_conn = get(conn, uri)
 
       assert Enum.empty?(json_response(ret_conn, :ok))
 
       {:ok, _user} = User.follow(user, following)
 
-      ret_conn = get(conn, "/api/v1/timelines/home")
+      ret_conn = get(conn, uri)
 
       assert [
                %{
@@ -59,7 +82,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
 
       {:ok, _user} = User.follow(third_user, user)
 
-      ret_conn = get(conn, "/api/v1/timelines/home")
+      ret_conn = get(conn, uri)
 
       assert [
                %{
index 81eefd7350d839078ba09793eb127061a3f041b2..c3ec9dfecbcf3f1dc44186748c30097fc2b03c28 100644 (file)
@@ -54,7 +54,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
     user = insert(:user)
     another_user = insert(:user)
     {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
-    {:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user)
+    {:ok, favorite_activity} = CommonAPI.favorite(another_user, create_activity.id)
     {:ok, [notification]} = Notification.create_notifications(favorite_activity)
     create_activity = Activity.get_by_id(create_activity.id)
 
index 43f32260616e9fcca5a481f765a3526e88369806..9bcc07b37c50f2a240b28b29bc5e47eb363ebbc4 100644 (file)
@@ -7,6 +7,8 @@ defmodule Pleroma.Web.NodeInfoTest do
 
   import Pleroma.Factory
 
+  alias Pleroma.Config
+
   setup do: clear_config([:mrf_simple])
   setup do: clear_config(:instance)
 
@@ -47,7 +49,7 @@ defmodule Pleroma.Web.NodeInfoTest do
 
     assert result = json_response(conn, 200)
 
-    assert Pleroma.Config.get([Pleroma.User, :restricted_nicknames]) ==
+    assert Config.get([Pleroma.User, :restricted_nicknames]) ==
              result["metadata"]["restrictedNicknames"]
   end
 
@@ -65,10 +67,10 @@ defmodule Pleroma.Web.NodeInfoTest do
   end
 
   test "returns fieldsLimits field", %{conn: conn} do
-    Pleroma.Config.put([:instance, :max_account_fields], 10)
-    Pleroma.Config.put([:instance, :max_remote_account_fields], 15)
-    Pleroma.Config.put([:instance, :account_field_name_length], 255)
-    Pleroma.Config.put([:instance, :account_field_value_length], 2048)
+    Config.put([:instance, :max_account_fields], 10)
+    Config.put([:instance, :max_remote_account_fields], 15)
+    Config.put([:instance, :account_field_name_length], 255)
+    Config.put([:instance, :account_field_value_length], 2048)
 
     response =
       conn
@@ -82,8 +84,8 @@ defmodule Pleroma.Web.NodeInfoTest do
   end
 
   test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do
-    option = Pleroma.Config.get([:instance, :safe_dm_mentions])
-    Pleroma.Config.put([:instance, :safe_dm_mentions], true)
+    option = Config.get([:instance, :safe_dm_mentions])
+    Config.put([:instance, :safe_dm_mentions], true)
 
     response =
       conn
@@ -92,7 +94,7 @@ defmodule Pleroma.Web.NodeInfoTest do
 
     assert "safe_dm_mentions" in response["metadata"]["features"]
 
-    Pleroma.Config.put([:instance, :safe_dm_mentions], false)
+    Config.put([:instance, :safe_dm_mentions], false)
 
     response =
       conn
@@ -101,14 +103,14 @@ defmodule Pleroma.Web.NodeInfoTest do
 
     refute "safe_dm_mentions" in response["metadata"]["features"]
 
-    Pleroma.Config.put([:instance, :safe_dm_mentions], option)
+    Config.put([:instance, :safe_dm_mentions], option)
   end
 
   describe "`metadata/federation/enabled`" do
     setup do: clear_config([:instance, :federating])
 
     test "it shows if federation is enabled/disabled", %{conn: conn} do
-      Pleroma.Config.put([:instance, :federating], true)
+      Config.put([:instance, :federating], true)
 
       response =
         conn
@@ -117,7 +119,7 @@ defmodule Pleroma.Web.NodeInfoTest do
 
       assert response["metadata"]["federation"]["enabled"] == true
 
-      Pleroma.Config.put([:instance, :federating], false)
+      Config.put([:instance, :federating], false)
 
       response =
         conn
@@ -128,15 +130,39 @@ defmodule Pleroma.Web.NodeInfoTest do
     end
   end
 
+  test "it shows default features flags", %{conn: conn} do
+    response =
+      conn
+      |> get("/nodeinfo/2.1.json")
+      |> json_response(:ok)
+
+    default_features = [
+      "pleroma_api",
+      "mastodon_api",
+      "mastodon_api_streaming",
+      "polls",
+      "pleroma_explicit_addressing",
+      "shareable_emoji_packs",
+      "multifetch",
+      "pleroma_emoji_reactions",
+      "pleroma:api/v1/notifications:include_types_filter"
+    ]
+
+    assert MapSet.subset?(
+             MapSet.new(default_features),
+             MapSet.new(response["metadata"]["features"])
+           )
+  end
+
   test "it shows MRF transparency data if enabled", %{conn: conn} do
-    config = Pleroma.Config.get([:instance, :rewrite_policy])
-    Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
+    config = Config.get([:instance, :rewrite_policy])
+    Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
 
-    option = Pleroma.Config.get([:instance, :mrf_transparency])
-    Pleroma.Config.put([:instance, :mrf_transparency], true)
+    option = Config.get([:instance, :mrf_transparency])
+    Config.put([:instance, :mrf_transparency], true)
 
     simple_config = %{"reject" => ["example.com"]}
-    Pleroma.Config.put(:mrf_simple, simple_config)
+    Config.put(:mrf_simple, simple_config)
 
     response =
       conn
@@ -145,25 +171,25 @@ defmodule Pleroma.Web.NodeInfoTest do
 
     assert response["metadata"]["federation"]["mrf_simple"] == simple_config
 
-    Pleroma.Config.put([:instance, :rewrite_policy], config)
-    Pleroma.Config.put([:instance, :mrf_transparency], option)
-    Pleroma.Config.put(:mrf_simple, %{})
+    Config.put([:instance, :rewrite_policy], config)
+    Config.put([:instance, :mrf_transparency], option)
+    Config.put(:mrf_simple, %{})
   end
 
   test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do
-    config = Pleroma.Config.get([:instance, :rewrite_policy])
-    Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
+    config = Config.get([:instance, :rewrite_policy])
+    Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
 
-    option = Pleroma.Config.get([:instance, :mrf_transparency])
-    Pleroma.Config.put([:instance, :mrf_transparency], true)
+    option = Config.get([:instance, :mrf_transparency])
+    Config.put([:instance, :mrf_transparency], true)
 
-    exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
-    Pleroma.Config.put([:instance, :mrf_transparency_exclusions], ["other.site"])
+    exclusions = Config.get([:instance, :mrf_transparency_exclusions])
+    Config.put([:instance, :mrf_transparency_exclusions], ["other.site"])
 
     simple_config = %{"reject" => ["example.com", "other.site"]}
     expected_config = %{"reject" => ["example.com"]}
 
-    Pleroma.Config.put(:mrf_simple, simple_config)
+    Config.put(:mrf_simple, simple_config)
 
     response =
       conn
@@ -173,9 +199,9 @@ defmodule Pleroma.Web.NodeInfoTest do
     assert response["metadata"]["federation"]["mrf_simple"] == expected_config
     assert response["metadata"]["federation"]["exclusions"] == true
 
-    Pleroma.Config.put([:instance, :rewrite_policy], config)
-    Pleroma.Config.put([:instance, :mrf_transparency], option)
-    Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions)
-    Pleroma.Config.put(:mrf_simple, %{})
+    Config.put([:instance, :rewrite_policy], config)
+    Config.put([:instance, :mrf_transparency], option)
+    Config.put([:instance, :mrf_transparency_exclusions], exclusions)
+    Config.put(:mrf_simple, %{})
   end
 end
index 6787b414b12d5eda7f202fe42e501dfc897a070b..bb349cb1968216ed136d6f512cf21fd785f67abc 100644 (file)
@@ -136,7 +136,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
 
       user = insert(:user)
 
-      {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
+      {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
 
       assert like_activity.data["type"] == "Like"
 
index 2aa87ac30599724cda63926b5da0de2bd3f11e65..ae5334015aa56e029203a922328236a6050b9d10 100644 (file)
@@ -138,7 +138,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
       user: user
     } do
       [activity | _] = insert_pair(:note_activity)
-      CommonAPI.favorite(activity.id, user)
+      CommonAPI.favorite(user, activity.id)
 
       response =
         conn
@@ -155,7 +155,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
       user: user
     } do
       activity = insert(:note_activity)
-      CommonAPI.favorite(activity.id, user)
+      CommonAPI.favorite(user, activity.id)
 
       build_conn()
       |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
@@ -172,7 +172,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
           "visibility" => "direct"
         })
 
-      CommonAPI.favorite(direct.id, user)
+      CommonAPI.favorite(user, direct.id)
 
       for u <- [user, current_user] do
         response =
@@ -202,7 +202,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
           "visibility" => "direct"
         })
 
-      CommonAPI.favorite(direct.id, user)
+      CommonAPI.favorite(user, direct.id)
 
       response =
         conn
@@ -219,7 +219,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
       activities = insert_list(10, :note_activity)
 
       Enum.each(activities, fn activity ->
-        CommonAPI.favorite(activity.id, user)
+        CommonAPI.favorite(user, activity.id)
       end)
 
       third_activity = Enum.at(activities, 2)
@@ -245,7 +245,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
       7
       |> insert_list(:note_activity)
       |> Enum.each(fn activity ->
-        CommonAPI.favorite(activity.id, user)
+        CommonAPI.favorite(user, activity.id)
       end)
 
       response =
@@ -277,7 +277,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
     test "returns 403 error when user has hidden own favorites", %{conn: conn} do
       user = insert(:user, hide_favorites: true)
       activity = insert(:note_activity)
-      CommonAPI.favorite(activity.id, user)
+      CommonAPI.favorite(user, activity.id)
 
       conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites")
 
@@ -287,7 +287,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
     test "hides favorites for new users by default", %{conn: conn} do
       user = insert(:user)
       activity = insert(:note_activity)
-      CommonAPI.favorite(activity.id, user)
+      CommonAPI.favorite(user, activity.id)
 
       assert user.hide_favorites
       conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites")
index 8f0cbe9b25e669378541597a442aca4ed1d0e927..61a1689b991583f27a69f620a7d8576c69a18efb 100644 (file)
@@ -169,6 +169,23 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     id_one = activity.id
     id_two = activity_two.id
     assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result
+
+    {:ok, %{id: id_three}} =
+      CommonAPI.post(other_user, %{
+        "status" => "Bye!",
+        "in_reply_to_status_id" => activity.id,
+        "in_reply_to_conversation_id" => participation.id
+      })
+
+    assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
+             conn
+             |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
+             |> json_response(:ok)
+
+    assert [%{"id" => ^id_three}] =
+             conn
+             |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
+             |> json_response(:ok)
   end
 
   test "PATCH /api/v1/pleroma/conversations/:id" do
index 9f931c941e0cdfc0d9f2bcb902c57cc68eb2e7fe..9121d90e743eca90b157e9e450038597026f1cb3 100644 (file)
@@ -170,7 +170,7 @@ defmodule Pleroma.Web.Push.ImplTest do
           "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
       })
 
-    {:ok, activity, _} = CommonAPI.favorite(activity.id, user)
+    {:ok, activity} = CommonAPI.favorite(user, activity.id)
     object = Object.normalize(activity)
 
     assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post"
index 5b928629b85dcebc6bd6ac84fd5fe265b51a6dcc..eb082b79f49943e44de47655e622ca52801b6e26 100644 (file)
@@ -64,9 +64,6 @@ defmodule Pleroma.Web.StreamerTest do
       blocked = insert(:user)
       {:ok, _user_relationship} = User.block(user, blocked)
 
-      {:ok, activity} = CommonAPI.post(user, %{"status" => ":("})
-      {:ok, notif, _} = CommonAPI.favorite(activity.id, blocked)
-
       task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
 
       Streamer.add_socket(
@@ -74,6 +71,9 @@ defmodule Pleroma.Web.StreamerTest do
         %{transport_pid: task.pid, assigns: %{user: user}}
       )
 
+      {:ok, activity} = CommonAPI.post(user, %{"status" => ":("})
+      {:ok, notif} = CommonAPI.favorite(blocked, activity.id)
+
       Streamer.stream("user:notification", notif)
       Task.await(task)
     end
@@ -83,10 +83,6 @@ defmodule Pleroma.Web.StreamerTest do
     } do
       user2 = insert(:user)
 
-      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
-      {:ok, activity} = CommonAPI.add_mute(user, activity)
-      {:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
-
       task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
 
       Streamer.add_socket(
@@ -94,6 +90,10 @@ defmodule Pleroma.Web.StreamerTest do
         %{transport_pid: task.pid, assigns: %{user: user}}
       )
 
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
+      {:ok, activity} = CommonAPI.add_mute(user, activity)
+      {:ok, notif} = CommonAPI.favorite(user2, activity.id)
+
       Streamer.stream("user:notification", notif)
       Task.await(task)
     end
@@ -103,10 +103,6 @@ defmodule Pleroma.Web.StreamerTest do
     } do
       user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"})
 
-      {:ok, user} = User.block_domain(user, "hecking-lewd-place.com")
-      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
-      {:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
-
       task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
 
       Streamer.add_socket(
@@ -114,6 +110,10 @@ defmodule Pleroma.Web.StreamerTest do
         %{transport_pid: task.pid, assigns: %{user: user}}
       )
 
+      {:ok, user} = User.block_domain(user, "hecking-lewd-place.com")
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
+      {:ok, notif} = CommonAPI.favorite(user2, activity.id)
+
       Streamer.stream("user:notification", notif)
       Task.await(task)
     end
@@ -209,7 +209,7 @@ defmodule Pleroma.Web.StreamerTest do
       Pleroma.Config.put([:instance, :skip_thread_containment], false)
       author = insert(:user)
       user = insert(:user)
-      User.follow(user, author, "accept")
+      User.follow(user, author, :follow_accept)
 
       activity =
         insert(:note_activity,
@@ -232,7 +232,7 @@ defmodule Pleroma.Web.StreamerTest do
       Pleroma.Config.put([:instance, :skip_thread_containment], true)
       author = insert(:user)
       user = insert(:user)
-      User.follow(user, author, "accept")
+      User.follow(user, author, :follow_accept)
 
       activity =
         insert(:note_activity,
@@ -255,7 +255,7 @@ defmodule Pleroma.Web.StreamerTest do
       Pleroma.Config.put([:instance, :skip_thread_containment], false)
       author = insert(:user)
       user = insert(:user, skip_thread_containment: true)
-      User.follow(user, author, "accept")
+      User.follow(user, author, :follow_accept)
 
       activity =
         insert(:note_activity,
@@ -476,7 +476,7 @@ defmodule Pleroma.Web.StreamerTest do
     CommonAPI.hide_reblogs(user1, user2)
 
     {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"})
-    {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, user2)
+    {:ok, favorite_activity} = CommonAPI.favorite(user2, create_activity.id)
 
     task =
       Task.async(fn ->
index 92f9aa0f515ce21fb5340a6ec65eb94e41770946..f6e13b66127030a0faf24f7a1d9a9619d3d242c3 100644 (file)
@@ -109,7 +109,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
     {:ok, user2} = TwitterAPI.register_user(data2)
 
     expected_text =
-      ~s(<span class="h-card"><a data-user="#{user1.id}" class="u-url mention" href="#{
+      ~s(<span class="h-card"><a class="u-url mention" data-user="#{user1.id}" href="#{
         user1.ap_id
       }" rel="ugc">@<span>john</span></a></span> test)