diff options
Diffstat (limited to 'lib')
83 files changed, 1382 insertions, 478 deletions
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex deleted file mode 100644 index f32492169..000000000 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ /dev/null @@ -1,113 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Mix.Tasks.Pleroma.Benchmark do - import Mix.Pleroma - use Mix.Task - - def run(["search"]) do - start_pleroma() - - Benchee.run(%{ - "search" => fn -> - Pleroma.Activity.search(nil, "cofe") - end - }) - end - - def run(["tag"]) do - start_pleroma() - - Benchee.run(%{ - "tag" => fn -> - %{"type" => "Create", "tag" => "cofe"} - |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities() - end - }) - end - - def run(["render_timeline", nickname | _] = args) do - start_pleroma() - user = Pleroma.User.get_by_nickname(nickname) - - activities = - %{} - |> Map.put("type", ["Create", "Announce"]) - |> Map.put("blocking_user", user) - |> Map.put("muting_user", user) - |> Map.put("user", user) - |> Map.put("limit", 4096) - |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities() - |> Enum.reverse() - - inputs = %{ - "1 activity" => Enum.take_random(activities, 1), - "10 activities" => Enum.take_random(activities, 10), - "20 activities" => Enum.take_random(activities, 20), - "40 activities" => Enum.take_random(activities, 40), - "80 activities" => Enum.take_random(activities, 80) - } - - inputs = - if Enum.at(args, 2) == "extended" do - Map.merge(inputs, %{ - "200 activities" => Enum.take_random(activities, 200), - "500 activities" => Enum.take_random(activities, 500), - "2000 activities" => Enum.take_random(activities, 2000), - "4096 activities" => Enum.take_random(activities, 4096) - }) - else - inputs - end - - Benchee.run( - %{ - "Standart rendering" => fn activities -> - Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ - activities: activities, - for: user, - as: :activity - }) - end - }, - inputs: inputs - ) - end - - def run(["adapters"]) do - start_pleroma() - - :ok = - Pleroma.Gun.Conn.open( - "https://httpbin.org/stream-bytes/1500", - :gun_connections - ) - - Process.sleep(1_500) - - Benchee.run( - %{ - "Without conn and without pool" => fn -> - {:ok, %Tesla.Env{}} = - Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - pool: :no_pool, - receive_conn: false - ) - end, - "Without conn and with pool" => fn -> - {:ok, %Tesla.Env{}} = - Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], receive_conn: false) - end, - "With reused conn and without pool" => fn -> - {:ok, %Tesla.Env{}} = - Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], pool: :no_pool) - end, - "With reused conn and with pool" => fn -> - {:ok, %Tesla.Env{}} = Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500") - end - }, - parallel: 10 - ) - end -end diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex new file mode 100644 index 000000000..8379a0c25 --- /dev/null +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -0,0 +1,145 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.Search.Meilisearch do + require Pleroma.Constants + + import Mix.Pleroma + import Ecto.Query + + import Pleroma.Search.Meilisearch, + only: [meili_post: 2, meili_put: 2, meili_get: 1, meili_delete: 1] + + def run(["index"]) do + start_pleroma() + Pleroma.HTML.compile_scrubbers() + + meili_version = + ( + {:ok, result} = meili_get("/version") + + result["pkgVersion"] + ) + + # The ranking rule syntax was changed but nothing about that is mentioned in the changelog + if not Version.match?(meili_version, ">= 0.25.0") do + raise "Meilisearch <0.24.0 not supported" + end + + {:ok, _} = + meili_post( + "/indexes/objects/settings/ranking-rules", + [ + "published:desc", + "words", + "exactness", + "proximity", + "typo", + "attribute", + "sort" + ] + ) + + {:ok, _} = + meili_post( + "/indexes/objects/settings/searchable-attributes", + [ + "content" + ] + ) + + IO.puts("Created indices. Starting to insert posts.") + + chunk_size = Pleroma.Config.get([Pleroma.Search.Meilisearch, :initial_indexing_chunk_size]) + + Pleroma.Repo.transaction( + fn -> + query = + from(Pleroma.Object, + # Only index public and unlisted posts which are notes and have some text + where: + fragment("data->>'type' = 'Note'") and + (fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()) or + fragment("data->'cc' \\? ?", ^Pleroma.Constants.as_public())), + order_by: [desc: fragment("data->'published'")] + ) + + count = query |> Pleroma.Repo.aggregate(:count, :data) + IO.puts("Entries to index: #{count}") + + Pleroma.Repo.stream( + query, + timeout: :infinity + ) + |> Stream.map(&Pleroma.Search.Meilisearch.object_to_search_data/1) + |> Stream.filter(fn o -> not is_nil(o) end) + |> Stream.chunk_every(chunk_size) + |> Stream.transform(0, fn objects, acc -> + new_acc = acc + Enum.count(objects) + + # Reset to the beginning of the line and rewrite it + IO.write("\r") + IO.write("Indexed #{new_acc} entries") + + {[objects], new_acc} + end) + |> Stream.each(fn objects -> + result = + meili_put( + "/indexes/objects/documents", + objects + ) + + with {:ok, res} <- result do + if not Map.has_key?(res, "uid") do + IO.puts("\nFailed to index: #{inspect(result)}") + end + else + e -> IO.puts("\nFailed to index due to network error: #{inspect(e)}") + end + end) + |> Stream.run() + end, + timeout: :infinity + ) + + IO.write("\n") + end + + def run(["clear"]) do + start_pleroma() + + meili_delete("/indexes/objects/documents") + end + + def run(["show-keys", master_key]) do + start_pleroma() + + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + {:ok, result} = + Pleroma.HTTP.get( + Path.join(endpoint, "/keys"), + [{"Authorization", "Bearer #{master_key}"}] + ) + + decoded = Jason.decode!(result.body) + + if decoded["results"] do + Enum.each(decoded["results"], fn %{"description" => desc, "key" => key} -> + IO.puts("#{desc}: #{key}") + end) + else + IO.puts("Error fetching the keys, check the master key is correct: #{inspect(decoded)}") + end + end + + def run(["stats"]) do + start_pleroma() + + {:ok, result} = meili_get("/indexes/objects/stats") + IO.puts("Number of entries: #{result["numberOfDocuments"]}") + IO.puts("Indexing? #{result["isIndexing"]}") + end +end diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex index 8cf9c32a2..cf4fda79f 100644 --- a/lib/phoenix/transports/web_socket/raw.ex +++ b/lib/phoenix/transports/web_socket/raw.ex @@ -26,7 +26,6 @@ defmodule Phoenix.Transports.WebSocket.Raw do conn |> fetch_query_params |> Transport.transport_log(opts[:transport_log]) - |> Transport.force_ssl(handler, endpoint, opts) |> Transport.check_origin(handler, endpoint, opts) case conn do diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 3556aaf9e..8a512dc57 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -368,7 +368,7 @@ defmodule Pleroma.Activity do ) end - defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search + defdelegate search(user, query, options \\ []), to: Pleroma.Search.DatabaseSearch def direct_conversation_id(activity, for_user) do alias Pleroma.Conversation.Participation diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index e68a3c57e..8fa6f3fae 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -54,7 +54,6 @@ defmodule Pleroma.Application do Config.DeprecationWarnings.warn() Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled() Pleroma.ApplicationRequirements.verify!() - setup_instrumenters() load_custom_modules() Pleroma.Docs.JSON.compile() limiters_setup() @@ -91,6 +90,7 @@ defmodule Pleroma.Application do # Define workers and child supervisors to be supervised children = [ + Pleroma.PromEx, Pleroma.Repo, Config.TransferTask, Pleroma.Emoji, @@ -138,7 +138,7 @@ defmodule Pleroma.Application do num else e -> - Logger.warn( + Logger.warning( "Could not get the postgres version: #{inspect(e)}.\nSetting the default value of 9.6" ) @@ -170,29 +170,6 @@ defmodule Pleroma.Application do end end - defp setup_instrumenters do - require Prometheus.Registry - - if Application.get_env(:prometheus, Pleroma.Repo.Instrumenter) do - :ok = - :telemetry.attach( - "prometheus-ecto", - [:pleroma, :repo, :query], - &Pleroma.Repo.Instrumenter.handle_event/4, - %{} - ) - - Pleroma.Repo.Instrumenter.setup() - end - - Pleroma.Web.Endpoint.MetricsExporter.setup() - Pleroma.Web.Endpoint.PipelineInstrumenter.setup() - - # Note: disabled until prometheus-phx is integrated into prometheus-phoenix: - # Pleroma.Web.Endpoint.Instrumenter.setup() - PrometheusPhx.setup() - end - defp cachex_children do [ build_cachex("used_captcha", ttl_interval: seconds_valid_interval()), @@ -322,7 +299,11 @@ defmodule Pleroma.Application do def limiters_setup do config = Config.get(ConcurrentLimiter, []) - [Pleroma.Web.RichMedia.Helpers, Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy] + [ + Pleroma.Web.RichMedia.Helpers, + Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, + Pleroma.Search + ] |> Enum.each(fn module -> mod_config = Keyword.get(config, module, []) diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 44b1c1705..1dbfea3e2 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -34,7 +34,7 @@ defmodule Pleroma.ApplicationRequirements do defp check_welcome_message_config!(:ok) do if Pleroma.Config.get([:welcome, :email, :enabled], false) and not Pleroma.Emails.Mailer.enabled?() do - Logger.warn(""" + Logger.warning(""" To send welcome emails, you need to enable the mailer. Welcome emails will NOT be sent with the current config. @@ -53,7 +53,7 @@ defmodule Pleroma.ApplicationRequirements do def check_confirmation_accounts!(:ok) do if Pleroma.Config.get([:instance, :account_activation_required]) && not Pleroma.Emails.Mailer.enabled?() do - Logger.warn(""" + Logger.warning(""" Account activation is required, but the mailer is disabled. Users will NOT be able to confirm their accounts with this config. Either disable account activation or enable the mailer. @@ -168,8 +168,6 @@ defmodule Pleroma.ApplicationRequirements do check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "exiftool"), check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"), check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"), - check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"), - check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "convert"), check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "ffprobe") ] diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index b53b15d95..1cd3241ea 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -24,7 +24,7 @@ defmodule Pleroma.Config.DeprecationWarnings do filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, []) if Pleroma.Upload.Filter.Exiftool in filters do - Logger.warn(""" + Logger.warning(""" !!!DEPRECATION WARNING!!! Your config is using Exiftool as a filter instead of Exiftool.StripLocation. This should work for now, but you are advised to change to the new configuration to prevent possible issues later: @@ -63,7 +63,7 @@ defmodule Pleroma.Config.DeprecationWarnings do |> Enum.any?(fn {_, v} -> Enum.any?(v, &is_binary/1) end) if has_strings do - Logger.warn(""" + Logger.warning(""" !!!DEPRECATION WARNING!!! Your config is using strings in the SimplePolicy configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later: @@ -121,7 +121,7 @@ defmodule Pleroma.Config.DeprecationWarnings do has_strings = Config.get([:instance, :quarantined_instances]) |> Enum.any?(&is_binary/1) if has_strings do - Logger.warn(""" + Logger.warning(""" !!!DEPRECATION WARNING!!! Your config is using strings in the quarantined_instances configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later: @@ -158,7 +158,7 @@ defmodule Pleroma.Config.DeprecationWarnings do has_strings = Config.get([:mrf, :transparency_exclusions]) |> Enum.any?(&is_binary/1) if has_strings do - Logger.warn(""" + Logger.warning(""" !!!DEPRECATION WARNING!!! Your config is using strings in the transparency_exclusions configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later: @@ -193,7 +193,7 @@ defmodule Pleroma.Config.DeprecationWarnings do def check_hellthread_threshold do if Config.get([:mrf_hellthread, :threshold]) do - Logger.warn(""" + Logger.warning(""" !!!DEPRECATION WARNING!!! You are using the old configuration mechanism for the hellthread filter. Please check config.md. """) @@ -274,7 +274,7 @@ defmodule Pleroma.Config.DeprecationWarnings do if warning == "" do :ok else - Logger.warn(warning_preface <> warning) + Logger.warning(warning_preface <> warning) :error end end @@ -284,7 +284,7 @@ defmodule Pleroma.Config.DeprecationWarnings do whitelist = Config.get([:media_proxy, :whitelist]) if Enum.any?(whitelist, &(not String.starts_with?(&1, "http"))) do - Logger.warn(""" + Logger.warning(""" !!!DEPRECATION WARNING!!! Your config is using old format (only domain) for MediaProxy whitelist option. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. """) @@ -299,7 +299,7 @@ defmodule Pleroma.Config.DeprecationWarnings do pool_config = Config.get(:connections_pool) if timeout = pool_config[:await_up_timeout] do - Logger.warn(""" + Logger.warning(""" !!!DEPRECATION WARNING!!! Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`. Please change to `config :pleroma, :connections_pool, connect_timeout` to ensure compatibility with future releases. """) @@ -331,7 +331,7 @@ defmodule Pleroma.Config.DeprecationWarnings do "\n* `:timeout` options in #{pool_name} pool is now `:recv_timeout`" end) - Logger.warn(Enum.join([warning_preface | pool_warnings])) + Logger.warning(Enum.join([warning_preface | pool_warnings])) Config.put(:pools, updated_config) :error diff --git a/lib/pleroma/config/getting.ex b/lib/pleroma/config/getting.ex index f9b66bba6..ec93fd02a 100644 --- a/lib/pleroma/config/getting.ex +++ b/lib/pleroma/config/getting.ex @@ -5,4 +5,11 @@ defmodule Pleroma.Config.Getting do @callback get(any()) :: any() @callback get(any(), any()) :: any() + + def get(key), do: get(key, nil) + def get(key, default), do: impl().get(key, default) + + def impl do + Application.get_env(:pleroma, :config_impl, Pleroma.Config) + end end diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex index 483d2bb79..836f0c1a7 100644 --- a/lib/pleroma/config/oban.ex +++ b/lib/pleroma/config/oban.ex @@ -23,7 +23,7 @@ defmodule Pleroma.Config.Oban do You are using old workers in Oban crontab settings, which were removed. Please, remove setting from crontab in your config file (prod.secret.exs): #{inspect(setting)} """ - |> Logger.warn() + |> Logger.warning() List.delete(acc, setting) else diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 44a984019..91885347f 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -55,8 +55,7 @@ defmodule Pleroma.Config.TransferTask do started_applications = Application.started_applications() - # TODO: some problem with prometheus after restart! - reject = [nil, :prometheus, :postgrex] + reject = [nil, :postgrex] reject = if restart_pleroma? do @@ -145,7 +144,7 @@ defmodule Pleroma.Config.TransferTask do error_msg = "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{inspect(value)} error: #{inspect(error)}" - Logger.warn(error_msg) + Logger.warning(error_msg) nil end @@ -179,12 +178,12 @@ defmodule Pleroma.Config.TransferTask do :ok = Application.start(app) else nil -> - Logger.warn("#{app} is not started.") + Logger.warning("#{app} is not started.") error -> error |> inspect() - |> Logger.warn() + |> Logger.warning() end end diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex index 6508f1947..456a8fd54 100644 --- a/lib/pleroma/docs/generator.ex +++ b/lib/pleroma/docs/generator.ex @@ -17,6 +17,8 @@ defmodule Pleroma.Docs.Generator do # This shouldn't be needed as all modules are expected to have module_info/1, # but in test enviroments some transient modules `:elixir_compiler_XX` # are loaded for some reason (where XX is a random integer). + Code.ensure_loaded(module) + if function_exported?(module, :module_info, 1) do module.module_info(:attributes) |> Keyword.get_values(:behaviour) diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index 97d4b8f70..eb6f6816b 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -59,7 +59,7 @@ defmodule Pleroma.Emoji.Loader do Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}") if not Enum.empty?(files) do - Logger.warn( + Logger.warning( "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{Enum.join(files, ", ")}" ) end diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex index 7c5785def..804cd11c7 100644 --- a/lib/pleroma/gun/conn.ex +++ b/lib/pleroma/gun/conn.ex @@ -56,7 +56,7 @@ defmodule Pleroma.Gun.Conn do {:ok, conn, protocol} else error -> - Logger.warn( + Logger.warning( "Opening proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}" ) @@ -90,7 +90,7 @@ defmodule Pleroma.Gun.Conn do {:ok, conn, protocol} else error -> - Logger.warn( + Logger.warning( "Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}" ) @@ -106,7 +106,7 @@ defmodule Pleroma.Gun.Conn do {:ok, conn, protocol} else error -> - Logger.warn( + Logger.warning( "Opening connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}" ) diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 24c845fcd..07dfea55b 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -8,11 +8,12 @@ defmodule Pleroma.Helpers.MediaHelper do """ alias Pleroma.HTTP + alias Vix.Vips.Operation require Logger def missing_dependencies do - Enum.reduce([imagemagick: "convert", ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc -> + Enum.reduce([ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc -> if Pleroma.Utils.command_available?(executable) do acc else @@ -22,54 +23,22 @@ defmodule Pleroma.Helpers.MediaHelper do end def image_resize(url, options) do - with executable when is_binary(executable) <- System.find_executable("convert"), - {:ok, args} <- prepare_image_resize_args(options), - {:ok, env} <- HTTP.get(url, [], pool: :media), - {:ok, fifo_path} <- mkfifo() do - args = List.flatten([fifo_path, args]) - run_fifo(fifo_path, env, executable, args) + with {:ok, env} <- HTTP.get(url, [], pool: :media), + {:ok, resized} <- + Operation.thumbnail_buffer(env.body, options.max_width, + height: options.max_height, + size: :VIPS_SIZE_DOWN + ) do + if options[:format] == "png" do + Operation.pngsave_buffer(resized, Q: options[:quality]) + else + Operation.jpegsave_buffer(resized, Q: options[:quality], interlace: true) + end else - nil -> {:error, {:convert, :command_not_found}} {:error, _} = error -> error end end - defp prepare_image_resize_args( - %{max_width: max_width, max_height: max_height, format: "png"} = options - ) do - quality = options[:quality] || 85 - resize = Enum.join([max_width, "x", max_height, ">"]) - - args = [ - "-resize", - resize, - "-quality", - to_string(quality), - "png:-" - ] - - {:ok, args} - end - - defp prepare_image_resize_args(%{max_width: max_width, max_height: max_height} = options) do - quality = options[:quality] || 85 - resize = Enum.join([max_width, "x", max_height, ">"]) - - args = [ - "-interlace", - "Plane", - "-resize", - resize, - "-quality", - to_string(quality), - "jpg:-" - ] - - {:ok, args} - end - - defp prepare_image_resize_args(_), do: {:error, :missing_options} - # Note: video thumbnail is intentionally not resized (always has original dimensions) def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 252a6aba5..e9bb2023a 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -70,15 +70,15 @@ defmodule Pleroma.HTTP.AdapterHelper do {:ok, parse_host(host), port} else {_, _} -> - Logger.warn("Parsing port failed #{inspect(proxy)}") + Logger.warning("Parsing port failed #{inspect(proxy)}") {:error, :invalid_proxy_port} :error -> - Logger.warn("Parsing port failed #{inspect(proxy)}") + Logger.warning("Parsing port failed #{inspect(proxy)}") {:error, :invalid_proxy_port} _ -> - Logger.warn("Parsing proxy failed #{inspect(proxy)}") + Logger.warning("Parsing proxy failed #{inspect(proxy)}") {:error, :invalid_proxy} end end @@ -88,7 +88,7 @@ defmodule Pleroma.HTTP.AdapterHelper do {:ok, type, parse_host(host), port} else _ -> - Logger.warn("Parsing proxy failed #{inspect(proxy)}") + Logger.warning("Parsing proxy failed #{inspect(proxy)}") {:error, :invalid_proxy} end end diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex index ca399b6c8..888079c1e 100644 --- a/lib/pleroma/http/web_push.ex +++ b/lib/pleroma/http/web_push.ex @@ -6,7 +6,11 @@ defmodule Pleroma.HTTP.WebPush do @moduledoc false def post(url, payload, headers, options \\ []) do - list_headers = Map.to_list(headers) + list_headers = + headers + |> Map.to_list() + |> Kernel.++([{"content-type", "octet-stream"}]) + Pleroma.HTTP.post(url, payload, list_headers, options) end end diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 9756c66dc..c497a4fb7 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -97,13 +97,9 @@ defmodule Pleroma.Instances.Instance do def reachable?(url_or_host) when is_binary(url_or_host), do: true def set_reachable(url_or_host) when is_binary(url_or_host) do - with host <- host(url_or_host), - %Instance{} = existing_record <- Repo.get_by(Instance, %{host: host}) do - {:ok, _instance} = - existing_record - |> changeset(%{unreachable_since: nil}) - |> Repo.update() - end + %Instance{host: host(url_or_host)} + |> changeset(%{unreachable_since: nil}) + |> Repo.insert(on_conflict: {:replace, [:unreachable_since]}, conflict_target: :host) end def set_reachable(_), do: {:error, nil} @@ -177,7 +173,7 @@ defmodule Pleroma.Instances.Instance do end rescue e -> - Logger.warn("Instance.get_or_update_favicon(\"#{host}\") error: #{inspect(e)}") + Logger.warning("Instance.get_or_update_favicon(\"#{host}\") error: #{inspect(e)}") nil end @@ -205,7 +201,7 @@ defmodule Pleroma.Instances.Instance do end rescue e -> - Logger.warn( + Logger.warning( "Instance.scrape_favicon(\"#{to_string(instance_uri)}\") error: #{inspect(e)}" ) @@ -288,7 +284,7 @@ defmodule Pleroma.Instances.Instance do end rescue e -> - Logger.warn( + Logger.warning( "Instance.scrape_metadata(\"#{to_string(instance_uri)}\") error: #{inspect(e)}" ) diff --git a/lib/pleroma/maintenance.ex b/lib/pleroma/maintenance.ex index eb5a6ef42..1e39b03e6 100644 --- a/lib/pleroma/maintenance.ex +++ b/lib/pleroma/maintenance.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Maintenance do "full" -> Logger.info("Running VACUUM FULL.") - Logger.warn( + Logger.warning( "Re-packing your entire database may take a while and will consume extra disk space during the process." ) diff --git a/lib/pleroma/migrators/support/base_migrator.ex b/lib/pleroma/migrators/support/base_migrator.ex index 3bcd59fd0..ce88caac7 100644 --- a/lib/pleroma/migrators/support/base_migrator.ex +++ b/lib/pleroma/migrators/support/base_migrator.ex @@ -73,7 +73,7 @@ defmodule Pleroma.Migrators.Support.BaseMigrator do data_migration.state == :manual or data_migration.name in manual_migrations -> message = "Data migration is in manual execution or manual fix mode." update_status(:manual, message) - Logger.warn("#{__MODULE__}: #{message}") + Logger.warning("#{__MODULE__}: #{message}") data_migration.state == :complete -> on_complete(data_migration) @@ -109,7 +109,7 @@ defmodule Pleroma.Migrators.Support.BaseMigrator do Putting data migration to manual fix mode. Try running `#{__MODULE__}.retry_failed/0`. """ - Logger.warn("#{__MODULE__}: #{message}") + Logger.warning("#{__MODULE__}: #{message}") update_status(:manual, message) on_complete(data_migration()) @@ -125,7 +125,7 @@ defmodule Pleroma.Migrators.Support.BaseMigrator do defp on_complete(data_migration) do if data_migration.feature_lock || feature_state() == :disabled do - Logger.warn( + Logger.warning( "#{__MODULE__}: migration complete but feature is locked; consider enabling." ) diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index aa137d250..fa5baf1a4 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -328,6 +328,52 @@ defmodule Pleroma.Object do end end + def increase_quotes_count(ap_id) do + Object + |> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id))) + |> update([o], + set: [ + data: + fragment( + """ + safe_jsonb_set(?, '{quotesCount}', + (coalesce((?->>'quotesCount')::int, 0) + 1)::varchar::jsonb, true) + """, + o.data, + o.data + ) + ] + ) + |> Repo.update_all([]) + |> case do + {1, [object]} -> set_cache(object) + _ -> {:error, "Not found"} + end + end + + def decrease_quotes_count(ap_id) do + Object + |> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id))) + |> update([o], + set: [ + data: + fragment( + """ + safe_jsonb_set(?, '{quotesCount}', + (greatest(0, (?->>'quotesCount')::int - 1))::varchar::jsonb, true) + """, + o.data, + o.data + ) + ] + ) + |> Repo.update_all([]) + |> case do + {1, [object]} -> set_cache(object) + _ -> {:error, "Not found"} + end + end + def increase_vote_count(ap_id, name, actor) do with %Object{} = object <- Object.normalize(ap_id, fetch: false), "Question" <- object.data["type"] do diff --git a/lib/pleroma/prom_ex.ex b/lib/pleroma/prom_ex.ex new file mode 100644 index 000000000..6608708b7 --- /dev/null +++ b/lib/pleroma/prom_ex.ex @@ -0,0 +1,49 @@ +defmodule Pleroma.PromEx do + use PromEx, otp_app: :pleroma + + alias PromEx.Plugins + + @impl true + def plugins do + [ + # PromEx built in plugins + Plugins.Application, + Plugins.Beam, + {Plugins.Phoenix, router: Pleroma.Web.Router, endpoint: Pleroma.Web.Endpoint}, + Plugins.Ecto, + Plugins.Oban + # Plugins.PhoenixLiveView, + # Plugins.Absinthe, + # Plugins.Broadway, + + # Add your own PromEx metrics plugins + # Pleroma.Users.PromExPlugin + ] + end + + @impl true + def dashboard_assigns do + [ + datasource_id: Pleroma.Config.get([Pleroma.PromEx, :datasource]), + default_selected_interval: "30s" + ] + end + + @impl true + def dashboards do + [ + # PromEx built in Grafana dashboards + {:prom_ex, "application.json"}, + {:prom_ex, "beam.json"}, + {:prom_ex, "phoenix.json"}, + {:prom_ex, "ecto.json"}, + {:prom_ex, "oban.json"} + # {:prom_ex, "phoenix_live_view.json"}, + # {:prom_ex, "absinthe.json"}, + # {:prom_ex, "broadway.json"}, + + # Add your dashboard definitions here with the format: {:otp_app, "path_in_priv"} + # {:pleroma, "/grafana_dashboards/user_metrics.json"} + ] + end +end diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex index 515b0c1ff..a50a59b3b 100644 --- a/lib/pleroma/repo.ex +++ b/lib/pleroma/repo.ex @@ -11,8 +11,6 @@ defmodule Pleroma.Repo do import Ecto.Query require Logger - defmodule Instrumenter, do: use(Prometheus.EctoInstrumenter) - @doc """ Dynamically loads the repository url from the DATABASE_URL environment variable. diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index 2248c2713..880940d07 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -192,7 +192,7 @@ defmodule Pleroma.ReverseProxy do halt(conn) {:error, error, conn} -> - Logger.warn( + Logger.warning( "#{__MODULE__} request to #{url} failed while reading/chunking: #{inspect(error)}" ) diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex index 0ed51ad07..63c6cb45b 100644 --- a/lib/pleroma/scheduled_activity.ex +++ b/lib/pleroma/scheduled_activity.ex @@ -6,7 +6,6 @@ defmodule Pleroma.ScheduledActivity do use Ecto.Schema alias Ecto.Multi - alias Pleroma.Config alias Pleroma.Repo alias Pleroma.ScheduledActivity alias Pleroma.User @@ -20,6 +19,8 @@ defmodule Pleroma.ScheduledActivity do @min_offset :timer.minutes(5) + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) + schema "scheduled_activities" do belongs_to(:user, User, type: FlakeId.Ecto.CompatType) field(:scheduled_at, :naive_datetime) @@ -87,7 +88,7 @@ defmodule Pleroma.ScheduledActivity do |> where([sa], type(sa.scheduled_at, :date) == type(^scheduled_at, :date)) |> select([sa], count(sa.id)) |> Repo.one() - |> Kernel.>=(Config.get([ScheduledActivity, :daily_user_limit])) + |> Kernel.>=(@config_impl.get([ScheduledActivity, :daily_user_limit])) end def exceeds_total_user_limit?(user_id) do @@ -95,7 +96,7 @@ defmodule Pleroma.ScheduledActivity do |> where(user_id: ^user_id) |> select([sa], count(sa.id)) |> Repo.one() - |> Kernel.>=(Config.get([ScheduledActivity, :total_user_limit])) + |> Kernel.>=(@config_impl.get([ScheduledActivity, :total_user_limit])) end def far_enough?(scheduled_at) when is_binary(scheduled_at) do @@ -123,7 +124,7 @@ defmodule Pleroma.ScheduledActivity do def create(%User{} = user, attrs) do Multi.new() |> Multi.insert(:scheduled_activity, new(user, attrs)) - |> maybe_add_jobs(Config.get([ScheduledActivity, :enabled])) + |> maybe_add_jobs(@config_impl.get([ScheduledActivity, :enabled])) |> Repo.transaction() |> transaction_response end diff --git a/lib/pleroma/search.ex b/lib/pleroma/search.ex new file mode 100644 index 000000000..3b266e59b --- /dev/null +++ b/lib/pleroma/search.ex @@ -0,0 +1,17 @@ +defmodule Pleroma.Search do + alias Pleroma.Workers.SearchIndexingWorker + + def add_to_index(%Pleroma.Activity{id: activity_id}) do + SearchIndexingWorker.enqueue("add_to_index", %{"activity" => activity_id}) + end + + def remove_from_index(%Pleroma.Object{id: object_id}) do + SearchIndexingWorker.enqueue("remove_from_index", %{"object" => object_id}) + end + + def search(query, options) do + search_module = Pleroma.Config.get([Pleroma.Search, :module], Pleroma.Activity) + + search_module.search(options[:for_user], query, options) + end +end diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/search/database_search.ex index 0b9b24aa4..c6311e0c7 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/search/database_search.ex @@ -1,9 +1,10 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Activity.Search do +defmodule Pleroma.Search.DatabaseSearch do alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Object.Fetcher alias Pleroma.Pagination alias Pleroma.User @@ -13,8 +14,11 @@ defmodule Pleroma.Activity.Search do import Ecto.Query + @behaviour Pleroma.Search.SearchBackend + + @impl true def search(user, search_query, options \\ []) do - index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin + index_type = if Config.get([:database, :rum_enabled]), do: :rum, else: :gin limit = Enum.min([Keyword.get(options, :limit), 40]) offset = Keyword.get(options, :offset, 0) author = Keyword.get(options, :author) @@ -45,6 +49,12 @@ defmodule Pleroma.Activity.Search do end end + @impl true + def add_to_index(_activity), do: :ok + + @impl true + def remove_from_index(_object), do: :ok + def maybe_restrict_author(query, %User{} = author) do Activity.Queries.by_author(query, author) end @@ -136,8 +146,8 @@ defmodule Pleroma.Activity.Search do ) end - defp maybe_restrict_local(q, user) do - limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated) + def maybe_restrict_local(q, user) do + limit = Config.get([:instance, :limit_to_local_content], :unauthenticated) case {limit, user} do {:all, _} -> restrict_local(q) @@ -149,7 +159,7 @@ defmodule Pleroma.Activity.Search do defp restrict_local(q), do: where(q, local: true) - defp maybe_fetch(activities, user, search_query) do + def maybe_fetch(activities, user, search_query) do with true <- Regex.match?(~r/https?:/, search_query), {:ok, object} <- Fetcher.fetch_object_from_id(search_query), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex new file mode 100644 index 000000000..2bff663e8 --- /dev/null +++ b/lib/pleroma/search/meilisearch.ex @@ -0,0 +1,181 @@ +defmodule Pleroma.Search.Meilisearch do + require Logger + require Pleroma.Constants + + alias Pleroma.Activity + alias Pleroma.Config.Getting, as: Config + + import Pleroma.Search.DatabaseSearch + import Ecto.Query + + @behaviour Pleroma.Search.SearchBackend + + defp meili_headers do + private_key = Config.get([Pleroma.Search.Meilisearch, :private_key]) + + [{"Content-Type", "application/json"}] ++ + if is_nil(private_key), do: [], else: [{"Authorization", "Bearer #{private_key}"}] + end + + def meili_get(path) do + endpoint = Config.get([Pleroma.Search.Meilisearch, :url]) + + result = + Pleroma.HTTP.get( + Path.join(endpoint, path), + meili_headers() + ) + + with {:ok, res} <- result do + {:ok, Jason.decode!(res.body)} + end + end + + def meili_post(path, params) do + endpoint = Config.get([Pleroma.Search.Meilisearch, :url]) + + result = + Pleroma.HTTP.post( + Path.join(endpoint, path), + Jason.encode!(params), + meili_headers() + ) + + with {:ok, res} <- result do + {:ok, Jason.decode!(res.body)} + end + end + + def meili_put(path, params) do + endpoint = Config.get([Pleroma.Search.Meilisearch, :url]) + + result = + Pleroma.HTTP.request( + :put, + Path.join(endpoint, path), + Jason.encode!(params), + meili_headers(), + [] + ) + + with {:ok, res} <- result do + {:ok, Jason.decode!(res.body)} + end + end + + def meili_delete(path) do + endpoint = Config.get([Pleroma.Search.Meilisearch, :url]) + + with {:ok, _} <- + Pleroma.HTTP.request( + :delete, + Path.join(endpoint, path), + "", + meili_headers(), + [] + ) do + :ok + else + _ -> {:error, "Could not remove from index"} + end + end + + @impl true + def search(user, query, options \\ []) do + limit = Enum.min([Keyword.get(options, :limit), 40]) + offset = Keyword.get(options, :offset, 0) + author = Keyword.get(options, :author) + + res = + meili_post( + "/indexes/objects/search", + %{q: query, offset: offset, limit: limit} + ) + + with {:ok, result} <- res do + hits = result["hits"] |> Enum.map(& &1["ap"]) + + try do + hits + |> Activity.create_by_object_ap_id() + |> Activity.with_preloaded_object() + |> Activity.restrict_deactivated_users() + |> maybe_restrict_local(user) + |> maybe_restrict_author(author) + |> maybe_restrict_blocked(user) + |> maybe_fetch(user, query) + |> order_by([object: obj], desc: obj.data["published"]) + |> Pleroma.Repo.all() + rescue + _ -> maybe_fetch([], user, query) + end + end + end + + def object_to_search_data(object) do + # Only index public or unlisted Notes + if not is_nil(object) and object.data["type"] == "Note" and + not is_nil(object.data["content"]) and + (Pleroma.Constants.as_public() in object.data["to"] or + Pleroma.Constants.as_public() in object.data["cc"]) and + object.data["content"] not in ["", "."] do + data = object.data + + content_str = + case data["content"] do + [nil | rest] -> to_string(rest) + str -> str + end + + content = + with {:ok, scrubbed} <- + FastSanitize.Sanitizer.scrub(content_str, Pleroma.HTML.Scrubber.SearchIndexing), + trimmed <- String.trim(scrubbed) do + trimmed + end + + # Make sure we have a non-empty string + if content != "" do + {:ok, published, _} = DateTime.from_iso8601(data["published"]) + + %{ + id: object.id, + content: content, + ap: data["id"], + published: published |> DateTime.to_unix() + } + end + end + end + + @impl true + def add_to_index(activity) do + maybe_search_data = object_to_search_data(activity.object) + + if activity.data["type"] == "Create" and maybe_search_data do + result = + meili_put( + "/indexes/objects/documents", + [maybe_search_data] + ) + + with {:ok, %{"status" => "enqueued"}} <- result do + # Added successfully + :ok + else + _ -> + # There was an error, report it + Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") + {:error, result} + end + else + # The post isn't something we can search, that's ok + :ok + end + end + + @impl true + def remove_from_index(object) do + meili_delete("/indexes/objects/documents/#{object.id}") + end +end diff --git a/lib/pleroma/search/search_backend.ex b/lib/pleroma/search/search_backend.ex new file mode 100644 index 000000000..a42e2f5f6 --- /dev/null +++ b/lib/pleroma/search/search_backend.ex @@ -0,0 +1,24 @@ +defmodule Pleroma.Search.SearchBackend do + @doc """ + Search statuses with a query, restricting to only those the user should have access to. + """ + @callback search(user :: Pleroma.User.t(), query :: String.t(), options :: [any()]) :: [ + Pleroma.Activity.t() + ] + + @doc """ + Add the object associated with the activity to the search index. + + The whole activity is passed, to allow filtering on things such as scope. + """ + @callback add_to_index(activity :: Pleroma.Activity.t()) :: :ok | {:error, any()} + + @doc """ + Remove the object from the index. + + Just the object, as opposed to the whole activity, is passed, since the object + is what contains the actual content and there is no need for fitlering when removing + from index. + """ + @callback remove_from_index(object :: Pleroma.Object.t()) :: {:ok, any()} | {:error, any()} +end diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex index 384c70fbc..92d395394 100644 --- a/lib/pleroma/telemetry/logger.ex +++ b/lib/pleroma/telemetry/logger.ex @@ -70,7 +70,7 @@ defmodule Pleroma.Telemetry.Logger do %{key: key}, _ ) do - Logger.warn(fn -> + Logger.warning(fn -> "Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{inspect(reason)}" end) end diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 4aee9326f..bedd7889a 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -34,7 +34,6 @@ defmodule Pleroma.Upload do """ alias Ecto.UUID - alias Pleroma.Config alias Pleroma.Maps alias Pleroma.Web.ActivityPub.Utils require Logger @@ -76,6 +75,8 @@ defmodule Pleroma.Upload do :path ] + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) + defp get_description(upload) do case {upload.description, Pleroma.Config.get([Pleroma.Upload, :default_description])} do {description, _} when is_binary(description) -> description @@ -244,18 +245,18 @@ defmodule Pleroma.Upload do defp url_from_spec(_upload, _base_url, {:url, url}), do: url def base_url do - uploader = Config.get([Pleroma.Upload, :uploader]) - upload_base_url = Config.get([Pleroma.Upload, :base_url]) - public_endpoint = Config.get([uploader, :public_endpoint]) + uploader = @config_impl.get([Pleroma.Upload, :uploader]) + upload_base_url = @config_impl.get([Pleroma.Upload, :base_url]) + public_endpoint = @config_impl.get([uploader, :public_endpoint]) case uploader do Pleroma.Uploaders.Local -> upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/" Pleroma.Uploaders.S3 -> - bucket = Config.get([Pleroma.Uploaders.S3, :bucket]) - truncated_namespace = Config.get([Pleroma.Uploaders.S3, :truncated_namespace]) - namespace = Config.get([Pleroma.Uploaders.S3, :bucket_namespace]) + bucket = @config_impl.get([Pleroma.Uploaders.S3, :bucket]) + truncated_namespace = @config_impl.get([Pleroma.Uploaders.S3, :truncated_namespace]) + namespace = @config_impl.get([Pleroma.Uploaders.S3, :bucket_namespace]) bucket_with_namespace = cond do diff --git a/lib/pleroma/upload/filter/analyze_metadata.ex b/lib/pleroma/upload/filter/analyze_metadata.ex index 9a76a998b..7ee643277 100644 --- a/lib/pleroma/upload/filter/analyze_metadata.ex +++ b/lib/pleroma/upload/filter/analyze_metadata.ex @@ -8,27 +8,28 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadata do """ require Logger + alias Vix.Vips.Image + alias Vix.Vips.Operation + @behaviour Pleroma.Upload.Filter @spec filter(Pleroma.Upload.t()) :: {:ok, :filtered, Pleroma.Upload.t()} | {:ok, :noop} | {:error, String.t()} def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload) do try do - image = - file - |> Mogrify.open() - |> Mogrify.verbose() + {:ok, image} = Image.new_from_file(file) + {width, height} = {Image.width(image), Image.height(image)} upload = upload - |> Map.put(:width, image.width) - |> Map.put(:height, image.height) - |> Map.put(:blurhash, get_blurhash(file)) + |> Map.put(:width, width) + |> Map.put(:height, height) + |> Map.put(:blurhash, get_blurhash(image)) {:ok, :filtered, upload} rescue e in ErlangError -> - Logger.warn("#{__MODULE__}: #{inspect(e)}") + Logger.warning("#{__MODULE__}: #{inspect(e)}") {:ok, :noop} end end @@ -45,7 +46,7 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadata do {:ok, :filtered, upload} rescue e in ErlangError -> - Logger.warn("#{__MODULE__}: #{inspect(e)}") + Logger.warning("#{__MODULE__}: #{inspect(e)}") {:ok, :noop} end end @@ -53,7 +54,7 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadata do def filter(_), do: {:ok, :noop} defp get_blurhash(file) do - with {:ok, blurhash} <- :eblurhash.magick(file) do + with {:ok, blurhash} <- vips_blurhash(file) do blurhash else _ -> nil @@ -77,7 +78,28 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadata do %{width: width, height: height} else nil -> {:error, {:ffprobe, :command_not_found}} - {:error, _} = error -> error + error -> {:error, error} + end + end + + defp vips_blurhash(%Vix.Vips.Image{} = image) do + with {:ok, resized_image} <- Operation.thumbnail_image(image, 100), + {height, width} <- {Image.height(resized_image), Image.width(resized_image)}, + max <- max(height, width), + {x, y} <- {max(round(width * 5 / max), 1), max(round(height * 5 / max), 1)} do + {:ok, rgb} = + if Image.has_alpha?(resized_image) do + # remove alpha channel + resized_image + |> Operation.extract_band!(0, n: 3) + |> Image.write_to_binary() + else + Image.write_to_binary(resized_image) + end + + Blurhash.encode(rgb, width, height, x, y) + else + _ -> nil end end end diff --git a/lib/pleroma/upload/filter/exiftool/read_description.ex b/lib/pleroma/upload/filter/exiftool/read_description.ex index 543b22031..8c1ed82f8 100644 --- a/lib/pleroma/upload/filter/exiftool/read_description.ex +++ b/lib/pleroma/upload/filter/exiftool/read_description.ex @@ -10,8 +10,6 @@ defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do """ @behaviour Pleroma.Upload.Filter - @spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()} - def filter(%Pleroma.Upload{description: description}) when is_binary(description), do: {:ok, :noop} diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex index 19287c532..7b32bd8a5 100644 --- a/lib/pleroma/uploaders/s3.ex +++ b/lib/pleroma/uploaders/s3.ex @@ -6,7 +6,8 @@ defmodule Pleroma.Uploaders.S3 do @behaviour Pleroma.Uploaders.Uploader require Logger - alias Pleroma.Config + @ex_aws_impl Application.compile_env(:pleroma, [__MODULE__, :ex_aws_impl], ExAws) + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) # The file name is re-encoded with S3's constraints here to comply with previous # links with less strict filenames @@ -22,7 +23,7 @@ defmodule Pleroma.Uploaders.S3 do @impl true def put_file(%Pleroma.Upload{} = upload) do - config = Config.get([__MODULE__]) + config = @config_impl.get([__MODULE__]) bucket = Keyword.get(config, :bucket) streaming = Keyword.get(config, :streaming_enabled) @@ -56,7 +57,7 @@ defmodule Pleroma.Uploaders.S3 do ]) end - case ExAws.request(op) do + case @ex_aws_impl.request(op) do {:ok, _} -> {:ok, {:file, s3_name}} @@ -69,9 +70,9 @@ defmodule Pleroma.Uploaders.S3 do @impl true def delete_file(file) do [__MODULE__, :bucket] - |> Config.get() + |> @config_impl.get() |> ExAws.S3.delete_object(file) - |> ExAws.request() + |> @ex_aws_impl.request() |> case do {:ok, %{status_code: 204}} -> :ok error -> {:error, inspect(error)} @@ -83,3 +84,7 @@ defmodule Pleroma.Uploaders.S3 do String.replace(name, @regex, "-") end end + +defmodule Pleroma.Uploaders.S3.ExAwsAPI do + @callback request(op :: ExAws.Operation.t()) :: {:ok, ExAws.Operation.t()} | {:error, term()} +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 523b362d6..10dafbe6f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1560,7 +1560,7 @@ defmodule Pleroma.User do unmute(muter, mutee) else {who, result} = error -> - Logger.warn( + Logger.warning( "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}" ) @@ -2136,7 +2136,7 @@ defmodule Pleroma.User do def public_key(_), do: {:error, "key not found"} def get_public_key_for_ap_id(ap_id) do - with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id), + with %User{} = user <- get_cached_by_ap_id(ap_id), {:ok, public_key} <- public_key(user) do {:ok, public_key} else @@ -2681,6 +2681,8 @@ defmodule Pleroma.User do |> update_and_set_cache() end + def update_last_active_at(user), do: user + def active_user_count(days \\ 30) do active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days) diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 447fca2a1..74e0ec073 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -35,6 +35,8 @@ defmodule Pleroma.User.Backup do timestamps() end + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) + def create(user, admin_id \\ nil) do with :ok <- validate_limit(user, admin_id), {:ok, backup} <- user |> new() |> Repo.insert() do @@ -124,7 +126,10 @@ defmodule Pleroma.User.Backup do |> Repo.update() end - def process(%__MODULE__{} = backup) do + def process( + %__MODULE__{} = backup, + processor_module \\ __MODULE__.Processor + ) do set_state(backup, :running, 0) current_pid = self() @@ -132,7 +137,7 @@ defmodule Pleroma.User.Backup do task = Task.Supervisor.async_nolink( Pleroma.TaskSupervisor, - __MODULE__, + processor_module, :do_process, [backup, current_pid] ) @@ -140,25 +145,8 @@ defmodule Pleroma.User.Backup do wait_backup(backup, backup.processed_number, task) end - def do_process(backup, current_pid) do - with {:ok, zip_file} <- export(backup, current_pid), - {:ok, %{size: size}} <- File.stat(zip_file), - {:ok, _upload} <- upload(backup, zip_file) do - backup - |> cast( - %{ - file_size: size, - processed: true, - state: :complete - }, - [:file_size, :processed, :state] - ) - |> Repo.update() - end - end - defp wait_backup(backup, current_processed, task) do - wait_time = Pleroma.Config.get([__MODULE__, :process_wait_time]) + wait_time = @config_impl.get([__MODULE__, :process_wait_time]) receive do {:progress, new_processed} -> @@ -305,7 +293,7 @@ defmodule Pleroma.User.Backup do acc + 1 else {:error, e} -> - Logger.warn( + Logger.warning( "Error processing backup item: #{inspect(e)}\n The item is: #{inspect(i)}" ) @@ -365,3 +353,35 @@ defmodule Pleroma.User.Backup do ) end end + +defmodule Pleroma.User.Backup.ProcessorAPI do + @callback do_process(%Pleroma.User.Backup{}, pid()) :: + {:ok, %Pleroma.User.Backup{}} | {:error, any()} +end + +defmodule Pleroma.User.Backup.Processor do + @behaviour Pleroma.User.Backup.ProcessorAPI + + alias Pleroma.Repo + alias Pleroma.User.Backup + + import Ecto.Changeset + + @impl true + def do_process(backup, current_pid) do + with {:ok, zip_file} <- Backup.export(backup, current_pid), + {:ok, %{size: size}} <- File.stat(zip_file), + {:ok, _upload} <- Backup.upload(backup, zip_file) do + backup + |> cast( + %{ + file_size: size, + processed: true, + state: :complete + }, + [:file_size, :processed, :state] + ) + |> Repo.update() + end + end +end diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index aee41b0fe..7a8b176cd 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -136,7 +136,7 @@ defmodule Pleroma.Web do namespace: Pleroma.Web # Import convenience functions from controllers - import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] + import Phoenix.Controller, only: [get_csrf_token: 0, view_module: 1] import Pleroma.Web.ErrorHelpers import Pleroma.Web.Gettext diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 3979d418e..32d1a1037 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -96,6 +96,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp increase_replies_count_if_reply(_create_data), do: :noop + defp increase_quotes_count_if_quote(%{ + "object" => %{"quoteUrl" => quote_ap_id} = object, + "type" => "Create" + }) do + if is_public?(object) do + Object.increase_quotes_count(quote_ap_id) + end + end + + defp increase_quotes_count_if_quote(_create_data), do: :noop + @object_types ~w[ChatMessage Question Answer Audio Video Image Event Article Note Page] @impl true def persist(%{"type" => type} = object, meta) when type in @object_types do @@ -140,6 +151,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) end) + # Add local posts to search index + if local, do: Pleroma.Search.add_to_index(activity) + {:ok, activity} else %Activity{} = activity -> @@ -299,6 +313,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do with {:ok, activity} <- insert(create_data, local, fake), {:fake, false, activity} <- {:fake, fake, activity}, _ <- increase_replies_count_if_reply(create_data), + _ <- increase_quotes_count_if_quote(create_data), {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity}, {:ok, _actor} <- increase_note_count_if_public(actor, activity), {:ok, _actor} <- update_last_status_at_if_public(actor, activity), @@ -1237,6 +1252,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_unauthenticated(query, _), do: query + defp restrict_quote_url(query, %{quote_url: quote_url}) do + from([_activity, object] in query, + where: fragment("(?)->'quoteUrl' = ?", object.data, ^quote_url) + ) + end + + defp restrict_quote_url(query, _), do: query + defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query defp exclude_poll_votes(query, _) do @@ -1399,6 +1422,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_instance(opts) |> restrict_announce_object_actor(opts) |> restrict_filtered(opts) + |> restrict_quote_url(opts) |> maybe_restrict_deactivated_users(opts) |> exclude_poll_votes(opts) |> exclude_chat_messages(opts) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 1357c379c..e38a94966 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -273,12 +273,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do - with %User{} = recipient <- User.get_cached_by_nickname(nickname), - {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]), + with %User{is_active: true} = recipient <- User.get_cached_by_nickname(nickname), + {:ok, %User{is_active: true} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]), true <- Utils.recipient_in_message(recipient, actor, params), params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do Federator.incoming_ap_doc(params) json(conn, "ok") + else + _ -> + conn + |> put_status(:bad_request) + |> json("Invalid request.") end end @@ -287,10 +292,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do json(conn, "ok") end - def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do - conn - |> put_status(:bad_request) - |> json("Invalid HTTP Signature") + def inbox(%{assigns: %{valid_signature: false}, req_headers: req_headers} = conn, params) do + Federator.incoming_ap_doc(%{req_headers: req_headers, params: params}) + json(conn, "ok") end # POST /relay/inbox -or- POST /internal/fetch/inbox @@ -476,7 +480,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do |> json(message) e -> - Logger.warn(fn -> "AP C2S: #{inspect(e)}" end) + Logger.warning(fn -> "AP C2S: #{inspect(e)}" end) conn |> put_status(:bad_request) diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index ff9f84497..7f6dce925 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -54,6 +54,8 @@ defmodule Pleroma.Web.ActivityPub.MRF do @required_description_keys [:key, :related_policy] def filter_one(policy, message) do + Code.ensure_loaded(policy) + should_plug_history? = if function_exported?(policy, :history_awareness, 0) do policy.history_awareness() @@ -188,6 +190,8 @@ defmodule Pleroma.Web.ActivityPub.MRF do def config_descriptions(policies) do Enum.reduce(policies, @mrf_config_descriptions, fn policy, acc -> + Code.ensure_loaded(policy) + if function_exported?(policy, :config_description, 0) do description = @default_description @@ -199,7 +203,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do if Enum.all?(@required_description_keys, &Map.has_key?(description, &1)) do [description | acc] else - Logger.warn( + Logger.warning( "#{policy} config description doesn't have one or all required keys #{inspect(@required_description_keys)}" ) diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex index 5b6adbb4b..5a4a97626 100644 --- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do try_follow(follower, message) else nil -> - Logger.warn( + Logger.warning( "#{__MODULE__} skipped because of missing `:mrf_follow_bot, :follower_nickname` configuration, the :follower_nickname account does not exist, or the account is not correctly configured as a bot." ) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index f66c379b5..28c2cf3b3 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -41,7 +41,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do shortcode e -> - Logger.warn("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}") + Logger.warning("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}") nil end else @@ -53,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do end else e -> - Logger.warn("MRF.StealEmojiPolicy: Failed to fetch #{url}: #{inspect(e)}") + Logger.warning("MRF.StealEmojiPolicy: Failed to fetch #{url}: #{inspect(e)}") nil end end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index 835ed97b7..1a5d02601 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -57,6 +57,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do field(:replies_count, :integer, default: 0) field(:like_count, :integer, default: 0) field(:announcement_count, :integer, default: 0) + field(:quotes_count, :integer, default: 0) field(:inReplyTo, ObjectValidators.ObjectID) field(:quoteUrl, ObjectValidators.ObjectID) field(:url, ObjectValidators.BareUri) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index af6aa0781..a580994b1 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -118,7 +118,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end end - @spec recipients(User.t(), Activity.t()) :: list(User.t()) | [] + @spec recipients(User.t(), Activity.t()) :: [[User.t()]] defp recipients(actor, activity) do followers = if actor.follower_address in activity.recipients do @@ -138,7 +138,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do [] end - Pleroma.Web.Federator.Publisher.remote_users(actor, activity) ++ followers ++ fetchers + mentioned = Pleroma.Web.Federator.Publisher.remote_users(actor, activity) + non_mentioned = (followers ++ fetchers) -- mentioned + + [mentioned, non_mentioned] end defp get_cc_ap_ids(ap_id, recipients) do @@ -195,34 +198,39 @@ defmodule Pleroma.Web.ActivityPub.Publisher do public = is_public?(activity) {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) - recipients = recipients(actor, activity) + [priority_recipients, recipients] = recipients(actor, activity) inboxes = - recipients - |> Enum.map(fn actor -> actor.inbox end) - |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) - |> Instances.filter_reachable() + [priority_recipients, recipients] + |> Enum.map(fn recipients -> + recipients + |> Enum.map(fn actor -> actor.inbox end) + |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) + |> Instances.filter_reachable() + end) Repo.checkout(fn -> - Enum.each(inboxes, fn {inbox, unreachable_since} -> - %User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end) - - # Get all the recipients on the same host and add them to cc. Otherwise, a remote - # instance would only accept a first message for the first recipient and ignore the rest. - cc = get_cc_ap_ids(ap_id, recipients) - - json = - data - |> Map.put("cc", cc) - |> Jason.encode!() - - Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ - inbox: inbox, - json: json, - actor_id: actor.id, - id: activity.data["id"], - unreachable_since: unreachable_since - }) + Enum.each(inboxes, fn inboxes -> + Enum.each(inboxes, fn {inbox, unreachable_since} -> + %User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end) + + # Get all the recipients on the same host and add them to cc. Otherwise, a remote + # instance would only accept a first message for the first recipient and ignore the rest. + cc = get_cc_ap_ids(ap_id, recipients) + + json = + data + |> Map.put("cc", cc) + |> Jason.encode!() + + Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ + inbox: inbox, + json: json, + actor_id: actor.id, + id: activity.data["id"], + unreachable_since: unreachable_since + }) + end) end) end) end @@ -239,25 +247,36 @@ defmodule Pleroma.Web.ActivityPub.Publisher do {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) json = Jason.encode!(data) - recipients(actor, activity) - |> Enum.map(fn %User{} = user -> - determine_inbox(activity, user) - end) - |> Enum.uniq() - |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) - |> Instances.filter_reachable() - |> Enum.each(fn {inbox, unreachable_since} -> - Pleroma.Web.Federator.Publisher.enqueue_one( - __MODULE__, - %{ - inbox: inbox, - json: json, - actor_id: actor.id, - id: activity.data["id"], - unreachable_since: unreachable_since - } - ) + [priority_inboxes, inboxes] = + recipients(actor, activity) + |> Enum.map(fn recipients -> + recipients + |> Enum.map(fn actor -> actor.inbox end) + |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) + end) + + inboxes = inboxes -- priority_inboxes + + [{priority_inboxes, 0}, {inboxes, 1}] + |> Enum.each(fn {inboxes, priority} -> + inboxes + |> Instances.filter_reachable() + |> Enum.each(fn {inbox, unreachable_since} -> + Pleroma.Web.Federator.Publisher.enqueue_one( + __MODULE__, + %{ + inbox: inbox, + json: json, + actor_id: actor.id, + id: activity.data["id"], + unreachable_since: unreachable_since + }, + priority: priority + ) + end) end) + + :ok end def gather_webfinger_links(%User{} = user) do diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 098c177c7..10f268f05 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -197,6 +197,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do # - Increase replies count # - Set up ActivityExpiration # - Set up notifications + # - Index incoming posts for search (if needed) @impl true def handle(%{data: %{"type" => "Create"}} = activity, meta) do with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta), @@ -209,6 +210,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Object.increase_replies_count(in_reply_to) end + if quote_url = object.data["quoteUrl"] do + Object.increase_quotes_count(quote_url) + end + reply_depth = (meta[:depth] || 0) + 1 # FIXME: Force inReplyTo to replies @@ -226,6 +231,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) end) + Pleroma.Search.add_to_index(Map.put(activity, :object, object)) + meta = meta |> add_notifications(notifications) @@ -285,6 +292,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do # - Reduce the user note count # - Reduce the reply count # - Stream out the activity + # - Removes posts from search index (if needed) @impl true def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do deleted_object = @@ -305,6 +313,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Object.decrease_replies_count(in_reply_to) end + if quote_url = deleted_object.data["quoteUrl"] do + Object.decrease_quotes_count(quote_url) + end + MessageReference.delete_for_object(deleted_object) ap_streamer().stream_out(object) @@ -323,6 +335,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end if result == :ok do + # Only remove from index when deleting actual objects, not users or anything else + with %Pleroma.Object{} <- deleted_object do + Pleroma.Search.remove_from_index(deleted_object) + end + {:ok, object, meta} else {:error, result} diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 86d3ac60f..35f3aea03 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -156,7 +156,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.drop(["conversation", "inReplyToAtomUri"]) else e -> - Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") + Logger.warning("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") object end else @@ -182,7 +182,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do object e -> - Logger.warn("Couldn't fetch #{inspect(quote_url)}, error: #{inspect(e)}") + Logger.warning("Couldn't fetch #{inspect(quote_url)}, error: #{inspect(e)}") object end end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 437220077..b32f19740 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do alias Ecto.UUID alias Pleroma.Activity alias Pleroma.Config + alias Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID alias Pleroma.Maps alias Pleroma.Notification alias Pleroma.Object @@ -852,9 +853,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do [actor | reported_activities] = activity.data["object"] stripped_activities = - Enum.map(reported_activities, fn - act when is_map(act) -> act["id"] - act when is_binary(act) -> act + Enum.reduce(reported_activities, [], fn act, acc -> + case ObjectID.cast(act) do + {:ok, act} -> [act | acc] + _ -> acc + end end) new_data = put_in(activity.data, ["object"], [actor | stripped_activities]) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index f69fca075..24ee683ae 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -46,6 +46,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do "following" => "#{user.ap_id}/following", "followers" => "#{user.ap_id}/followers", "inbox" => "#{user.ap_id}/inbox", + "outbox" => "#{user.ap_id}/outbox", "name" => "Pleroma", "summary" => "An internal service actor for this Pleroma instance. No user-serviceable parts inside.", diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index a07be7e40..8e395bde8 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -23,6 +23,18 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + def show2_operation do + %Operation{ + tags: ["Instance misc"], + summary: "Retrieve instance information", + description: "Information about the server", + operationId: "InstanceController.show2", + responses: %{ + 200 => Operation.response("Instance", "application/json", instance2()) + } + } + end + def peers_operation do %Operation{ tags: ["Instance misc"], @@ -165,6 +177,166 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + defp instance2 do + %Schema{ + type: :object, + properties: %{ + domain: %Schema{type: :string, description: "The domain name of the instance"}, + title: %Schema{type: :string, description: "The title of the website"}, + version: %Schema{ + type: :string, + description: "The version of Pleroma installed on the instance" + }, + source_url: %Schema{ + type: :string, + description: "The version of Pleroma installed on the instance" + }, + description: %Schema{ + type: :string, + description: "Admin-defined description of the Pleroma site" + }, + usage: %Schema{ + type: :object, + description: "Instance usage statistics", + properties: %{ + users: %Schema{ + type: :object, + description: "User count statistics", + properties: %{ + active_month: %Schema{ + type: :integer, + description: "Monthly active users" + } + } + } + } + }, + email: %Schema{ + type: :string, + description: "An email that may be contacted for any inquiries", + format: :email + }, + urls: %Schema{ + type: :object, + description: "URLs of interest for clients apps", + properties: %{} + }, + stats: %Schema{ + type: :object, + description: "Statistics about how much information the instance contains", + properties: %{ + user_count: %Schema{ + type: :integer, + description: "Users registered on this instance" + }, + status_count: %Schema{ + type: :integer, + description: "Statuses authored by users on instance" + }, + domain_count: %Schema{ + type: :integer, + description: "Domains federated with this instance" + } + } + }, + thumbnail: %Schema{ + type: :object, + properties: %{ + url: %Schema{ + type: :string, + description: "Banner image for the website", + nullable: true + } + } + }, + languages: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: "Primary langauges of the website and its staff" + }, + registrations: %Schema{ + type: :object, + description: "Registrations-related configuration", + properties: %{ + enabled: %Schema{ + type: :boolean, + description: "Whether registrations are enabled" + }, + approval_required: %Schema{ + type: :boolean, + description: "Whether users need to be manually approved by admin" + } + } + }, + configuration: %Schema{ + type: :object, + description: "Instance configuration", + properties: %{ + urls: %Schema{ + type: :object, + properties: %{ + streaming: %Schema{ + type: :string, + description: "Websockets address for push streaming" + } + } + }, + statuses: %Schema{ + type: :object, + description: "A map with poll limits for local statuses", + properties: %{ + max_characters: %Schema{ + type: :integer, + description: "Posts character limit (CW/Subject included in the counter)" + }, + max_media_attachments: %Schema{ + type: :integer, + description: "Media attachment limit" + } + } + }, + media_attachments: %Schema{ + type: :object, + description: "A map with poll limits for media attachments", + properties: %{ + image_size_limit: %Schema{ + type: :integer, + description: "File size limit of uploaded images" + }, + video_size_limit: %Schema{ + type: :integer, + description: "File size limit of uploaded videos" + } + } + }, + polls: %Schema{ + type: :object, + description: "A map with poll limits for local polls", + properties: %{ + max_options: %Schema{ + type: :integer, + description: "Maximum number of options." + }, + max_characters_per_option: %Schema{ + type: :integer, + description: "Maximum number of characters per option." + }, + min_expiration: %Schema{ + type: :integer, + description: "Minimum expiration time (in seconds)." + }, + max_expiration: %Schema{ + type: :integer, + description: "Maximum expiration time (in seconds)." + } + } + } + } + } + } + } + end + defp array_of_domains do %Schema{ type: :array, diff --git a/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex index ca40da930..141b60533 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex @@ -59,6 +59,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do album: %Schema{type: :string, description: "The album of the media playing"}, artist: %Schema{type: :string, description: "The artist of the media playing"}, length: %Schema{type: :integer, description: "The length of the media playing"}, + externalLink: %Schema{type: :string, description: "A URL referencing the media playing"}, visibility: %Schema{ allOf: [VisibilityScope], default: "public", @@ -69,7 +70,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do "title" => "Some Title", "artist" => "Some Artist", "album" => "Some Album", - "length" => 180_000 + "length" => 180_000, + "externalLink" => "https://www.last.fm/music/Some+Artist/_/Some+Title" } } end @@ -83,6 +85,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do title: %Schema{type: :string, description: "The title of the media playing"}, album: %Schema{type: :string, description: "The album of the media playing"}, artist: %Schema{type: :string, description: "The artist of the media playing"}, + externalLink: %Schema{type: :string, description: "A URL referencing the media playing"}, length: %Schema{ type: :integer, description: "The length of the media playing", @@ -97,6 +100,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do "artist" => "Some Artist", "album" => "Some Album", "length" => 180_000, + "externalLink" => "https://www.last.fm/music/Some+Artist/_/Some+Title", "created_at" => "2019-09-28T12:40:45.000Z" } } diff --git a/lib/pleroma/web/api_spec/operations/pleroma_status_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_status_operation.ex new file mode 100644 index 000000000..6e69c5269 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_status_operation.ex @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaStatusOperation do + alias OpenApiSpex.Operation + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.StatusOperation + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def quotes_operation do + %Operation{ + tags: ["Retrieve status information"], + summary: "Quoted by", + description: "View quotes for a given status", + operationId: "PleromaAPI.StatusController.quotes", + parameters: [id_param() | pagination_params()], + security: [%{"oAuth" => ["read:statuses"]}], + responses: %{ + 200 => + Operation.response( + "Array of Status", + "application/json", + StatusOperation.array_of_statuses() + ), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + def id_param do + Operation.parameter(:id, :path, FlakeID, "Status ID", + example: "9umDrYheeY451cQnEe", + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 07f03134a..a4052803b 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -213,6 +213,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do type: :boolean, description: "`true` if the quoted post is visible to the user" }, + quotes_count: %Schema{ + type: :integer, + description: "How many statuses quoted this status" + }, local: %Schema{ type: :boolean, description: "`true` if the post was made on the local instance" @@ -367,7 +371,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do "in_reply_to_account_acct" => nil, "local" => true, "spoiler_text" => %{"text/plain" => ""}, - "thread_muted" => false + "thread_muted" => false, + "quotes_count" => 0 }, "poll" => nil, "reblog" => nil, diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 82c7f70d2..dfc0a625d 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -550,7 +550,7 @@ defmodule Pleroma.Web.CommonAPI do remove_mute(user, activity) else {what, result} = error -> - Logger.warn( + Logger.warning( "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}" ) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index ca1329284..8910ad5b8 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -83,7 +83,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp listen_object(draft) do object = draft.params - |> Map.take([:album, :artist, :title, :length]) + |> Map.take([:album, :artist, :title, :length, :externalLink]) |> Map.new(fn {key, value} -> {to_string(key), value} end) |> Map.put("type", "Audio") |> Map.put("to", draft.to) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 0f394e951..dcda3e0e8 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -321,13 +321,13 @@ defmodule Pleroma.Web.CommonAPI.Utils do format_asctime(date) else _e -> - Logger.warn("Date #{date} in wrong format, must be ISO 8601") + Logger.warning("Date #{date} in wrong format, must be ISO 8601") "" end end def date_to_asctime(date) do - Logger.warn("Date #{date} in wrong format, must be ISO 8601") + Logger.warning("Date #{date} in wrong format, must be ISO 8601") "" end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 574f3ab63..307fa069e 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -9,7 +9,20 @@ defmodule Pleroma.Web.Endpoint do alias Pleroma.Config - socket("/socket", Pleroma.Web.UserSocket) + socket("/socket", Pleroma.Web.UserSocket, + websocket: [ + path: "/websocket", + serializer: [ + {Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"}, + {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"} + ], + timeout: 60_000, + transport_log: false, + compress: false + ], + longpoll: false + ) + socket("/live", Phoenix.LiveView.Socket) plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint]) @@ -138,47 +151,6 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Web.Plugs.RemoteIp) - defmodule Instrumenter do - use Prometheus.PhoenixInstrumenter - end - - defmodule PipelineInstrumenter do - use Prometheus.PlugPipelineInstrumenter - end - - defmodule MetricsExporter do - use Prometheus.PlugExporter - end - - defmodule MetricsExporterCaller do - @behaviour Plug - - def init(opts), do: opts - - def call(conn, opts) do - prometheus_config = Application.get_env(:prometheus, MetricsExporter, []) - ip_whitelist = List.wrap(prometheus_config[:ip_whitelist]) - - cond do - !prometheus_config[:enabled] -> - conn - - ip_whitelist != [] and - !Enum.find(ip_whitelist, fn ip -> - Pleroma.Helpers.InetHelper.parse_address(ip) == {:ok, conn.remote_ip} - end) -> - conn - - true -> - MetricsExporter.call(conn, opts) - end - end - end - - plug(PipelineInstrumenter) - - plug(MetricsExporterCaller) - plug(Pleroma.Web.Router) @doc """ diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex index 1a86f7a53..4a0885fab 100644 --- a/lib/pleroma/web/fallback/redirect_controller.ex +++ b/lib/pleroma/web/fallback/redirect_controller.ex @@ -17,10 +17,28 @@ defmodule Pleroma.Web.Fallback.RedirectController do |> json(%{error: "Not implemented"}) end + def add_generated_metadata(page_content, extra \\ "") do + title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>" + favicon = "<link rel='icon' href='#{Pleroma.Config.get([:instance, :favicon])}'>" + manifest = "<link rel='manifest' href='/manifest.json'>" + + page_content + |> String.replace( + "<!--server-generated-meta-->", + title <> favicon <> manifest <> extra + ) + end + def redirector(conn, _params, code \\ 200) do + {:ok, index_content} = File.read(index_file_path()) + + response = + index_content + |> add_generated_metadata() + conn |> put_resp_content_type("text/html") - |> send_file(code, index_file_path()) + |> send_resp(code, response) end def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do @@ -34,14 +52,12 @@ defmodule Pleroma.Web.Fallback.RedirectController do def redirector_with_meta(conn, params) do {:ok, index_content} = File.read(index_file_path()) - tags = build_tags(conn, params) preloads = preload_data(conn, params) - title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>" response = index_content - |> String.replace("<!--server-generated-meta-->", tags <> preloads <> title) + |> add_generated_metadata(tags <> preloads) conn |> put_resp_content_type("text/html") @@ -55,11 +71,10 @@ defmodule Pleroma.Web.Fallback.RedirectController do def redirector_with_preload(conn, params) do {:ok, index_content} = File.read(index_file_path()) preloads = preload_data(conn, params) - title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>" response = index_content - |> String.replace("<!--server-generated-meta-->", preloads <> title) + |> add_generated_metadata(preloads) conn |> put_resp_content_type("text/html") diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index 84b77cda1..8621d984c 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -35,6 +35,17 @@ defmodule Pleroma.Web.Federator do end # Client API + def incoming_ap_doc(%{params: params, req_headers: req_headers}) do + ReceiverWorker.enqueue( + "incoming_ap_doc", + %{"req_headers" => req_headers, "params" => params, "timeout" => :timer.seconds(20)}, + priority: 2 + ) + end + + def incoming_ap_doc(%{"type" => "Delete"} = params) do + ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}, priority: 3) + end def incoming_ap_doc(params) do ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}) diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex index a45796e9d..8c6547208 100644 --- a/lib/pleroma/web/federator/publisher.ex +++ b/lib/pleroma/web/federator/publisher.ex @@ -29,11 +29,12 @@ defmodule Pleroma.Web.Federator.Publisher do @doc """ Enqueue publishing a single activity. """ - @spec enqueue_one(module(), Map.t()) :: :ok - def enqueue_one(module, %{} = params) do + @spec enqueue_one(module(), Map.t(), Keyword.t()) :: {:ok, %Oban.Job{}} + def enqueue_one(module, %{} = params, worker_args \\ []) do PublisherWorker.enqueue( "publish_one", - %{"module" => to_string(module), "params" => params} + %{"module" => to_string(module), "params" => params}, + worker_args ) end diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 6410e872c..3e664903a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_auth when action in [:show, :peers]) + plug(:skip_auth when action in [:show, :show2, :peers]) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation @@ -16,6 +16,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do render(conn, "show.json") end + @doc "GET /api/v2/instance" + def show2(conn, _params) do + render(conn, "show2.json") + end + @doc "GET /api/v1/instance/peers" def peers(conn, _params) do json(conn, Pleroma.Stats.get_peers()) diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 5e6e04734..e4acba226 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do use Pleroma.Web, :controller - alias Pleroma.Activity alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ControllerHelper @@ -100,7 +99,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do end defp resource_search(_, "statuses", query, options) do - statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end) + statuses = with_fallback(fn -> Pleroma.Search.search(query, options) end) StatusView.render("index.json", activities: statuses, diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index cc3e3582f..237de3055 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.AccountView do @@ -249,6 +249,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do nil end + last_status_at = + user.last_status_at && + user.last_status_at |> NaiveDateTime.to_date() |> Date.to_iso8601() + %{ id: to_string(user.id), username: username_from_nickname(user.nickname), @@ -277,7 +281,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do actor_type: user.actor_type } }, - last_status_at: user.last_status_at, + last_status_at: last_status_at, # Pleroma extensions # Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index ba7836e51..f95b5360a 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -13,12 +13,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do def render("show.json", _) do instance = Config.get(:instance) - %{ + common_information(instance) + |> Map.merge(%{ uri: Pleroma.Web.WebFinger.host(), - title: Keyword.get(instance, :name), description: Keyword.get(instance, :description), short_description: Keyword.get(instance, :short_description), - version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", email: Keyword.get(instance, :email), urls: %{ streaming_api: Pleroma.Web.Endpoint.websocket_url() @@ -27,9 +26,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do thumbnail: URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail)) |> to_string, - languages: Keyword.get(instance, :languages, ["en"]), registrations: Keyword.get(instance, :registrations_open), approval_required: Keyword.get(instance, :account_approval_required), + configuration: configuration(), # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit), max_media_attachments: Keyword.get(instance, :max_media_attachments), @@ -41,19 +40,44 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), shout_limit: Config.get([:shout, :limit]), description_limit: Keyword.get(instance, :description_limit), - pleroma: %{ - metadata: %{ - account_activation_required: Keyword.get(instance, :account_activation_required), - features: features(), - federation: federation(), - fields_limits: fields_limits(), - post_formats: Config.get([:instance, :allowed_post_formats]), - birthday_required: Config.get([:instance, :birthday_required]), - birthday_min_age: Config.get([:instance, :birthday_min_age]) - }, - stats: %{mau: Pleroma.User.active_user_count()}, - vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) - } + pleroma: pleroma_configuration(instance) + }) + end + + def render("show2.json", _) do + instance = Config.get(:instance) + + common_information(instance) + |> Map.merge(%{ + domain: Pleroma.Web.WebFinger.host(), + source_url: Pleroma.Application.repository(), + description: Keyword.get(instance, :short_description), + usage: %{users: %{active_month: Pleroma.User.active_user_count()}}, + thumbnail: %{ + url: + URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail)) + |> to_string + }, + configuration: configuration2(), + registrations: %{ + enabled: Keyword.get(instance, :registrations_open), + approval_required: Keyword.get(instance, :account_approval_required), + message: nil + }, + contact: %{ + email: Keyword.get(instance, :email), + account: nil + }, + # Extra (not present in Mastodon): + pleroma: pleroma_configuration2(instance) + }) + end + + defp common_information(instance) do + %{ + title: Keyword.get(instance, :name), + version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", + languages: Keyword.get(instance, :languages, ["en"]) } end @@ -133,7 +157,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do |> Map.put(:enabled, Config.get([:instance, :federating])) end - def fields_limits do + defp fields_limits do %{ max_fields: Config.get([:instance, :max_account_fields]), max_remote_fields: Config.get([:instance, :max_remote_account_fields]), @@ -141,4 +165,65 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do value_length: Config.get([:instance, :account_field_value_length]) } end + + defp configuration do + %{ + statuses: %{ + max_characters: Config.get([:instance, :limit]), + max_media_attachments: Config.get([:instance, :max_media_attachments]) + }, + media_attachments: %{ + image_size_limit: Config.get([:instance, :upload_limit]), + video_size_limit: Config.get([:instance, :upload_limit]) + }, + polls: %{ + max_options: Config.get([:instance, :poll_limits, :max_options]), + max_characters_per_option: Config.get([:instance, :poll_limits, :max_option_chars]), + min_expiration: Config.get([:instance, :poll_limits, :min_expiration]), + max_expiration: Config.get([:instance, :poll_limits, :max_expiration]) + } + } + end + + defp configuration2 do + configuration() + |> Map.merge(%{ + urls: %{streaming: Pleroma.Web.Endpoint.websocket_url()} + }) + end + + defp pleroma_configuration(instance) do + %{ + metadata: %{ + account_activation_required: Keyword.get(instance, :account_activation_required), + features: features(), + federation: federation(), + fields_limits: fields_limits(), + post_formats: Config.get([:instance, :allowed_post_formats]), + birthday_required: Config.get([:instance, :birthday_required]), + birthday_min_age: Config.get([:instance, :birthday_min_age]) + }, + stats: %{mau: Pleroma.User.active_user_count()}, + vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + } + end + + defp pleroma_configuration2(instance) do + configuration = pleroma_configuration(instance) + + configuration + |> Map.merge(%{ + metadata: + configuration.metadata + |> Map.merge(%{ + avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit), + background_upload_limit: Keyword.get(instance, :background_upload_limit), + banner_upload_limit: Keyword.get(instance, :banner_upload_limit), + background_image: + Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), + description_limit: Keyword.get(instance, :description_limit), + shout_limit: Config.get([:shout, :limit]) + }) + }) + end end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index d070262cc..0e2e604f5 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -447,7 +447,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do thread_muted: thread_muted?, emoji_reactions: emoji_reactions, parent_visible: visible_for_user?(reply_to, opts[:for]), - pinned_at: pinned_at + pinned_at: pinned_at, + quotes_count: object.data["quotesCount"] || 0 } } end @@ -562,25 +563,24 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do page_url = page_url_data |> to_string - image_url_data = - if is_binary(rich_media["image"]) do - URI.parse(rich_media["image"]) - else - nil - end - - image_url = build_image_url(image_url_data, page_url_data) + image_url = proxied_url(rich_media["image"], page_url_data) + audio_url = proxied_url(rich_media["audio"], page_url_data) + video_url = proxied_url(rich_media["video"], page_url_data) %{ type: "link", provider_name: page_url_data.host, provider_url: page_url_data.scheme <> "://" <> page_url_data.host, url: page_url, - image: image_url |> MediaProxy.url(), + image: image_url, title: rich_media["title"] || "", description: rich_media["description"] || "", pleroma: %{ - opengraph: rich_media + opengraph: + rich_media + |> Maps.put_if_present("image", image_url) + |> Maps.put_if_present("audio", audio_url) + |> Maps.put_if_present("video", video_url) } } end @@ -817,4 +817,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do defp get_source_content_type(_source) do Utils.get_content_type(nil) end + + defp proxied_url(url, page_url_data) do + if is_binary(url) do + build_image_url(URI.parse(url), page_url_data) |> MediaProxy.url() + else + nil + end + end end diff --git a/lib/pleroma/web/pleroma_api/controllers/status_controller.ex b/lib/pleroma/web/pleroma_api/controllers/status_controller.ex new file mode 100644 index 000000000..482662fdd --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/status_controller.ex @@ -0,0 +1,66 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.StatusController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + require Ecto.Query + require Pleroma.Constants + + alias Pleroma.Activity + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + plug( + OAuthScopesPlug, + %{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} when action == :quotes + ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaStatusOperation + + @doc "GET /api/v1/pleroma/statuses/:id/quotes" + def quotes(%{assigns: %{user: user}} = conn, %{id: id} = params) do + with %Activity{object: object} = activity <- Activity.get_by_id_with_object(id), + true <- Visibility.visible_for_user?(activity, user) do + params = + params + |> Map.put(:type, "Create") + |> Map.put(:blocking_user, user) + |> Map.put(:quote_url, object.data["id"]) + + recipients = + if user do + [Pleroma.Constants.as_public()] ++ [user.ap_id | User.following(user)] + else + [Pleroma.Constants.as_public()] + end + + activities = + recipients + |> ActivityPub.fetch_activities(params) + |> Enum.reverse() + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("index.json", + activities: activities, + for: user, + as: :activity + ) + else + nil -> {:error, :not_found} + false -> {:error, :not_found} + end + end +end diff --git a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex index a5985fb2a..edf0a2390 100644 --- a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex +++ b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex @@ -27,6 +27,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do title: object.data["title"] |> HTML.strip_tags(), artist: object.data["artist"] |> HTML.strip_tags(), album: object.data["album"] |> HTML.strip_tags(), + externalLink: object.data["externalLink"], length: object.data["length"] } end diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index 5093414c4..a27dcd0ab 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -201,7 +201,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do def warn_if_disabled do unless Config.get([:http_security, :enabled]) do - Logger.warn(" + Logger.warning(" .i;;;;i. iYcviii;vXY: .YXi .i1c. diff --git a/lib/pleroma/web/plugs/rate_limiter.ex b/lib/pleroma/web/plugs/rate_limiter.ex index 2080b06bd..aa79dbf6b 100644 --- a/lib/pleroma/web/plugs/rate_limiter.ex +++ b/lib/pleroma/web/plugs/rate_limiter.ex @@ -89,7 +89,7 @@ defmodule Pleroma.Web.Plugs.RateLimiter do end defp handle_disabled(conn) do - Logger.warn( + Logger.warning( "Rate limiter disabled due to forwarded IP not being found. Please ensure your reverse proxy is providing the X-Forwarded-For header or disable the RemoteIP plug/rate limiter." ) diff --git a/lib/pleroma/web/push.ex b/lib/pleroma/web/push.ex index 9665b0b4a..0d43f402e 100644 --- a/lib/pleroma/web/push.ex +++ b/lib/pleroma/web/push.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.Push do def init do unless enabled() do - Logger.warn(""" + Logger.warning(""" VAPID key pair is not found. If you wish to enabled web push, please run mix web_push.gen.keypair diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 3c5f00764..36f44d8e8 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -57,7 +57,7 @@ defmodule Pleroma.Web.Push.Impl do end def perform(_) do - Logger.warn("Unknown notification type") + Logger.warning("Unknown notification type") {:error, :unknown_type} end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 0488df30e..61000bb9b 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -4,11 +4,12 @@ defmodule Pleroma.Web.RichMedia.Helpers do alias Pleroma.Activity - alias Pleroma.Config alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Web.RichMedia.Parser + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) + @options [ pool: :media, max_body: 2_000_000, @@ -17,7 +18,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do @spec validate_page_url(URI.t() | binary()) :: :ok | :error defp validate_page_url(page_url) when is_binary(page_url) do - validate_tld = Config.get([Pleroma.Formatter, :validate_tld]) + validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld]) page_url |> Linkify.Parser.url?(validate_tld: validate_tld) @@ -27,10 +28,10 @@ defmodule Pleroma.Web.RichMedia.Helpers do defp validate_page_url(%URI{host: host, scheme: "https", authority: authority}) when is_binary(authority) do cond do - host in Config.get([:rich_media, :ignore_hosts], []) -> + host in @config_impl.get([:rich_media, :ignore_hosts], []) -> :error - get_tld(host) in Config.get([:rich_media, :ignore_tld], []) -> + get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) -> :error true -> @@ -56,7 +57,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do end def fetch_data_for_object(object) do - with true <- Config.get([:rich_media, :enabled]), + with true <- @config_impl.get([:rich_media, :enabled]), {:ok, page_url} <- HTML.extract_first_external_url_from_object(object), :ok <- validate_page_url(page_url), @@ -68,7 +69,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do end def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do - with true <- Config.get([:rich_media, :enabled]), + with true <- @config_impl.get([:rich_media, :enabled]), %Object{} = object <- Object.normalize(activity, fetch: false) do fetch_data_for_object(object) else diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index dbe81eabb..c37c45963 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -75,7 +75,7 @@ defmodule Pleroma.Web.RichMedia.Parser do end defp log_error(url, reason) do - Logger.warn(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) + Logger.warning(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6b9e158a3..22c85e34b 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -224,6 +224,12 @@ defmodule Pleroma.Web.Router do post("/remote_interaction", UtilController, :remote_interaction) end + scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do + pipe_through(:pleroma_api) + + get("/federation_status", InstancesController, :show) + end + scope "/api/v1/pleroma", Pleroma.Web do pipe_through(:pleroma_api) post("/uploader_callback/:upload_path", UploaderController, :callback) @@ -465,6 +471,8 @@ defmodule Pleroma.Web.Router do get("/main/ostatus", UtilController, :show_subscribe_form) get("/ostatus_subscribe", RemoteFollowController, :follow) post("/ostatus_subscribe", RemoteFollowController, :do_follow) + + get("/authorize_interaction", RemoteFollowController, :authorize_interaction) end scope "/api/pleroma", Pleroma.Web.TwitterAPI do @@ -578,6 +586,8 @@ defmodule Pleroma.Web.Router do pipe_through(:api) get("/accounts/:id/favourites", AccountController, :favourites) get("/accounts/:id/endorsements", AccountController, :endorsements) + + get("/statuses/:id/quotes", StatusController, :quotes) end scope [] do @@ -602,7 +612,6 @@ defmodule Pleroma.Web.Router do scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:api) get("/accounts/:id/scrobbles", ScrobbleController, :index) - get("/federation_status", InstancesController, :show) end scope "/api/v2/pleroma", Pleroma.Web.PleromaAPI do @@ -774,11 +783,14 @@ defmodule Pleroma.Web.Router do scope "/api/v2", Pleroma.Web.MastodonAPI do pipe_through(:api) + get("/search", SearchController, :search2) post("/media", MediaController, :create2) get("/suggestions", SuggestionController, :index2) + + get("/instance", InstanceController, :show2) end scope "/api", Pleroma.Web do @@ -1003,9 +1015,8 @@ defmodule Pleroma.Web.Router do options("/*path", RedirectController, :empty) end - # TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+ def get_api_routes do - __MODULE__.__routes__() + Phoenix.Router.routes(__MODULE__) |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end) |> Enum.map(fn r -> r.path diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex index e45d13bdf..e3639aae7 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex @@ -1,8 +1,8 @@ -<%= if get_flash(@conn, :info) do %> -<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> +<%= if Phoenix.Flash.get(@flash, :info) do %> +<p class="alert alert-info" role="alert"><%= Phoenix.Flash.get(@flash, :info) %></p> <% end %> -<%= if get_flash(@conn, :error) do %> -<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> +<%= if Phoenix.Flash.get(@flash, :error) do %> +<p class="alert alert-danger" role="alert"><%= Phoenix.Flash.get(@flash, :error) %></p> <% end %> <h2><%= Gettext.dpgettext("static_pages", "mfa recover page title", "Two-factor recovery") %></h2> diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex index 50e6c04b6..f995b8805 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex @@ -1,8 +1,8 @@ -<%= if get_flash(@conn, :info) do %> -<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> +<%= if Phoenix.Flash.get(@flash, :info) do %> +<p class="alert alert-info" role="alert"><%= Phoenix.Flash.get(@flash, :info) %></p> <% end %> -<%= if get_flash(@conn, :error) do %> -<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> +<%= if Phoenix.Flash.get(@flash, :error) do %> +<p class="alert alert-danger" role="alert"><%= Phoenix.Flash.get(@flash, :error) %></p> <% end %> <h2><%= Gettext.dpgettext("static_pages", "mfa auth page title", "Two-factor authentication") %></h2> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex index 1f661efb2..e7f65266f 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex @@ -1,8 +1,8 @@ -<%= if get_flash(@conn, :info) do %> - <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> +<%= if Phoenix.Flash.get(@flash, :info) do %> + <p class="alert alert-info" role="alert"><%= Phoenix.Flash.get(@flash, :info) %></p> <% end %> -<%= if get_flash(@conn, :error) do %> - <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> +<%= if Phoenix.Flash.get(@flash, :error) do %> + <p class="alert alert-danger" role="alert"><%= Phoenix.Flash.get(@flash, :error) %></p> <% end %> <h2><%= Gettext.dpgettext("static_pages", "oauth register page title", "Registration Details") %></h2> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index b3654f3eb..5b38f7142 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -1,8 +1,8 @@ -<%= if get_flash(@conn, :info) do %> -<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> +<%= if Phoenix.Flash.get(@flash, :info) do %> +<p class="alert alert-info" role="alert"><%= Phoenix.Flash.get(@flash, :info) %></p> <% end %> -<%= if get_flash(@conn, :error) do %> -<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> +<%= if Phoenix.Flash.get(@flash, :error) do %> +<p class="alert alert-danger" role="alert"><%= Phoenix.Flash.get(@flash, :error) %></p> <% end %> <%= form_for @conn, Routes.o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index 6229d5d05..178ad2b43 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -121,6 +121,13 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do render(conn, "followed.html", %{error: "Insufficient permissions: follow | write:follows."}) end + # GET /authorize_interaction + # + def authorize_interaction(conn, %{"uri" => uri}) do + conn + |> redirect(to: Routes.remote_follow_path(conn, :follow, %{acct: uri})) + end + defp handle_follow_error(conn, {:mfa_token, followee, _} = _) do render(conn, "follow_login.html", %{error: "Wrong username or password", followee: followee}) end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index d5a24ae6c..ca8a98960 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -345,13 +345,16 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end def healthcheck(conn, _params) do - with true <- Config.get([:instance, :healthcheck]), + with {:cfg, true} <- {:cfg, Config.get([:instance, :healthcheck])}, %{healthy: true} = info <- Healthcheck.system_info() do json(conn, info) else %{healthy: false} = info -> service_unavailable(conn, info) + {:cfg, false} -> + service_unavailable(conn, %{"error" => "Healthcheck disabled"}) + _ -> service_unavailable(conn, %{}) end diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index c6840f10b..0684a770c 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -163,7 +163,7 @@ defmodule Pleroma.Web.WebFinger do get_template_from_xml(body) else error -> - Logger.warn("Can't find LRDD template in #{inspect(meta_url)}: #{inspect(error)}") + Logger.warning("Can't find LRDD template in #{inspect(meta_url)}: #{inspect(error)}") {:error, :lrdd_not_found} end end diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex index 1540c1605..0292bbb3b 100644 --- a/lib/pleroma/workers/cron/digest_emails_worker.ex +++ b/lib/pleroma/workers/cron/digest_emails_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do The worker to send digest emails. """ - use Oban.Worker, queue: "digest_emails" + use Oban.Worker, queue: "mailer" alias Pleroma.Config alias Pleroma.Emails diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex index 267fe2837..1c3e445aa 100644 --- a/lib/pleroma/workers/cron/new_users_digest_worker.ex +++ b/lib/pleroma/workers/cron/new_users_digest_worker.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do import Ecto.Query - use Pleroma.Workers.WorkerHelper, queue: "new_users_digest" + use Pleroma.Workers.WorkerHelper, queue: "mailer" @impl Oban.Worker def perform(_job) do diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index cf1bb62b4..1dddd8d2e 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -3,24 +3,56 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.ReceiverWorker do + alias Pleroma.Signature + alias Pleroma.User alias Pleroma.Web.Federator use Pleroma.Workers.WorkerHelper, queue: "federator_incoming" @impl Oban.Worker + + def perform(%Job{ + args: %{"op" => "incoming_ap_doc", "req_headers" => req_headers, "params" => params} + }) do + # Oban's serialization converts our tuple headers to lists. + # Revert it for the signature validation. + req_headers = Enum.into(req_headers, [], &List.to_tuple(&1)) + + conn_data = %{params: params, req_headers: req_headers} + + with {:ok, %User{} = _actor} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]), + {:ok, _public_key} <- Signature.refetch_public_key(conn_data), + {:signature, true} <- {:signature, HTTPSignatures.validate_conn(conn_data)}, + {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do + {:ok, res} + else + e -> process_errors(e) + end + end + def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do {:ok, res} else + e -> process_errors(e) + end + end + + @impl Oban.Worker + def timeout(%_{args: %{"timeout" => timeout}}), do: timeout + + def timeout(_job), do: :timer.seconds(5) + + defp process_errors(errors) do + case errors do {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} {:error, :already_present} -> {:cancel, :already_present} {:error, {:validate_object, reason}} -> {:cancel, reason} {:error, {:error, {:validate, reason}}} -> {:cancel, reason} {:error, {:reject, reason}} -> {:cancel, reason} + {:signature, false} -> {:cancel, :invalid_signature} + {:error, {:error, reason = "Object has been deleted"}} -> {:cancel, reason} e -> e end end - - @impl Oban.Worker - def timeout(_job), do: :timer.seconds(5) end diff --git a/lib/pleroma/workers/search_indexing_worker.ex b/lib/pleroma/workers/search_indexing_worker.ex new file mode 100644 index 000000000..8476a2be5 --- /dev/null +++ b/lib/pleroma/workers/search_indexing_worker.ex @@ -0,0 +1,23 @@ +defmodule Pleroma.Workers.SearchIndexingWorker do + use Pleroma.Workers.WorkerHelper, queue: "search_indexing" + + @impl Oban.Worker + + alias Pleroma.Config.Getting, as: Config + + def perform(%Job{args: %{"op" => "add_to_index", "activity" => activity_id}}) do + activity = Pleroma.Activity.get_by_id_with_object(activity_id) + + search_module = Config.get([Pleroma.Search, :module]) + + search_module.add_to_index(activity) + end + + def perform(%Job{args: %{"op" => "remove_from_index", "object" => object_id}}) do + object = Pleroma.Object.get_by_id(object_id) + + search_module = Config.get([Pleroma.Search, :module]) + + search_module.remove_from_index(object) + end +end |