From 23d279e03ee1f7a1285614754738711359bc4b81 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 1 Aug 2019 17:28:00 +0300 Subject: [#1149] Replaced RetryQueue with oban-based retries. --- lib/pleroma/application.ex | 4 +- lib/pleroma/web/activity_pub/publisher.ex | 16 +- lib/pleroma/web/federator/federator.ex | 14 -- lib/pleroma/web/federator/publisher.ex | 22 +-- lib/pleroma/web/federator/retry_queue.ex | 239 ------------------------------ lib/pleroma/web/salmon/salmon.ex | 11 +- lib/pleroma/workers/publisher.ex | 14 ++ 7 files changed, 43 insertions(+), 277 deletions(-) delete mode 100644 lib/pleroma/web/federator/retry_queue.ex create mode 100644 lib/pleroma/workers/publisher.ex (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 035331491..ce7d8c4b2 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -120,8 +120,8 @@ defmodule Pleroma.Application do hackney_pool_children() ++ [ %{ - id: Pleroma.Web.Federator.RetryQueue, - start: {Pleroma.Web.Federator.RetryQueue, :start_link, []} + id: Oban, + start: {Oban, :start_link, [Application.get_env(:pleroma, Oban)]} }, %{ id: Pleroma.Web.OAuth.Token.CleanWorker, diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 46edab0bd..29f3221d1 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -85,6 +85,15 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end end + def publish_one(%{actor_id: actor_id} = params) do + actor = User.get_by_id(actor_id) + + params + |> Map.delete(:actor_id) + |> Map.put(:actor, actor) + |> publish_one() + end + defp should_federate?(inbox, public) do if public do true @@ -160,7 +169,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do Publishes an activity with BCC to all relevant peers. """ - def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do + def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity) + when is_list(bcc) and bcc != [] do public = is_public?(activity) {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) @@ -187,7 +197,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ inbox: inbox, json: json, - actor: actor, + actor_id: actor.id, id: activity.data["id"], unreachable_since: unreachable_since }) @@ -222,7 +232,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do %{ inbox: inbox, json: json, - actor: actor, + actor_id: actor.id, id: activity.data["id"], unreachable_since: unreachable_since } diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index f4f9e83e0..97ec9d549 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Federator.Publisher - alias Pleroma.Web.Federator.RetryQueue alias Pleroma.Web.OStatus alias Pleroma.Web.Websub @@ -130,19 +129,6 @@ defmodule Pleroma.Web.Federator do end end - def perform( - :publish_single_websub, - %{xml: _xml, topic: _topic, callback: _callback, secret: _secret} = params - ) do - case Websub.publish_one(params) do - {:ok, _} -> - :ok - - {:error, _} -> - RetryQueue.enqueue(params, Websub) - end - end - def perform(type, _) do Logger.debug(fn -> "Unknown task: #{type}" end) {:error, "Don't know what to do with this"} diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex index 70f870244..e8c1bf17f 100644 --- a/lib/pleroma/web/federator/publisher.ex +++ b/lib/pleroma/web/federator/publisher.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.Federator.Publisher do alias Pleroma.Activity alias Pleroma.Config alias Pleroma.User - alias Pleroma.Web.Federator.RetryQueue require Logger @@ -30,23 +29,10 @@ defmodule Pleroma.Web.Federator.Publisher do Enqueue publishing a single activity. """ @spec enqueue_one(module(), Map.t()) :: :ok - def enqueue_one(module, %{} = params), - do: PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_one, module, params]) - - @spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()} - def perform(:publish_one, module, params) do - case apply(module, :publish_one, [params]) do - {:ok, _} -> - :ok - - {:error, _e} -> - RetryQueue.enqueue(params, module) - end - end - - def perform(type, _, _) do - Logger.debug("Unknown task: #{type}") - {:error, "Don't know what to do with this"} + def enqueue_one(module, %{} = params) do + %{module: to_string(module), params: params} + |> Pleroma.Workers.Publisher.new() + |> Pleroma.Repo.insert() end @doc """ diff --git a/lib/pleroma/web/federator/retry_queue.ex b/lib/pleroma/web/federator/retry_queue.ex deleted file mode 100644 index 3db948c2e..000000000 --- a/lib/pleroma/web/federator/retry_queue.ex +++ /dev/null @@ -1,239 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Federator.RetryQueue do - use GenServer - - require Logger - - def init(args) do - queue_table = :ets.new(:pleroma_retry_queue, [:bag, :protected]) - - {:ok, %{args | queue_table: queue_table, running_jobs: :sets.new()}} - end - - def start_link do - enabled = - if Pleroma.Config.get(:env) == :test, - do: true, - else: Pleroma.Config.get([__MODULE__, :enabled], false) - - if enabled do - Logger.info("Starting retry queue") - - linkres = - GenServer.start_link( - __MODULE__, - %{delivered: 0, dropped: 0, queue_table: nil, running_jobs: nil}, - name: __MODULE__ - ) - - maybe_kickoff_timer() - linkres - else - Logger.info("Retry queue disabled") - :ignore - end - end - - def enqueue(data, transport, retries \\ 0) do - GenServer.cast(__MODULE__, {:maybe_enqueue, data, transport, retries + 1}) - end - - def get_stats do - GenServer.call(__MODULE__, :get_stats) - end - - def reset_stats do - GenServer.call(__MODULE__, :reset_stats) - end - - def get_retry_params(retries) do - if retries > Pleroma.Config.get([__MODULE__, :max_retries]) do - {:drop, "Max retries reached"} - else - {:retry, growth_function(retries)} - end - end - - def get_retry_timer_interval do - Pleroma.Config.get([:retry_queue, :interval], 1000) - end - - defp ets_count_expires(table, current_time) do - :ets.select_count( - table, - [ - { - {:"$1", :"$2"}, - [{:"=<", :"$1", {:const, current_time}}], - [true] - } - ] - ) - end - - defp ets_pop_n_expired(table, current_time, desired) do - {popped, _continuation} = - :ets.select( - table, - [ - { - {:"$1", :"$2"}, - [{:"=<", :"$1", {:const, current_time}}], - [:"$_"] - } - ], - desired - ) - - popped - |> Enum.each(fn e -> - :ets.delete_object(table, e) - end) - - popped - end - - def maybe_start_job(running_jobs, queue_table) do - # we don't want to hit the ets or the DateTime more times than we have to - # could optimize slightly further by not using the count, and instead grabbing - # up to N objects early... - current_time = DateTime.to_unix(DateTime.utc_now()) - n_running_jobs = :sets.size(running_jobs) - - if n_running_jobs < Pleroma.Config.get([__MODULE__, :max_jobs]) do - n_ready_jobs = ets_count_expires(queue_table, current_time) - - if n_ready_jobs > 0 do - # figure out how many we could start - available_job_slots = Pleroma.Config.get([__MODULE__, :max_jobs]) - n_running_jobs - start_n_jobs(running_jobs, queue_table, current_time, available_job_slots) - else - running_jobs - end - else - running_jobs - end - end - - defp start_n_jobs(running_jobs, _queue_table, _current_time, 0) do - running_jobs - end - - defp start_n_jobs(running_jobs, queue_table, current_time, available_job_slots) - when available_job_slots > 0 do - candidates = ets_pop_n_expired(queue_table, current_time, available_job_slots) - - candidates - |> List.foldl(running_jobs, fn {_, e}, rj -> - {:ok, pid} = Task.start(fn -> worker(e) end) - mref = Process.monitor(pid) - :sets.add_element(mref, rj) - end) - end - - def worker({:send, data, transport, retries}) do - case transport.publish_one(data) do - {:ok, _} -> - GenServer.cast(__MODULE__, :inc_delivered) - :delivered - - {:error, _reason} -> - enqueue(data, transport, retries) - :retry - end - end - - def handle_call(:get_stats, _from, %{delivered: delivery_count, dropped: drop_count} = state) do - {:reply, %{delivered: delivery_count, dropped: drop_count}, state} - end - - def handle_call(:reset_stats, _from, %{delivered: delivery_count, dropped: drop_count} = state) do - {:reply, %{delivered: delivery_count, dropped: drop_count}, - %{state | delivered: 0, dropped: 0}} - end - - def handle_cast(:reset_stats, state) do - {:noreply, %{state | delivered: 0, dropped: 0}} - end - - def handle_cast( - {:maybe_enqueue, data, transport, retries}, - %{dropped: drop_count, queue_table: queue_table, running_jobs: running_jobs} = state - ) do - case get_retry_params(retries) do - {:retry, timeout} -> - :ets.insert(queue_table, {timeout, {:send, data, transport, retries}}) - running_jobs = maybe_start_job(running_jobs, queue_table) - {:noreply, %{state | running_jobs: running_jobs}} - - {:drop, message} -> - Logger.debug(message) - {:noreply, %{state | dropped: drop_count + 1}} - end - end - - def handle_cast(:kickoff_timer, state) do - retry_interval = get_retry_timer_interval() - Process.send_after(__MODULE__, :retry_timer_run, retry_interval) - {:noreply, state} - end - - def handle_cast(:inc_delivered, %{delivered: delivery_count} = state) do - {:noreply, %{state | delivered: delivery_count + 1}} - end - - def handle_cast(:inc_dropped, %{dropped: drop_count} = state) do - {:noreply, %{state | dropped: drop_count + 1}} - end - - def handle_info({:send, data, transport, retries}, %{delivered: delivery_count} = state) do - case transport.publish_one(data) do - {:ok, _} -> - {:noreply, %{state | delivered: delivery_count + 1}} - - {:error, _reason} -> - enqueue(data, transport, retries) - {:noreply, state} - end - end - - def handle_info( - :retry_timer_run, - %{queue_table: queue_table, running_jobs: running_jobs} = state - ) do - maybe_kickoff_timer() - running_jobs = maybe_start_job(running_jobs, queue_table) - {:noreply, %{state | running_jobs: running_jobs}} - end - - def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do - %{running_jobs: running_jobs, queue_table: queue_table} = state - running_jobs = :sets.del_element(ref, running_jobs) - running_jobs = maybe_start_job(running_jobs, queue_table) - {:noreply, %{state | running_jobs: running_jobs}} - end - - def handle_info(unknown, state) do - Logger.debug("RetryQueue: don't know what to do with #{inspect(unknown)}, ignoring") - {:noreply, state} - end - - if Pleroma.Config.get(:env) == :test do - defp growth_function(_retries) do - _shutit = Pleroma.Config.get([__MODULE__, :initial_timeout]) - DateTime.to_unix(DateTime.utc_now()) - 1 - end - else - defp growth_function(retries) do - round(Pleroma.Config.get([__MODULE__, :initial_timeout]) * :math.pow(retries, 3)) + - DateTime.to_unix(DateTime.utc_now()) - end - end - - defp maybe_kickoff_timer do - GenServer.cast(__MODULE__, :kickoff_timer) - end -end diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 9b01ebcc6..bbaa293fd 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -170,6 +170,15 @@ defmodule Pleroma.Web.Salmon do end end + def publish_one(%{recipient_id: recipient_id} = params) do + recipient = User.get_by_id(recipient_id) + + params + |> Map.delete(:recipient_id) + |> Map.put(:recipient, recipient) + |> publish_one() + end + def publish_one(_), do: :noop @supported_activities [ @@ -218,7 +227,7 @@ defmodule Pleroma.Web.Salmon do Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end) Publisher.enqueue_one(__MODULE__, %{ - recipient: remote_user, + recipient_id: remote_user.id, feed: feed, unreachable_since: reachable_urls_metadata[remote_user.info.salmon] }) diff --git a/lib/pleroma/workers/publisher.ex b/lib/pleroma/workers/publisher.ex new file mode 100644 index 000000000..639794830 --- /dev/null +++ b/lib/pleroma/workers/publisher.ex @@ -0,0 +1,14 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.Publisher do + use Oban.Worker, queue: "federator_outgoing", max_attempts: 5 + + @impl Oban.Worker + def perform(%Oban.Job{args: %{module: module_name, params: params}}) do + module_name + |> String.to_atom() + |> apply(:publish_one, [params]) + end +end -- cgit v1.2.3 From b7fad8d395c2bd1afe445a370e539571f5ec0c18 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 9 Aug 2019 20:08:01 +0300 Subject: [#1149] Oban jobs implementation for :federator_incoming and :federator_outgoing queues. --- lib/pleroma/web/activity_pub/utils.ex | 9 +-- lib/pleroma/web/federator/federator.ex | 132 ++++++++------------------------- lib/pleroma/web/federator/publisher.ex | 12 ++- lib/pleroma/workers/publisher.ex | 25 ++++++- lib/pleroma/workers/receiver.ex | 61 +++++++++++++++ lib/pleroma/workers/subscriber.ex | 44 +++++++++++ 6 files changed, 171 insertions(+), 112 deletions(-) create mode 100644 lib/pleroma/workers/receiver.ex create mode 100644 lib/pleroma/workers/subscriber.ex (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 39074888b..f0917f9d4 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -168,14 +168,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do """ def maybe_federate(%Activity{local: true} = activity) do if Pleroma.Config.get!([:instance, :federating]) do - priority = - case activity.data["type"] do - "Delete" -> 10 - "Create" -> 1 - _ -> 5 - end - - Pleroma.Web.Federator.publish(activity, priority) + Pleroma.Web.Federator.publish(activity) end :ok diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 97ec9d549..bb9eadfee 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -3,22 +3,15 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Federator do - alias Pleroma.Activity - alias Pleroma.Object.Containment - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Transmogrifier - alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.Federator.Publisher - alias Pleroma.Web.OStatus - alias Pleroma.Web.Websub + alias Pleroma.Workers.Publisher, as: PublisherWorker + alias Pleroma.Workers.Receiver, as: ReceiverWorker + alias Pleroma.Workers.Subscriber, as: SubscriberWorker require Logger def init do # 1 minute - Process.sleep(1000 * 60) - refresh_subscriptions() + refresh_subscriptions(schedule_in: 60) end @doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)" @@ -36,111 +29,50 @@ defmodule Pleroma.Web.Federator do # Client API def incoming_doc(doc) do - PleromaJobQueue.enqueue(:federator_incoming, __MODULE__, [:incoming_doc, doc]) + %{"op" => "incoming_doc", "body" => doc} + |> ReceiverWorker.new(worker_args(:federator_incoming)) + |> Pleroma.Repo.insert() end def incoming_ap_doc(params) do - PleromaJobQueue.enqueue(:federator_incoming, __MODULE__, [:incoming_ap_doc, params]) + %{"op" => "incoming_ap_doc", "params" => params} + |> ReceiverWorker.new(worker_args(:federator_incoming)) + |> Pleroma.Repo.insert() end - def publish(activity, priority \\ 1) do - PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority) + def publish(%{id: "pleroma:fakeid"} = activity) do + PublisherWorker.perform_publish(activity) end - def verify_websub(websub) do - PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub]) - end - - def request_subscription(sub) do - PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub]) - end - - def refresh_subscriptions do - PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions]) - end - - # Job Worker Callbacks - - def perform(:refresh_subscriptions) do - Logger.debug("Federator running refresh subscriptions") - Websub.refresh_subscriptions() - - spawn(fn -> - # 6 hours - Process.sleep(1000 * 60 * 60 * 6) - refresh_subscriptions() - end) - end - - def perform(:request_subscription, websub) do - Logger.debug("Refreshing #{websub.topic}") - - with {:ok, websub} <- Websub.request_subscription(websub) do - Logger.debug("Successfully refreshed #{websub.topic}") - else - _e -> Logger.debug("Couldn't refresh #{websub.topic}") - end + def publish(activity) do + %{"op" => "publish", "activity_id" => activity.id} + |> PublisherWorker.new(worker_args(:federator_outgoing)) + |> Pleroma.Repo.insert() end - def perform(:publish, activity) do - Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) - - with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]), - {:ok, actor} <- User.ensure_keys_present(actor) do - Publisher.publish(actor, activity) - end - end - - def perform(:verify_websub, websub) do - Logger.debug(fn -> - "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" - end) - - Websub.verify(websub) - end - - def perform(:incoming_doc, doc) do - Logger.info("Got document, trying to parse") - OStatus.handle_incoming(doc) + def verify_websub(websub) do + %{"op" => "verify_websub", "websub_id" => websub.id} + |> SubscriberWorker.new(worker_args(:federator_outgoing)) + |> Pleroma.Repo.insert() end - def perform(:incoming_ap_doc, params) do - Logger.info("Handling incoming AP activity") - - params = Utils.normalize_params(params) - - # NOTE: we use the actor ID to do the containment, this is fine because an - # actor shouldn't be acting on objects outside their own AP server. - with {:ok, _user} <- ap_enabled_actor(params["actor"]), - nil <- Activity.normalize(params["id"]), - :ok <- Containment.contain_origin_from_id(params["actor"], params), - {:ok, activity} <- Transmogrifier.handle_incoming(params) do - {:ok, activity} - else - %Activity{} -> - Logger.info("Already had #{params["id"]}") - :error - - _e -> - # Just drop those for now - Logger.info("Unhandled activity") - Logger.info(Jason.encode!(params, pretty: true)) - :error - end + def request_subscription(websub) do + %{"op" => "request_subscription", "websub_id" => websub.id} + |> SubscriberWorker.new(worker_args(:federator_outgoing)) + |> Pleroma.Repo.insert() end - def perform(type, _) do - Logger.debug(fn -> "Unknown task: #{type}" end) - {:error, "Don't know what to do with this"} + def refresh_subscriptions(worker_args \\ []) do + %{"op" => "refresh_subscriptions"} + |> SubscriberWorker.new(worker_args ++ [max_attempts: 1] ++ worker_args(:federator_outgoing)) + |> Pleroma.Repo.insert() end - def ap_enabled_actor(id) do - user = User.get_cached_by_ap_id(id) - - if User.ap_enabled?(user) do - {:ok, user} + defp worker_args(queue) do + if max_attempts = Pleroma.Config.get([:workers, :retries, queue]) do + [max_attempts: max_attempts] else - ActivityPub.make_user_from_ap_id(id) + [] end end end diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex index e8c1bf17f..05d2be615 100644 --- a/lib/pleroma/web/federator/publisher.ex +++ b/lib/pleroma/web/federator/publisher.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.Federator.Publisher do alias Pleroma.Activity alias Pleroma.Config alias Pleroma.User + alias Pleroma.Workers.Publisher, as: PublisherWorker require Logger @@ -30,8 +31,15 @@ defmodule Pleroma.Web.Federator.Publisher do """ @spec enqueue_one(module(), Map.t()) :: :ok def enqueue_one(module, %{} = params) do - %{module: to_string(module), params: params} - |> Pleroma.Workers.Publisher.new() + worker_args = + if max_attempts = Pleroma.Config.get([:workers, :retries, :federator_outgoing]) do + [max_attempts: max_attempts] + else + [] + end + + %{"op" => "publish_one", "module" => to_string(module), "params" => params} + |> PublisherWorker.new(worker_args) |> Pleroma.Repo.insert() end diff --git a/lib/pleroma/workers/publisher.ex b/lib/pleroma/workers/publisher.ex index 639794830..67871977a 100644 --- a/lib/pleroma/workers/publisher.ex +++ b/lib/pleroma/workers/publisher.ex @@ -3,12 +3,33 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.Publisher do - use Oban.Worker, queue: "federator_outgoing", max_attempts: 5 + alias Pleroma.Activity + alias Pleroma.User + + # Note: `max_attempts` is intended to be overridden in `new/1` call + use Oban.Worker, + queue: "federator_outgoing", + max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) @impl Oban.Worker - def perform(%Oban.Job{args: %{module: module_name, params: params}}) do + def perform(%{"op" => "publish", "activity_id" => activity_id}) do + with %Activity{} = activity <- Activity.get_by_id(activity_id) do + perform_publish(activity) + else + _ -> raise "Non-existing activity: #{activity_id}" + end + end + + def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}) do module_name |> String.to_atom() |> apply(:publish_one, [params]) end + + def perform_publish(%Activity{} = activity) do + with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]), + {:ok, actor} <- User.ensure_keys_present(actor) do + Pleroma.Web.Federator.Publisher.publish(actor, activity) + end + end end diff --git a/lib/pleroma/workers/receiver.ex b/lib/pleroma/workers/receiver.ex new file mode 100644 index 000000000..43558b4e6 --- /dev/null +++ b/lib/pleroma/workers/receiver.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.Receiver do + alias Pleroma.Activity + alias Pleroma.Object.Containment + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.OStatus + + require Logger + + # Note: `max_attempts` is intended to be overridden in `new/1` call + use Oban.Worker, + queue: "federator_incoming", + max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + + @impl Oban.Worker + def perform(%{"op" => "incoming_doc", "body" => doc}) do + Logger.info("Got incoming document, trying to parse") + OStatus.handle_incoming(doc) + end + + def perform(%{"op" => "incoming_ap_doc", "params" => params}) do + Logger.info("Handling incoming AP activity") + + params = Utils.normalize_params(params) + + # NOTE: we use the actor ID to do the containment, this is fine because an + # actor shouldn't be acting on objects outside their own AP server. + with {:ok, _user} <- ap_enabled_actor(params["actor"]), + nil <- Activity.normalize(params["id"]), + :ok <- Containment.contain_origin_from_id(params["actor"], params), + {:ok, activity} <- Transmogrifier.handle_incoming(params) do + {:ok, activity} + else + %Activity{} -> + Logger.info("Already had #{params["id"]}") + :error + + _e -> + # Just drop those for now + Logger.info("Unhandled activity") + Logger.info(Jason.encode!(params, pretty: true)) + :error + end + end + + defp ap_enabled_actor(id) do + user = User.get_cached_by_ap_id(id) + + if User.ap_enabled?(user) do + {:ok, user} + else + ActivityPub.make_user_from_ap_id(id) + end + end +end diff --git a/lib/pleroma/workers/subscriber.ex b/lib/pleroma/workers/subscriber.ex new file mode 100644 index 000000000..a8c01bb10 --- /dev/null +++ b/lib/pleroma/workers/subscriber.ex @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.Subscriber do + alias Pleroma.Repo + alias Pleroma.Web.Websub + alias Pleroma.Web.Websub.WebsubClientSubscription + + require Logger + + # Note: `max_attempts` is intended to be overridden in `new/1` call + use Oban.Worker, + queue: "federator_outgoing", + max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + + @impl Oban.Worker + def perform(%{"op" => "refresh_subscriptions"}) do + Websub.refresh_subscriptions() + # Schedule the next run in 6 hours + Pleroma.Web.Federator.refresh_subscriptions(schedule_in: 3600 * 6) + end + + def perform(%{"op" => "request_subscription", "websub_id" => websub_id}) do + websub = Repo.get(WebsubClientSubscription, websub_id) + Logger.debug("Refreshing #{websub.topic}") + + with {:ok, websub} <- Websub.request_subscription(websub) do + Logger.debug("Successfully refreshed #{websub.topic}") + else + _e -> Logger.debug("Couldn't refresh #{websub.topic}") + end + end + + def perform(%{"op" => "verify_websub", "websub_id" => websub_id}) do + websub = Repo.get(WebsubClientSubscription, websub_id) + + Logger.debug(fn -> + "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" + end) + + Websub.verify(websub) + end +end -- cgit v1.2.3 From 33a5fc4a70b6f9b8c2d8c03a412d7eec8d5b3db1 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 10 Aug 2019 20:38:31 +0300 Subject: [#1149] Fixed failing tests. Ensured Instance.set_unreachable/2 supports ISO 8601 datetime. --- lib/pleroma/digest_email_worker.ex | 4 +--- lib/pleroma/instances/instance.ex | 8 +++++++- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/digest_email_worker.ex b/lib/pleroma/digest_email_worker.ex index 18e67d39b..3b0e2bca6 100644 --- a/lib/pleroma/digest_email_worker.ex +++ b/lib/pleroma/digest_email_worker.ex @@ -1,8 +1,6 @@ defmodule Pleroma.DigestEmailWorker do import Ecto.Query - @queue_name :digest_emails - def perform do config = Pleroma.Config.get([:email_notifications, :digest]) negative_interval = -Map.fetch!(config, :interval) @@ -17,7 +15,7 @@ defmodule Pleroma.DigestEmailWorker do select: u ) |> Pleroma.Repo.all() - |> Enum.each(&PleromaJobQueue.enqueue(@queue_name, __MODULE__, [&1])) + |> Enum.each(&PleromaJobQueue.enqueue(:digest_emails, __MODULE__, [&1])) end @doc """ diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 4d7ed4ca1..544c4b687 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -90,7 +90,7 @@ defmodule Pleroma.Instances.Instance do def set_unreachable(url_or_host, unreachable_since \\ nil) def set_unreachable(url_or_host, unreachable_since) when is_binary(url_or_host) do - unreachable_since = unreachable_since || DateTime.utc_now() + unreachable_since = parse_datetime(unreachable_since) || NaiveDateTime.utc_now() host = host(url_or_host) existing_record = Repo.get_by(Instance, %{host: host}) @@ -114,4 +114,10 @@ defmodule Pleroma.Instances.Instance do end def set_unreachable(_, _), do: {:error, nil} + + defp parse_datetime(datetime) when is_binary(datetime) do + NaiveDateTime.from_iso8601(datetime) + end + + defp parse_datetime(datetime), do: datetime end -- cgit v1.2.3 From 0e1c481a94392b69833fbe6afc184ebbd90e1330 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 13 Aug 2019 20:20:26 +0300 Subject: [#1149] Added more oban workers. Refactoring. --- lib/pleroma/digest_email_worker.ex | 11 ++- lib/pleroma/scheduled_activity_worker.ex | 8 +- lib/pleroma/user.ex | 57 ++++++++----- lib/pleroma/web/activity_pub/activity_pub.ex | 7 +- .../activity_pub/mrf/mediaproxy_warming_policy.ex | 12 ++- lib/pleroma/web/activity_pub/transmogrifier.ex | 7 +- lib/pleroma/web/federator/federator.ex | 98 ++++++++++++++++++++-- lib/pleroma/web/oauth/token/clean_worker.ex | 10 ++- lib/pleroma/web/push/push.ex | 12 ++- .../web/twitter_api/controllers/util_controller.ex | 14 +--- lib/pleroma/workers/background_worker.ex | 66 +++++++++++++++ lib/pleroma/workers/helper.ex | 13 +++ lib/pleroma/workers/mailer.ex | 18 ++++ lib/pleroma/workers/publisher.ex | 20 +---- lib/pleroma/workers/receiver.ex | 46 +--------- lib/pleroma/workers/scheduled_activity_worker.ex | 15 ++++ lib/pleroma/workers/subscriber.ex | 23 +---- lib/pleroma/workers/transmogrifier.ex | 18 ++++ lib/pleroma/workers/web_pusher.ex | 19 +++++ 19 files changed, 347 insertions(+), 127 deletions(-) create mode 100644 lib/pleroma/workers/background_worker.ex create mode 100644 lib/pleroma/workers/helper.ex create mode 100644 lib/pleroma/workers/mailer.ex create mode 100644 lib/pleroma/workers/scheduled_activity_worker.ex create mode 100644 lib/pleroma/workers/transmogrifier.ex create mode 100644 lib/pleroma/workers/web_pusher.ex (limited to 'lib') diff --git a/lib/pleroma/digest_email_worker.ex b/lib/pleroma/digest_email_worker.ex index 3b0e2bca6..6e44cc955 100644 --- a/lib/pleroma/digest_email_worker.ex +++ b/lib/pleroma/digest_email_worker.ex @@ -1,6 +1,11 @@ defmodule Pleroma.DigestEmailWorker do + alias Pleroma.Repo + alias Pleroma.Workers.Mailer, as: MailerWorker + import Ecto.Query + defdelegate worker_args(queue), to: Pleroma.Workers.Helper + def perform do config = Pleroma.Config.get([:email_notifications, :digest]) negative_interval = -Map.fetch!(config, :interval) @@ -15,7 +20,11 @@ defmodule Pleroma.DigestEmailWorker do select: u ) |> Pleroma.Repo.all() - |> Enum.each(&PleromaJobQueue.enqueue(:digest_emails, __MODULE__, [&1])) + |> Enum.each(fn user -> + %{"op" => "digest_email", "user_id" => user.id} + |> MailerWorker.new([queue: "digest_emails"] ++ worker_args(:digest_emails)) + |> Repo.insert() + end) end @doc """ diff --git a/lib/pleroma/scheduled_activity_worker.ex b/lib/pleroma/scheduled_activity_worker.ex index 65b38622f..cabea51ca 100644 --- a/lib/pleroma/scheduled_activity_worker.ex +++ b/lib/pleroma/scheduled_activity_worker.ex @@ -8,14 +8,18 @@ defmodule Pleroma.ScheduledActivityWorker do """ alias Pleroma.Config + alias Pleroma.Repo alias Pleroma.ScheduledActivity alias Pleroma.User alias Pleroma.Web.CommonAPI + use GenServer require Logger @schedule_interval :timer.minutes(1) + defdelegate worker_args(queue), to: Pleroma.Workers.Helper + def start_link do GenServer.start_link(__MODULE__, nil) end @@ -45,7 +49,9 @@ defmodule Pleroma.ScheduledActivityWorker do def handle_info(:perform, state) do ScheduledActivity.due_activities(@schedule_interval) |> Enum.each(fn scheduled_activity -> - PleromaJobQueue.enqueue(:scheduled_activities, __MODULE__, [:execute, scheduled_activity.id]) + %{"op" => "execute", "activity_id" => scheduled_activity.id} + |> Pleroma.Workers.ScheduledActivityWorker.new(worker_args(:scheduled_activities)) + |> Repo.insert() end) schedule_next() diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 7d18f099e..bc2102ca7 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -26,6 +26,7 @@ defmodule Pleroma.User do alias Pleroma.Web.OStatus alias Pleroma.Web.RelMe alias Pleroma.Web.Websub + alias Pleroma.Workers.BackgroundWorker require Logger @@ -39,6 +40,8 @@ defmodule Pleroma.User do @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ + defdelegate worker_args(queue), to: Pleroma.Workers.Helper + schema "users" do field(:bio, :string) field(:email, :string) @@ -579,8 +582,11 @@ defmodule Pleroma.User do end @doc "Fetch some posts when the user has just been federated with" - def fetch_initial_posts(user), - do: PleromaJobQueue.enqueue(:background, __MODULE__, [:fetch_initial_posts, user]) + def fetch_initial_posts(user) do + %{"op" => "fetch_initial_posts", "user_id" => user.id} + |> BackgroundWorker.new(worker_args(:background)) + |> Repo.insert() + end @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() def get_followers_query(%User{} = user, nil) do @@ -1001,7 +1007,9 @@ defmodule Pleroma.User do end def deactivate_async(user, status \\ true) do - PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status]) + %{"op" => "deactivate_user", "user_id" => user.id, "status" => status} + |> BackgroundWorker.new(worker_args(:background)) + |> Repo.insert() end def deactivate(%User{} = user, status \\ true) do @@ -1029,9 +1037,11 @@ defmodule Pleroma.User do |> update_and_set_cache() end - @spec delete(User.t()) :: :ok - def delete(%User{} = user), - do: PleromaJobQueue.enqueue(:background, __MODULE__, [:delete, user]) + def delete(%User{} = user) do + %{"op" => "delete_user", "user_id" => user.id} + |> BackgroundWorker.new(worker_args(:background)) + |> Repo.insert() + end @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:delete, %User{} = user) do @@ -1138,21 +1148,26 @@ defmodule Pleroma.User do Repo.all(query) end - def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers), - do: - PleromaJobQueue.enqueue(:background, __MODULE__, [ - :blocks_import, - blocker, - blocked_identifiers - ]) - - def follow_import(%User{} = follower, followed_identifiers) when is_list(followed_identifiers), - do: - PleromaJobQueue.enqueue(:background, __MODULE__, [ - :follow_import, - follower, - followed_identifiers - ]) + def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do + %{ + "op" => "blocks_import", + "blocker_id" => blocker.id, + "blocked_identifiers" => blocked_identifiers + } + |> BackgroundWorker.new(worker_args(:background)) + |> Repo.insert() + end + + def follow_import(%User{} = follower, followed_identifiers) + when is_list(followed_identifiers) do + %{ + "op" => "follow_import", + "follower_id" => follower.id, + "followed_identifiers" => followed_identifiers + } + |> BackgroundWorker.new(worker_args(:background)) + |> Repo.insert() + end def delete_user_activities(%User{ap_id: ap_id} = user) do ap_id diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1a279a7df..8be8ac86f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.WebFinger + alias Pleroma.Workers.BackgroundWorker import Ecto.Query import Pleroma.Web.ActivityPub.Utils @@ -25,6 +26,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do require Logger require Pleroma.Constants + defdelegate worker_args(queue), to: Pleroma.Workers.Helper + # For Announce activities, we filter the recipients based on following status for any actors # that match actual users. See issue #164 for more information about why this is necessary. defp get_recipients(%{"type" => "Announce"} = data) do @@ -145,7 +148,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do activity end - PleromaJobQueue.enqueue(:background, Pleroma.Web.RichMedia.Helpers, [:fetch, activity]) + %{"op" => "fetch_data_for_activity", "activity_id" => activity.id} + |> BackgroundWorker.new(worker_args(:background)) + |> Repo.insert() Notification.create_notifications(activity) diff --git a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex index 01d21a299..1df3bb5b6 100644 --- a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex @@ -7,7 +7,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do @behaviour Pleroma.Web.ActivityPub.MRF alias Pleroma.HTTP + alias Pleroma.Repo alias Pleroma.Web.MediaProxy + alias Pleroma.Workers.BackgroundWorker require Logger @@ -16,6 +18,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do recv_timeout: 10_000 ] + defdelegate worker_args(queue), to: Pleroma.Workers.Helper + def perform(:prefetch, url) do Logger.info("Prefetching #{inspect(url)}") @@ -30,7 +34,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do url |> Enum.each(fn %{"href" => href} -> - PleromaJobQueue.enqueue(:background, __MODULE__, [:prefetch, href]) + %{"op" => "media_proxy_prefetch", "url" => href} + |> BackgroundWorker.new(worker_args(:background)) + |> Repo.insert() x -> Logger.debug("Unhandled attachment URL object #{inspect(x)}") @@ -46,7 +52,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do %{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message ) when is_list(attachments) and length(attachments) > 0 do - PleromaJobQueue.enqueue(:background, __MODULE__, [:preload, message]) + %{"op" => "media_proxy_preload", "message" => message} + |> BackgroundWorker.new(worker_args(:background)) + |> Repo.insert() {:ok, message} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 5403b71d8..0f117cd04 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -15,12 +15,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Federator + alias Pleroma.Workers.Transmogrifier, as: TransmogrifierWorker import Ecto.Query require Logger require Pleroma.Constants + defdelegate worker_args(queue), to: Pleroma.Workers.Helper + @doc """ Modifies an incoming AP object (mastodon format) to our internal format. """ @@ -1073,7 +1076,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do already_ap <- User.ap_enabled?(user), {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do unless already_ap do - PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user]) + %{"op" => "user_upgrade", "user_id" => user.id} + |> TransmogrifierWorker.new(worker_args(:transmogrifier)) + |> Repo.insert() end {:ok, user} diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index bb9eadfee..d85fe824f 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -3,12 +3,23 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Federator do + alias Pleroma.Activity + alias Pleroma.Object.Containment + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.Federator.Publisher + alias Pleroma.Web.OStatus + alias Pleroma.Web.Websub alias Pleroma.Workers.Publisher, as: PublisherWorker alias Pleroma.Workers.Receiver, as: ReceiverWorker alias Pleroma.Workers.Subscriber, as: SubscriberWorker require Logger + defdelegate worker_args(queue), to: Pleroma.Workers.Helper + def init do # 1 minute refresh_subscriptions(schedule_in: 60) @@ -41,7 +52,7 @@ defmodule Pleroma.Web.Federator do end def publish(%{id: "pleroma:fakeid"} = activity) do - PublisherWorker.perform_publish(activity) + perform(:publish, activity) end def publish(activity) do @@ -68,11 +79,88 @@ defmodule Pleroma.Web.Federator do |> Pleroma.Repo.insert() end - defp worker_args(queue) do - if max_attempts = Pleroma.Config.get([:workers, :retries, queue]) do - [max_attempts: max_attempts] + # Job Worker Callbacks + + @spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()} + def perform(:publish_one, module, params) do + apply(module, :publish_one, [params]) + end + + def perform(:publish, activity) do + Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) + + with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]), + {:ok, actor} <- User.ensure_keys_present(actor) do + Publisher.publish(actor, activity) + end + end + + def perform(:incoming_doc, doc) do + Logger.info("Got document, trying to parse") + OStatus.handle_incoming(doc) + end + + def perform(:incoming_ap_doc, params) do + Logger.info("Handling incoming AP activity") + + params = Utils.normalize_params(params) + + # NOTE: we use the actor ID to do the containment, this is fine because an + # actor shouldn't be acting on objects outside their own AP server. + with {:ok, _user} <- ap_enabled_actor(params["actor"]), + nil <- Activity.normalize(params["id"]), + :ok <- Containment.contain_origin_from_id(params["actor"], params), + {:ok, activity} <- Transmogrifier.handle_incoming(params) do + {:ok, activity} + else + %Activity{} -> + Logger.info("Already had #{params["id"]}") + :error + + _e -> + # Just drop those for now + Logger.info("Unhandled activity") + Logger.info(Jason.encode!(params, pretty: true)) + :error + end + end + + def perform(:request_subscription, websub) do + Logger.debug("Refreshing #{websub.topic}") + + with {:ok, websub} <- Websub.request_subscription(websub) do + Logger.debug("Successfully refreshed #{websub.topic}") + else + _e -> Logger.debug("Couldn't refresh #{websub.topic}") + end + end + + def perform(:verify_websub, websub) do + Logger.debug(fn -> + "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" + end) + + Websub.verify(websub) + end + + def perform(:refresh_subscriptions) do + Logger.debug("Federator running refresh subscriptions") + Websub.refresh_subscriptions() + + spawn(fn -> + # 6 hours + Process.sleep(1000 * 60 * 60 * 6) + refresh_subscriptions() + end) + end + + def ap_enabled_actor(id) do + user = User.get_cached_by_ap_id(id) + + if User.ap_enabled?(user) do + {:ok, user} else - [] + ActivityPub.make_user_from_ap_id(id) end end end diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex index dca852449..c0c9c3653 100644 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -14,9 +14,12 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do [:oauth2, :clean_expired_tokens_interval], 86_400_000 ) - @queue :background + alias Pleroma.Repo alias Pleroma.Web.OAuth.Token + alias Pleroma.Workers.BackgroundWorker + + defdelegate worker_args(queue), to: Pleroma.Workers.Helper def start_link, do: GenServer.start_link(__MODULE__, nil) @@ -31,8 +34,11 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do @doc false def handle_info(:perform, state) do + %{"op" => "clean_expired_tokens"} + |> BackgroundWorker.new(worker_args(:background)) + |> Repo.insert() + Process.send_after(self(), :perform, @interval) - PleromaJobQueue.enqueue(@queue, __MODULE__, [:clean]) {:noreply, state} end diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex index 729dad02a..b4f0e5127 100644 --- a/lib/pleroma/web/push/push.ex +++ b/lib/pleroma/web/push/push.ex @@ -3,10 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Push do - alias Pleroma.Web.Push.Impl + alias Pleroma.Repo + alias Pleroma.Workers.WebPusher require Logger + defdelegate worker_args(queue), to: Pleroma.Workers.Helper + def init do unless enabled() do Logger.warn(""" @@ -31,6 +34,9 @@ defmodule Pleroma.Web.Push do end end - def send(notification), - do: PleromaJobQueue.enqueue(:web_push, Impl, [notification]) + def send(notification) do + %{"op" => "web_push", "notification_id" => notification.id} + |> WebPusher.new(worker_args(:web_push)) + |> Repo.insert() + end end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 3405bd3b7..7ba4ad305 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -265,12 +265,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do String.split(line, ",") |> List.first() end) |> List.delete("Account address") do - PleromaJobQueue.enqueue(:background, User, [ - :follow_import, - follower, - followed_identifiers - ]) - + User.follow_import(follower, followed_identifiers) json(conn, "job started") end end @@ -281,12 +276,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do with blocked_identifiers <- String.split(list) do - PleromaJobQueue.enqueue(:background, User, [ - :blocks_import, - blocker, - blocked_identifiers - ]) - + User.blocks_import(blocker, blocked_identifiers) json(conn, "job started") end end diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex new file mode 100644 index 000000000..3ab2b6bcc --- /dev/null +++ b/lib/pleroma/workers/background_worker.ex @@ -0,0 +1,66 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.BackgroundWorker do + alias Pleroma.Activity + alias Pleroma.User + alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy + alias Pleroma.Web.OAuth.Token.CleanWorker + + # Note: `max_attempts` is intended to be overridden in `new/1` call + use Oban.Worker, + queue: "background", + max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + + @impl Oban.Worker + def perform(%{"op" => "fetch_initial_posts", "user_id" => user_id}) do + user = User.get_by_id(user_id) + User.perform(:fetch_initial_posts, user) + end + + def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}) do + user = User.get_by_id(user_id) + User.perform(:deactivate_async, user, status) + end + + def perform(%{"op" => "delete_user", "user_id" => user_id}) do + user = User.get_by_id(user_id) + User.perform(:delete, user) + end + + def perform(%{ + "op" => "blocks_import", + "blocker_id" => blocker_id, + "blocked_identifiers" => blocked_identifiers + }) do + blocker = User.get_by_id(blocker_id) + User.perform(:blocks_import, blocker, blocked_identifiers) + end + + def perform(%{ + "op" => "follow_import", + "follower_id" => follower_id, + "followed_identifiers" => followed_identifiers + }) do + follower = User.get_by_id(follower_id) + User.perform(:follow_import, follower, followed_identifiers) + end + + def perform(%{"op" => "clean_expired_tokens"}) do + CleanWorker.perform(:clean) + end + + def perform(%{"op" => "media_proxy_preload", "message" => message}) do + MediaProxyWarmingPolicy.perform(:preload, message) + end + + def perform(%{"op" => "media_proxy_prefetch", "url" => url}) do + MediaProxyWarmingPolicy.perform(:prefetch, url) + end + + def perform(%{"op" => "fetch_data_for_activity", "activity_id" => activity_id}) do + activity = Activity.get_by_id(activity_id) + Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity) + end +end diff --git a/lib/pleroma/workers/helper.ex b/lib/pleroma/workers/helper.ex new file mode 100644 index 000000000..3286ce0e8 --- /dev/null +++ b/lib/pleroma/workers/helper.ex @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.Helper do + def worker_args(queue) do + if max_attempts = Pleroma.Config.get([:workers, :retries, queue]) do + [max_attempts: max_attempts] + else + [] + end + end +end diff --git a/lib/pleroma/workers/mailer.ex b/lib/pleroma/workers/mailer.ex new file mode 100644 index 000000000..da7fa6fd5 --- /dev/null +++ b/lib/pleroma/workers/mailer.ex @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.Mailer do + alias Pleroma.User + + # Note: `max_attempts` is intended to be overridden in `new/1` call + use Oban.Worker, + queue: "mailer", + max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + + @impl Oban.Worker + def perform(%{"op" => "digest_email", "user_id" => user_id}) do + user = User.get_by_id(user_id) + Pleroma.DigestEmailWorker.perform(user) + end +end diff --git a/lib/pleroma/workers/publisher.ex b/lib/pleroma/workers/publisher.ex index 67871977a..c890ffb79 100644 --- a/lib/pleroma/workers/publisher.ex +++ b/lib/pleroma/workers/publisher.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Workers.Publisher do alias Pleroma.Activity - alias Pleroma.User + alias Pleroma.Web.Federator # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, @@ -13,23 +13,11 @@ defmodule Pleroma.Workers.Publisher do @impl Oban.Worker def perform(%{"op" => "publish", "activity_id" => activity_id}) do - with %Activity{} = activity <- Activity.get_by_id(activity_id) do - perform_publish(activity) - else - _ -> raise "Non-existing activity: #{activity_id}" - end + activity = Activity.get_by_id(activity_id) + Federator.perform(:publish, activity) end def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}) do - module_name - |> String.to_atom() - |> apply(:publish_one, [params]) - end - - def perform_publish(%Activity{} = activity) do - with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]), - {:ok, actor} <- User.ensure_keys_present(actor) do - Pleroma.Web.Federator.Publisher.publish(actor, activity) - end + Federator.perform(:publish_one, String.to_atom(module_name), params) end end diff --git a/lib/pleroma/workers/receiver.ex b/lib/pleroma/workers/receiver.ex index 43558b4e6..d3de95716 100644 --- a/lib/pleroma/workers/receiver.ex +++ b/lib/pleroma/workers/receiver.ex @@ -3,15 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.Receiver do - alias Pleroma.Activity - alias Pleroma.Object.Containment - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Transmogrifier - alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.OStatus - - require Logger + alias Pleroma.Web.Federator # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, @@ -20,42 +12,10 @@ defmodule Pleroma.Workers.Receiver do @impl Oban.Worker def perform(%{"op" => "incoming_doc", "body" => doc}) do - Logger.info("Got incoming document, trying to parse") - OStatus.handle_incoming(doc) + Federator.perform(:incoming_doc, doc) end def perform(%{"op" => "incoming_ap_doc", "params" => params}) do - Logger.info("Handling incoming AP activity") - - params = Utils.normalize_params(params) - - # NOTE: we use the actor ID to do the containment, this is fine because an - # actor shouldn't be acting on objects outside their own AP server. - with {:ok, _user} <- ap_enabled_actor(params["actor"]), - nil <- Activity.normalize(params["id"]), - :ok <- Containment.contain_origin_from_id(params["actor"], params), - {:ok, activity} <- Transmogrifier.handle_incoming(params) do - {:ok, activity} - else - %Activity{} -> - Logger.info("Already had #{params["id"]}") - :error - - _e -> - # Just drop those for now - Logger.info("Unhandled activity") - Logger.info(Jason.encode!(params, pretty: true)) - :error - end - end - - defp ap_enabled_actor(id) do - user = User.get_cached_by_ap_id(id) - - if User.ap_enabled?(user) do - {:ok, user} - else - ActivityPub.make_user_from_ap_id(id) - end + Federator.perform(:incoming_ap_doc, params) end end diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex new file mode 100644 index 000000000..a49834fd8 --- /dev/null +++ b/lib/pleroma/workers/scheduled_activity_worker.ex @@ -0,0 +1,15 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.ScheduledActivityWorker do + # Note: `max_attempts` is intended to be overridden in `new/1` call + use Oban.Worker, + queue: "scheduled_activities", + max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + + @impl Oban.Worker + def perform(%{"op" => "execute", "activity_id" => activity_id}) do + Pleroma.ScheduledActivityWorker.perform(:execute, activity_id) + end +end diff --git a/lib/pleroma/workers/subscriber.ex b/lib/pleroma/workers/subscriber.ex index a8c01bb10..6af3ad0a1 100644 --- a/lib/pleroma/workers/subscriber.ex +++ b/lib/pleroma/workers/subscriber.ex @@ -4,11 +4,9 @@ defmodule Pleroma.Workers.Subscriber do alias Pleroma.Repo - alias Pleroma.Web.Websub + alias Pleroma.Web.Federator alias Pleroma.Web.Websub.WebsubClientSubscription - require Logger - # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, queue: "federator_outgoing", @@ -16,29 +14,16 @@ defmodule Pleroma.Workers.Subscriber do @impl Oban.Worker def perform(%{"op" => "refresh_subscriptions"}) do - Websub.refresh_subscriptions() - # Schedule the next run in 6 hours - Pleroma.Web.Federator.refresh_subscriptions(schedule_in: 3600 * 6) + Federator.perform(:refresh_subscriptions) end def perform(%{"op" => "request_subscription", "websub_id" => websub_id}) do websub = Repo.get(WebsubClientSubscription, websub_id) - Logger.debug("Refreshing #{websub.topic}") - - with {:ok, websub} <- Websub.request_subscription(websub) do - Logger.debug("Successfully refreshed #{websub.topic}") - else - _e -> Logger.debug("Couldn't refresh #{websub.topic}") - end + Federator.perform(:request_subscription, websub) end def perform(%{"op" => "verify_websub", "websub_id" => websub_id}) do websub = Repo.get(WebsubClientSubscription, websub_id) - - Logger.debug(fn -> - "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" - end) - - Websub.verify(websub) + Federator.perform(:verify_websub, websub) end end diff --git a/lib/pleroma/workers/transmogrifier.ex b/lib/pleroma/workers/transmogrifier.ex new file mode 100644 index 000000000..c6b4fab47 --- /dev/null +++ b/lib/pleroma/workers/transmogrifier.ex @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.Transmogrifier do + alias Pleroma.User + + # Note: `max_attempts` is intended to be overridden in `new/1` call + use Oban.Worker, + queue: "transmogrifier", + max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + + @impl Oban.Worker + def perform(%{"op" => "user_upgrade", "user_id" => user_id}) do + user = User.get_by_id(user_id) + Pleroma.Web.ActivityPub.Transmogrifier.perform(:user_upgrade, user) + end +end diff --git a/lib/pleroma/workers/web_pusher.ex b/lib/pleroma/workers/web_pusher.ex new file mode 100644 index 000000000..b99581eb0 --- /dev/null +++ b/lib/pleroma/workers/web_pusher.ex @@ -0,0 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.WebPusher do + alias Pleroma.Notification + alias Pleroma.Repo + + # Note: `max_attempts` is intended to be overridden in `new/1` call + use Oban.Worker, + queue: "web_push", + max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + + @impl Oban.Worker + def perform(%{"op" => "web_push", "notification_id" => notification_id}) do + notification = Repo.get(Notification, notification_id) + Pleroma.Web.Push.Impl.perform(notification) + end +end -- cgit v1.2.3 From a180c1360ecdbed76eccf3435bb2c831356746bc Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 14 Aug 2019 21:42:21 +0300 Subject: [#1149] Oban mailer job. Adjusted tests. --- lib/pleroma/application.ex | 1 + lib/pleroma/emails/mailer.ex | 13 ++++++++++++- lib/pleroma/workers/mailer.ex | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 5550a4902..7cf60f44a 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -233,6 +233,7 @@ defmodule Pleroma.Application do defp after_supervisor_start do with digest_config <- Application.get_env(:pleroma, :email_notifications)[:digest], true <- digest_config[:active] do + # TODO: consider replacing with `quantum` scheduler PleromaJobQueue.schedule( digest_config[:schedule], :digest_emails, diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 2e4657b7c..bb534f602 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -9,6 +9,8 @@ defmodule Pleroma.Emails.Mailer do The module contains functions to delivery email using Swoosh.Mailer. """ + alias Pleroma.Repo + alias Pleroma.Workers.Mailer, as: MailerWorker alias Swoosh.DeliveryError @otp_app :pleroma @@ -17,9 +19,18 @@ defmodule Pleroma.Emails.Mailer do @spec enabled?() :: boolean() def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled]) + defdelegate worker_args(queue), to: Pleroma.Workers.Helper + @doc "add email to queue" def deliver_async(email, config \\ []) do - PleromaJobQueue.enqueue(:mailer, __MODULE__, [:deliver_async, email, config]) + encoded_email = + email + |> :erlang.term_to_binary() + |> Base.encode64() + + %{"op" => "email", "encoded_email" => encoded_email, "config" => config} + |> MailerWorker.new(worker_args(:mailer)) + |> Repo.insert() end @doc "callback to perform send email from queue" diff --git a/lib/pleroma/workers/mailer.ex b/lib/pleroma/workers/mailer.ex index da7fa6fd5..8bf9952bc 100644 --- a/lib/pleroma/workers/mailer.ex +++ b/lib/pleroma/workers/mailer.ex @@ -11,6 +11,15 @@ defmodule Pleroma.Workers.Mailer do max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) @impl Oban.Worker + def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}) do + email = + encoded_email + |> Base.decode64!() + |> :erlang.binary_to_term() + + Pleroma.Emails.Mailer.deliver(email, config) + end + def perform(%{"op" => "digest_email", "user_id" => user_id}) do user = User.get_by_id(user_id) Pleroma.DigestEmailWorker.perform(user) -- cgit v1.2.3 From c29686309eaf2cdae039ce813755c0e23cdc4a03 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 23 Aug 2019 09:23:10 +0300 Subject: [#1149] Upgraded `oban` from 0.6.0 to 0.7.1. --- lib/pleroma/application.ex | 5 +-- lib/pleroma/workers/background_worker.ex | 42 ++++++++++++++---------- lib/pleroma/workers/mailer.ex | 6 ++-- lib/pleroma/workers/publisher.ex | 6 ++-- lib/pleroma/workers/receiver.ex | 6 ++-- lib/pleroma/workers/scheduled_activity_worker.ex | 4 +-- lib/pleroma/workers/subscriber.ex | 8 ++--- lib/pleroma/workers/transmogrifier.ex | 4 +-- lib/pleroma/workers/web_pusher.ex | 4 +-- 9 files changed, 44 insertions(+), 41 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 2e2922d28..384b03aa9 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -41,10 +41,7 @@ defmodule Pleroma.Application do hackney_pool_children() ++ [ Pleroma.Stats, - %{ - id: Oban, - start: {Oban, :start_link, [Application.get_env(:pleroma, Oban)]} - }, + {Oban, Application.get_env(:pleroma, Oban)}, %{ id: :web_push_init, start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 3ab2b6bcc..3c021b9b4 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -11,55 +11,61 @@ defmodule Pleroma.Workers.BackgroundWorker do # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, queue: "background", - max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + max_attempts: 1 @impl Oban.Worker - def perform(%{"op" => "fetch_initial_posts", "user_id" => user_id}) do + def perform(%{"op" => "fetch_initial_posts", "user_id" => user_id}, _job) do user = User.get_by_id(user_id) User.perform(:fetch_initial_posts, user) end - def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}) do + def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do user = User.get_by_id(user_id) User.perform(:deactivate_async, user, status) end - def perform(%{"op" => "delete_user", "user_id" => user_id}) do + def perform(%{"op" => "delete_user", "user_id" => user_id}, _job) do user = User.get_by_id(user_id) User.perform(:delete, user) end - def perform(%{ - "op" => "blocks_import", - "blocker_id" => blocker_id, - "blocked_identifiers" => blocked_identifiers - }) do + def perform( + %{ + "op" => "blocks_import", + "blocker_id" => blocker_id, + "blocked_identifiers" => blocked_identifiers + }, + _job + ) do blocker = User.get_by_id(blocker_id) User.perform(:blocks_import, blocker, blocked_identifiers) end - def perform(%{ - "op" => "follow_import", - "follower_id" => follower_id, - "followed_identifiers" => followed_identifiers - }) do + def perform( + %{ + "op" => "follow_import", + "follower_id" => follower_id, + "followed_identifiers" => followed_identifiers + }, + _job + ) do follower = User.get_by_id(follower_id) User.perform(:follow_import, follower, followed_identifiers) end - def perform(%{"op" => "clean_expired_tokens"}) do + def perform(%{"op" => "clean_expired_tokens"}, _job) do CleanWorker.perform(:clean) end - def perform(%{"op" => "media_proxy_preload", "message" => message}) do + def perform(%{"op" => "media_proxy_preload", "message" => message}, _job) do MediaProxyWarmingPolicy.perform(:preload, message) end - def perform(%{"op" => "media_proxy_prefetch", "url" => url}) do + def perform(%{"op" => "media_proxy_prefetch", "url" => url}, _job) do MediaProxyWarmingPolicy.perform(:prefetch, url) end - def perform(%{"op" => "fetch_data_for_activity", "activity_id" => activity_id}) do + def perform(%{"op" => "fetch_data_for_activity", "activity_id" => activity_id}, _job) do activity = Activity.get_by_id(activity_id) Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity) end diff --git a/lib/pleroma/workers/mailer.ex b/lib/pleroma/workers/mailer.ex index 8bf9952bc..1cce2ea03 100644 --- a/lib/pleroma/workers/mailer.ex +++ b/lib/pleroma/workers/mailer.ex @@ -8,10 +8,10 @@ defmodule Pleroma.Workers.Mailer do # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, queue: "mailer", - max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + max_attempts: 1 @impl Oban.Worker - def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}) do + def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}, _job) do email = encoded_email |> Base.decode64!() @@ -20,7 +20,7 @@ defmodule Pleroma.Workers.Mailer do Pleroma.Emails.Mailer.deliver(email, config) end - def perform(%{"op" => "digest_email", "user_id" => user_id}) do + def perform(%{"op" => "digest_email", "user_id" => user_id}, _job) do user = User.get_by_id(user_id) Pleroma.DigestEmailWorker.perform(user) end diff --git a/lib/pleroma/workers/publisher.ex b/lib/pleroma/workers/publisher.ex index c890ffb79..0a9084589 100644 --- a/lib/pleroma/workers/publisher.ex +++ b/lib/pleroma/workers/publisher.ex @@ -9,15 +9,15 @@ defmodule Pleroma.Workers.Publisher do # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, queue: "federator_outgoing", - max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + max_attempts: 1 @impl Oban.Worker - def perform(%{"op" => "publish", "activity_id" => activity_id}) do + def perform(%{"op" => "publish", "activity_id" => activity_id}, _job) do activity = Activity.get_by_id(activity_id) Federator.perform(:publish, activity) end - def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}) do + def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}, _job) do Federator.perform(:publish_one, String.to_atom(module_name), params) end end diff --git a/lib/pleroma/workers/receiver.ex b/lib/pleroma/workers/receiver.ex index d3de95716..4ee270d74 100644 --- a/lib/pleroma/workers/receiver.ex +++ b/lib/pleroma/workers/receiver.ex @@ -8,14 +8,14 @@ defmodule Pleroma.Workers.Receiver do # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, queue: "federator_incoming", - max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + max_attempts: 1 @impl Oban.Worker - def perform(%{"op" => "incoming_doc", "body" => doc}) do + def perform(%{"op" => "incoming_doc", "body" => doc}, _job) do Federator.perform(:incoming_doc, doc) end - def perform(%{"op" => "incoming_ap_doc", "params" => params}) do + def perform(%{"op" => "incoming_ap_doc", "params" => params}, _job) do Federator.perform(:incoming_ap_doc, params) end end diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex index a49834fd8..d9724c78a 100644 --- a/lib/pleroma/workers/scheduled_activity_worker.ex +++ b/lib/pleroma/workers/scheduled_activity_worker.ex @@ -6,10 +6,10 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, queue: "scheduled_activities", - max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + max_attempts: 1 @impl Oban.Worker - def perform(%{"op" => "execute", "activity_id" => activity_id}) do + def perform(%{"op" => "execute", "activity_id" => activity_id}, _job) do Pleroma.ScheduledActivityWorker.perform(:execute, activity_id) end end diff --git a/lib/pleroma/workers/subscriber.ex b/lib/pleroma/workers/subscriber.ex index 6af3ad0a1..783c44173 100644 --- a/lib/pleroma/workers/subscriber.ex +++ b/lib/pleroma/workers/subscriber.ex @@ -10,19 +10,19 @@ defmodule Pleroma.Workers.Subscriber do # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, queue: "federator_outgoing", - max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + max_attempts: 1 @impl Oban.Worker - def perform(%{"op" => "refresh_subscriptions"}) do + def perform(%{"op" => "refresh_subscriptions"}, _job) do Federator.perform(:refresh_subscriptions) end - def perform(%{"op" => "request_subscription", "websub_id" => websub_id}) do + def perform(%{"op" => "request_subscription", "websub_id" => websub_id}, _job) do websub = Repo.get(WebsubClientSubscription, websub_id) Federator.perform(:request_subscription, websub) end - def perform(%{"op" => "verify_websub", "websub_id" => websub_id}) do + def perform(%{"op" => "verify_websub", "websub_id" => websub_id}, _job) do websub = Repo.get(WebsubClientSubscription, websub_id) Federator.perform(:verify_websub, websub) end diff --git a/lib/pleroma/workers/transmogrifier.ex b/lib/pleroma/workers/transmogrifier.ex index c6b4fab47..e13202c06 100644 --- a/lib/pleroma/workers/transmogrifier.ex +++ b/lib/pleroma/workers/transmogrifier.ex @@ -8,10 +8,10 @@ defmodule Pleroma.Workers.Transmogrifier do # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, queue: "transmogrifier", - max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + max_attempts: 1 @impl Oban.Worker - def perform(%{"op" => "user_upgrade", "user_id" => user_id}) do + def perform(%{"op" => "user_upgrade", "user_id" => user_id}, _job) do user = User.get_by_id(user_id) Pleroma.Web.ActivityPub.Transmogrifier.perform(:user_upgrade, user) end diff --git a/lib/pleroma/workers/web_pusher.ex b/lib/pleroma/workers/web_pusher.ex index b99581eb0..7b78bb3ea 100644 --- a/lib/pleroma/workers/web_pusher.ex +++ b/lib/pleroma/workers/web_pusher.ex @@ -9,10 +9,10 @@ defmodule Pleroma.Workers.WebPusher do # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, queue: "web_push", - max_attempts: Pleroma.Config.get([:workers, :retries, :compile_time_default]) + max_attempts: 1 @impl Oban.Worker - def perform(%{"op" => "web_push", "notification_id" => notification_id}) do + def perform(%{"op" => "web_push", "notification_id" => notification_id}, _job) do notification = Repo.get(Notification, notification_id) Pleroma.Web.Push.Impl.perform(notification) end -- cgit v1.2.3 From c056736daaedb2a08557ee6c6a9bcb6bf44110ca Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 23 Aug 2019 16:11:39 +0300 Subject: [#1149] Publisher worker fix (atomized `params` keys). --- lib/pleroma/workers/publisher.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/workers/publisher.ex b/lib/pleroma/workers/publisher.ex index 0a9084589..00fae99c7 100644 --- a/lib/pleroma/workers/publisher.ex +++ b/lib/pleroma/workers/publisher.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Workers.Publisher do end def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}, _job) do + params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end) Federator.perform(:publish_one, String.to_atom(module_name), params) end end -- cgit v1.2.3 From 581123f8bb703023cb652267a1fc34292f862852 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 23 Aug 2019 18:28:23 +0300 Subject: [#1149] Introduced `quantum` job scheduler. Documentation & config changes. --- lib/pleroma/application.ex | 19 ++----------------- lib/pleroma/scheduler.ex | 7 +++++++ lib/pleroma/web/federator/federator.ex | 8 +------- 3 files changed, 10 insertions(+), 24 deletions(-) create mode 100644 lib/pleroma/scheduler.ex (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 384b03aa9..ce2d3ab59 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -31,6 +31,7 @@ defmodule Pleroma.Application do children = [ Pleroma.Repo, + Pleroma.Scheduler, Pleroma.Config.TransferTask, Pleroma.Emoji, Pleroma.Captcha, @@ -69,9 +70,7 @@ defmodule Pleroma.Application do # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # for other strategies and supported options opts = [strategy: :one_for_one, name: Pleroma.Supervisor] - result = Supervisor.start_link(children, opts) - :ok = after_supervisor_start() - result + Supervisor.start_link(children, opts) end defp setup_instrumenters do @@ -162,18 +161,4 @@ defmodule Pleroma.Application do :hackney_pool.child_spec(pool, options) end end - - defp after_supervisor_start do - with digest_config <- Application.get_env(:pleroma, :email_notifications)[:digest], - true <- digest_config[:active] do - # TODO: consider replacing with `quantum` scheduler - PleromaJobQueue.schedule( - digest_config[:schedule], - :digest_emails, - Pleroma.DigestEmailWorker - ) - end - - :ok - end end diff --git a/lib/pleroma/scheduler.ex b/lib/pleroma/scheduler.ex new file mode 100644 index 000000000..d84cd99ad --- /dev/null +++ b/lib/pleroma/scheduler.ex @@ -0,0 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Scheduler do + use Quantum.Scheduler, otp_app: :pleroma +end diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index d85fe824f..cf7e50fee 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Web.Federator do defdelegate worker_args(queue), to: Pleroma.Workers.Helper def init do - # 1 minute + # To do: consider removing this call in favor of scheduled execution (`quantum`-based) refresh_subscriptions(schedule_in: 60) end @@ -146,12 +146,6 @@ defmodule Pleroma.Web.Federator do def perform(:refresh_subscriptions) do Logger.debug("Federator running refresh subscriptions") Websub.refresh_subscriptions() - - spawn(fn -> - # 6 hours - Process.sleep(1000 * 60 * 60 * 6) - refresh_subscriptions() - end) end def ap_enabled_actor(id) do -- cgit v1.2.3 From cd78e63a2528ab813088d5e44a026f6bb05b344b Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Tue, 27 Aug 2019 14:34:37 +0300 Subject: [#1149] Bugfix: Pleroma.Workers.Subscriber / "verify_websub" works with WebsubServerSubscription. --- lib/pleroma/workers/subscriber.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/workers/subscriber.ex b/lib/pleroma/workers/subscriber.ex index 783c44173..e960b35bf 100644 --- a/lib/pleroma/workers/subscriber.ex +++ b/lib/pleroma/workers/subscriber.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Workers.Subscriber do alias Pleroma.Repo alias Pleroma.Web.Federator - alias Pleroma.Web.Websub.WebsubClientSubscription + alias Pleroma.Web.Websub # Note: `max_attempts` is intended to be overridden in `new/1` call use Oban.Worker, @@ -18,12 +18,12 @@ defmodule Pleroma.Workers.Subscriber do end def perform(%{"op" => "request_subscription", "websub_id" => websub_id}, _job) do - websub = Repo.get(WebsubClientSubscription, websub_id) + websub = Repo.get(Websub.WebsubClientSubscription, websub_id) Federator.perform(:request_subscription, websub) end def perform(%{"op" => "verify_websub", "websub_id" => websub_id}, _job) do - websub = Repo.get(WebsubClientSubscription, websub_id) + websub = Repo.get(Websub.WebsubServerSubscription, websub_id) Federator.perform(:verify_websub, websub) end end -- cgit v1.2.3 From 5e4fde1d3d49ec56fae3b199fb4af51057e2dffd Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 27 Aug 2019 20:48:16 +0300 Subject: Filter logs by date --- lib/pleroma/moderation_log.ex | 37 +++++++++++++++++++++-- lib/pleroma/user/info.ex | 4 +-- lib/pleroma/web/admin_api/admin_api_controller.ex | 8 ++++- 3 files changed, 43 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 1ef6fe67a..2164ecfc2 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -14,13 +14,46 @@ defmodule Pleroma.ModerationLog do timestamps() end - def get_all(page, page_size) do + def get_all(params) do + params + |> get_all_query() + |> maybe_filter_by_date(params) + |> Repo.all() + end + + defp maybe_filter_by_date(query, %{start_date: nil, end_date: nil}), do: query + + defp maybe_filter_by_date(query, %{start_date: start_date, end_date: nil}) do + from(q in query, + where: q.inserted_at >= ^parse_datetime(start_date) + ) + end + + defp maybe_filter_by_date(query, %{start_date: nil, end_date: end_date}) do + from(q in query, + where: q.inserted_at <= ^parse_datetime(end_date) + ) + end + + defp maybe_filter_by_date(query, %{start_date: start_date, end_date: end_date}) do + from(q in query, + where: q.inserted_at >= ^parse_datetime(start_date), + where: q.inserted_at <= ^parse_datetime(end_date) + ) + end + + defp get_all_query(%{page: page, page_size: page_size}) do from(q in __MODULE__, order_by: [desc: q.inserted_at], limit: ^page_size, offset: ^((page - 1) * page_size) ) - |> Repo.all() + end + + defp parse_datetime(datetime) do + {:ok, parsed_datetime, _} = DateTime.from_iso8601(datetime) + + parsed_datetime end def insert_log(%{ diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 779bfbc18..7027c947b 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -318,9 +318,7 @@ defmodule Pleroma.User.Info do name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255) value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255) - is_binary(name) && - is_binary(value) && - String.length(name) <= name_limit && + is_binary(name) && is_binary(value) && String.length(name) <= name_limit && String.length(value) <= value_limit end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 544b9d7d8..065394a24 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -539,7 +539,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def list_log(conn, params) do {page, page_size} = page_params(params) - log = ModerationLog.get_all(page, page_size) + log = + ModerationLog.get_all(%{ + page: page, + page_size: page_size, + start_date: params["start_date"], + end_date: params["end_date"] + }) conn |> put_view(ModerationLogView) -- cgit v1.2.3 From cef2e980b1f6b07c2bdb01030559aca83257bd7e Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 28 Aug 2019 21:32:44 +0300 Subject: division emoji.ex on loader.ex and emoji.ex --- lib/mix/tasks/pleroma/emoji.ex | 2 +- lib/pleroma/emoji.ex | 212 ++++------------------------------------- lib/pleroma/emoji/loader.ex | 204 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 197 deletions(-) create mode 100644 lib/pleroma/emoji/loader.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index c2225af7d..dc5f7c193 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -235,7 +235,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do cwd: tmp_pack_dir ) - emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts) + emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts) File.write!(files_name, Jason.encode!(emoji_map, pretty: true)) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 66e20f0e4..ab6ba7d6a 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -4,24 +4,22 @@ defmodule Pleroma.Emoji do @moduledoc """ - The emojis are loaded from: - - * emoji packs in INSTANCE-DIR/emoji - * the files: `config/emoji.txt` and `config/custom_emoji.txt` - * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder - - This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime. + This GenServer stores in an ETS table the list of the loaded emojis, + and also allows to reload the list at runtime. """ use GenServer - require Logger + alias Pleroma.Emoji.Loader - @type pattern :: Regex.t() | module() | String.t() - @type patterns :: pattern() | [pattern()] - @type group_patterns :: keyword(patterns()) + require Logger @ets __MODULE__.Ets - @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}] + @ets_options [ + :ordered_set, + :protected, + :named_table, + {:read_concurrency, true} + ] @doc false def start_link(_) do @@ -44,7 +42,7 @@ defmodule Pleroma.Emoji do end @doc "Returns all the emojos!!" - @spec get_all() :: [{String.t(), String.t()}, ...] + @spec get_all() :: list({String.t(), String.t(), String.t()}) def get_all do :ets.tab2list(@ets) end @@ -58,13 +56,13 @@ defmodule Pleroma.Emoji do @doc false def handle_cast(:reload, state) do - load() + update_emojis(Loader.load()) {:noreply, state} end @doc false def handle_call(:reload, _from, state) do - load() + update_emojis(Loader.load()) {:reply, :ok, state} end @@ -75,189 +73,11 @@ defmodule Pleroma.Emoji do @doc false def code_change(_old_vsn, state, _extra) do - load() + update_emojis(Loader.load()) {:ok, state} end - defp load do - emoji_dir_path = - Path.join( - Pleroma.Config.get!([:instance, :static_dir]), - "emoji" - ) - - emoji_groups = Pleroma.Config.get([:emoji, :groups]) - - case File.ls(emoji_dir_path) do - {:error, :enoent} -> - # The custom emoji directory doesn't exist, - # don't do anything - nil - - {:error, e} -> - # There was some other error - Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}") - - {:ok, results} -> - grouped = - Enum.group_by(results, fn file -> File.dir?(Path.join(emoji_dir_path, file)) end) - - packs = grouped[true] || [] - files = grouped[false] || [] - - # Print the packs we've found - Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}") - - if not Enum.empty?(files) do - Logger.warn( - "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{ - Enum.join(files, ", ") - }" - ) - end - - emojis = - Enum.flat_map( - packs, - fn pack -> load_pack(Path.join(emoji_dir_path, pack), emoji_groups) end - ) - - true = :ets.insert(@ets, emojis) - end - - # Compat thing for old custom emoji handling & default emoji, - # it should run even if there are no emoji packs - shortcode_globs = Pleroma.Config.get([:emoji, :shortcode_globs], []) - - emojis = - (load_from_file("config/emoji.txt", emoji_groups) ++ - load_from_file("config/custom_emoji.txt", emoji_groups) ++ - load_from_globs(shortcode_globs, emoji_groups)) - |> Enum.reject(fn value -> value == nil end) - - true = :ets.insert(@ets, emojis) - - :ok - end - - defp load_pack(pack_dir, emoji_groups) do - pack_name = Path.basename(pack_dir) - - emoji_txt = Path.join(pack_dir, "emoji.txt") - - if File.exists?(emoji_txt) do - load_from_file(emoji_txt, emoji_groups) - else - extensions = Pleroma.Config.get([:emoji, :pack_extensions]) - - Logger.info( - "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji" - ) - - make_shortcode_to_file_map(pack_dir, extensions) - |> Enum.map(fn {shortcode, rel_file} -> - filename = Path.join("/emoji/#{pack_name}", rel_file) - - {shortcode, filename, [to_string(match_extra(emoji_groups, filename))]} - end) - end - end - - def make_shortcode_to_file_map(pack_dir, exts) do - find_all_emoji(pack_dir, exts) - |> Enum.map(&Path.relative_to(&1, pack_dir)) - |> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end) - |> Enum.into(%{}) - end - - def find_all_emoji(dir, exts) do - Enum.reduce( - File.ls!(dir), - [], - fn f, acc -> - filepath = Path.join(dir, f) - - if File.dir?(filepath) do - acc ++ find_all_emoji(filepath, exts) - else - acc ++ [filepath] - end - end - ) - |> Enum.filter(fn f -> Path.extname(f) in exts end) - end - - defp load_from_file(file, emoji_groups) do - if File.exists?(file) do - load_from_file_stream(File.stream!(file), emoji_groups) - else - [] - end - end - - defp load_from_file_stream(stream, emoji_groups) do - stream - |> Stream.map(&String.trim/1) - |> Stream.map(fn line -> - case String.split(line, ~r/,\s*/) do - [name, file] -> - {name, file, [to_string(match_extra(emoji_groups, file))]} - - [name, file | tags] -> - {name, file, tags} - - _ -> - nil - end - end) - |> Enum.to_list() - end - - defp load_from_globs(globs, emoji_groups) do - static_path = Path.join(:code.priv_dir(:pleroma), "static") - - paths = - Enum.map(globs, fn glob -> - Path.join(static_path, glob) - |> Path.wildcard() - end) - |> Enum.concat() - - Enum.map(paths, fn path -> - tag = match_extra(emoji_groups, Path.join("/", Path.relative_to(path, static_path))) - shortcode = Path.basename(path, Path.extname(path)) - external_path = Path.join("/", Path.relative_to(path, static_path)) - {shortcode, external_path, [to_string(tag)]} - end) - end - - @doc """ - Finds a matching group for the given emoji filename - """ - @spec match_extra(group_patterns(), String.t()) :: atom() | nil - def match_extra(group_patterns, filename) do - match_group_patterns(group_patterns, fn pattern -> - case pattern do - %Regex{} = regex -> Regex.match?(regex, filename) - string when is_binary(string) -> filename == string - end - end) - end - - defp match_group_patterns(group_patterns, matcher) do - Enum.find_value(group_patterns, fn {group, patterns} -> - patterns = - patterns - |> List.wrap() - |> Enum.map(fn pattern -> - if String.contains?(pattern, "*") do - ~r(#{String.replace(pattern, "*", ".*")}) - else - pattern - end - end) - - Enum.any?(patterns, matcher) && group - end) + defp update_emojis(emojis) do + :ets.insert(@ets, emojis) end end diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex new file mode 100644 index 000000000..e93b0aecc --- /dev/null +++ b/lib/pleroma/emoji/loader.ex @@ -0,0 +1,204 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Emoji.Loader do + @moduledoc """ + The Loader emoji from: + + * emoji packs in INSTANCE-DIR/emoji + * the files: `config/emoji.txt` and `config/custom_emoji.txt` + * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder + """ + alias Pleroma.Config + + require Logger + + @type pattern :: Regex.t() | module() | String.t() + @type patterns :: pattern() | [pattern()] + @type group_patterns :: keyword(patterns()) + @type emoji :: {String.t(), String.t(), list(String.t())} + + @doc """ + Loads emojis from files/packs. + + returns list emojis in format: + `{"000", "/emoji/freespeechextremist.com/000.png", ["Custom"]}` + """ + @spec load() :: list(emoji) + def load do + emoji_dir_path = Path.join(Config.get!([:instance, :static_dir]), "emoji") + + emoji_groups = Config.get([:emoji, :groups]) + + emojis = + case File.ls(emoji_dir_path) do + {:error, :enoent} -> + # The custom emoji directory doesn't exist, + # don't do anything + [] + + {:error, e} -> + # There was some other error + Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}") + [] + + {:ok, results} -> + grouped = + Enum.group_by(results, fn file -> + File.dir?(Path.join(emoji_dir_path, file)) + end) + + packs = grouped[true] || [] + files = grouped[false] || [] + + # Print the packs we've found + Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}") + + if not Enum.empty?(files) do + Logger.warn( + "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{ + Enum.join(files, ", ") + }" + ) + end + + Enum.flat_map(packs, fn pack -> + load_pack(Path.join(emoji_dir_path, pack), emoji_groups) + end) + end + + # Compat thing for old custom emoji handling & default emoji, + # it should run even if there are no emoji packs + shortcode_globs = Config.get([:emoji, :shortcode_globs], []) + + emojis_txt = + (load_from_file("config/emoji.txt", emoji_groups) ++ + load_from_file("config/custom_emoji.txt", emoji_groups) ++ + load_from_globs(shortcode_globs, emoji_groups)) + |> Enum.reject(fn value -> value == nil end) + + emojis ++ emojis_txt + end + + defp load_pack(pack_dir, emoji_groups) do + pack_name = Path.basename(pack_dir) + + emoji_txt = Path.join(pack_dir, "emoji.txt") + + if File.exists?(emoji_txt) do + load_from_file(emoji_txt, emoji_groups) + else + extensions = Config.get([:emoji, :pack_extensions]) + + Logger.info( + "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji" + ) + + make_shortcode_to_file_map(pack_dir, extensions) + |> Enum.map(fn {shortcode, rel_file} -> + filename = Path.join("/emoji/#{pack_name}", rel_file) + + {shortcode, filename, [to_string(match_extra(emoji_groups, filename))]} + end) + end + end + + def make_shortcode_to_file_map(pack_dir, exts) do + find_all_emoji(pack_dir, exts) + |> Enum.map(&Path.relative_to(&1, pack_dir)) + |> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end) + |> Enum.into(%{}) + end + + def find_all_emoji(dir, exts) do + Enum.reduce( + File.ls!(dir), + [], + fn f, acc -> + filepath = Path.join(dir, f) + + if File.dir?(filepath) do + acc ++ find_all_emoji(filepath, exts) + else + acc ++ [filepath] + end + end + ) + |> Enum.filter(fn f -> Path.extname(f) in exts end) + end + + defp load_from_file(file, emoji_groups) do + if File.exists?(file) do + load_from_file_stream(File.stream!(file), emoji_groups) + else + [] + end + end + + defp load_from_file_stream(stream, emoji_groups) do + stream + |> Stream.map(&String.trim/1) + |> Stream.map(fn line -> + case String.split(line, ~r/,\s*/) do + [name, file] -> + {name, file, [to_string(match_extra(emoji_groups, file))]} + + [name, file | tags] -> + {name, file, tags} + + _ -> + nil + end + end) + |> Enum.to_list() + end + + defp load_from_globs(globs, emoji_groups) do + static_path = Path.join(:code.priv_dir(:pleroma), "static") + + paths = + Enum.map(globs, fn glob -> + Path.join(static_path, glob) + |> Path.wildcard() + end) + |> Enum.concat() + + Enum.map(paths, fn path -> + tag = match_extra(emoji_groups, Path.join("/", Path.relative_to(path, static_path))) + shortcode = Path.basename(path, Path.extname(path)) + external_path = Path.join("/", Path.relative_to(path, static_path)) + {shortcode, external_path, [to_string(tag)]} + end) + end + + @doc """ + Finds a matching group for the given emoji filename + """ + @spec match_extra(group_patterns(), String.t()) :: atom() | nil + def match_extra(group_patterns, filename) do + match_group_patterns(group_patterns, fn pattern -> + case pattern do + %Regex{} = regex -> Regex.match?(regex, filename) + string when is_binary(string) -> filename == string + end + end) + end + + defp match_group_patterns(group_patterns, matcher) do + Enum.find_value(group_patterns, fn {group, patterns} -> + patterns = + patterns + |> List.wrap() + |> Enum.map(fn pattern -> + if String.contains?(pattern, "*") do + ~r(#{String.replace(pattern, "*", ".*")}) + else + pattern + end + end) + + Enum.any?(patterns, matcher) && group + end) + end +end -- cgit v1.2.3 From d7808b5db437b3300122127cef4c7ad076de7bda Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 29 Aug 2019 06:22:18 +0300 Subject: added code\path fields without html tags in ets --- lib/pleroma/emoji/loader.ex | 12 +++++++- lib/pleroma/formatter.ex | 33 +++++++++++++--------- lib/pleroma/web/common_api/utils.ex | 2 +- .../controllers/mastodon_api_controller.ex | 2 +- .../web/twitter_api/controllers/util_controller.ex | 2 +- 5 files changed, 33 insertions(+), 18 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index e93b0aecc..70eba9ac6 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -78,7 +78,17 @@ defmodule Pleroma.Emoji.Loader do load_from_globs(shortcode_globs, emoji_groups)) |> Enum.reject(fn value -> value == nil end) - emojis ++ emojis_txt + Enum.map(emojis ++ emojis_txt, &prepare_emoji/1) + end + + defp prepare_emoji({code, file, tags} = _emoji) do + { + code, + file, + tags, + Pleroma.HTML.strip_tags(code), + Pleroma.HTML.strip_tags(file) + } end defp load_pack(pack_dir, emoji_groups) do diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 607843a5b..84955289c 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -107,19 +107,22 @@ defmodule Pleroma.Formatter do def emojify(text, nil), do: text def emojify(text, emoji, strip \\ false) do - Enum.reduce(emoji, text, fn emoji_data, text -> - emoji = HTML.strip_tags(elem(emoji_data, 0)) - file = HTML.strip_tags(elem(emoji_data, 1)) - - html = - if not strip do - "#{emoji}" - else - "" - end - - String.replace(text, ":#{emoji}:", html) |> HTML.filter_tags() + Enum.reduce(emoji, text, fn + {_, _, _, emoji, file}, text -> + String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) + + emoji_data, text -> + emoji = HTML.strip_tags(elem(emoji_data, 0)) + file = HTML.strip_tags(elem(emoji_data, 1)) + String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) end) + |> HTML.filter_tags() + end + + defp prepare_emoji_html(_emoji, _file, true), do: "" + + defp prepare_emoji_html(emoji, file, _strip) do + "#{emoji}" end def demojify(text) do @@ -130,7 +133,9 @@ defmodule Pleroma.Formatter do @doc "Outputs a list of the emoji-shortcodes in a text" def get_emoji(text) when is_binary(text) do - Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end) + Enum.filter(Emoji.get_all(), fn {emoji, _, _, _, _} -> + String.contains?(text, ":#{emoji}:") + end) end def get_emoji(_), do: [] @@ -138,7 +143,7 @@ defmodule Pleroma.Formatter do @doc "Outputs a list of the emoji-Maps in a text" def get_emoji_map(text) when is_binary(text) do get_emoji(text) - |> Enum.reduce(%{}, fn {name, file, _group}, acc -> + |> Enum.reduce(%{}, fn {name, file, _group, _, _}, acc -> Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}") end) end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 6958c7511..9686e6491 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -435,7 +435,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def emoji_from_profile(%{info: _info} = user) do (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name)) - |> Enum.map(fn {shortcode, url, _} -> + |> Enum.map(fn {shortcode, url, _, _, _} -> %{ "type" => "Emoji", "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"}, diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 83e877c0e..603c6b3c6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -331,7 +331,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do defp mastodonized_emoji do Pleroma.Emoji.get_all() - |> Enum.map(fn {shortcode, relative_url, tags} -> + |> Enum.map(fn {shortcode, relative_url, tags, _, _} -> url = to_string(URI.merge(Web.base_url(), relative_url)) %{ diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 3405bd3b7..923480242 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -240,7 +240,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def emoji(conn, _params) do emoji = Emoji.get_all() - |> Enum.map(fn {short_code, path, tags} -> + |> Enum.map(fn {short_code, path, tags, _, _} -> {short_code, %{image_url: path, tags: tags}} end) |> Enum.into(%{}) -- cgit v1.2.3 From 5c90b7073332ac333a5db9dfc82744cee03843fa Mon Sep 17 00:00:00 2001 From: Maksim Date: Thu, 29 Aug 2019 11:45:25 +0000 Subject: Apply suggestion to lib/pleroma/emoji/loader.ex --- lib/pleroma/emoji/loader.ex | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index 70eba9ac6..82fc3b8c3 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -122,19 +122,17 @@ defmodule Pleroma.Emoji.Loader do end def find_all_emoji(dir, exts) do - Enum.reduce( - File.ls!(dir), - [], - fn f, acc -> - filepath = Path.join(dir, f) - - if File.dir?(filepath) do - acc ++ find_all_emoji(filepath, exts) - else - acc ++ [filepath] - end + dir + |> File.ls!() + |> Enum.flat_map(fn f -> + filepath = Path.join(dir, f) + + if File.dir?(filepath) do + find_all_emoji(filepath, exts) + else + [filepath] end - ) + end) |> Enum.filter(fn f -> Path.extname(f) in exts end) end -- cgit v1.2.3 From d8098d142a0e8412eabdf5fe63705c25bcb1be34 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 29 Aug 2019 22:01:37 +0300 Subject: added Emoji.Formatter --- lib/pleroma/emoji/formatter.ex | 59 ++++++++++++++++++++++ lib/pleroma/formatter.ex | 52 ------------------- lib/pleroma/web/common_api/common_api.ex | 18 ++++--- lib/pleroma/web/common_api/utils.ex | 5 +- .../controllers/mastodon_api_controller.ex | 4 +- lib/pleroma/web/metadata/utils.ex | 5 +- .../web/twitter_api/twitter_api_controller.ex | 4 +- lib/pleroma/web/twitter_api/views/activity_view.ex | 6 +-- lib/pleroma/web/twitter_api/views/user_view.ex | 7 +-- 9 files changed, 87 insertions(+), 73 deletions(-) create mode 100644 lib/pleroma/emoji/formatter.ex (limited to 'lib') diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex new file mode 100644 index 000000000..acdef3988 --- /dev/null +++ b/lib/pleroma/emoji/formatter.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Emoji.Formatter do + alias Pleroma.Emoji + alias Pleroma.HTML + alias Pleroma.Web.MediaProxy + + def emojify(text) do + emojify(text, Emoji.get_all()) + end + + def emojify(text, nil), do: text + + def emojify(text, emoji, strip \\ false) do + Enum.reduce(emoji, text, fn + {_, _, _, emoji, file}, text -> + String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) + + emoji_data, text -> + emoji = HTML.strip_tags(elem(emoji_data, 0)) + file = HTML.strip_tags(elem(emoji_data, 1)) + String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) + end) + |> HTML.filter_tags() + end + + defp prepare_emoji_html(_emoji, _file, true), do: "" + + defp prepare_emoji_html(emoji, file, _strip) do + "#{emoji}" + end + + def demojify(text) do + emojify(text, Emoji.get_all(), true) + end + + def demojify(text, nil), do: text + + @doc "Outputs a list of the emoji-shortcodes in a text" + def get_emoji(text) when is_binary(text) do + Enum.filter(Emoji.get_all(), fn {emoji, _, _, _, _} -> + String.contains?(text, ":#{emoji}:") + end) + end + + def get_emoji(_), do: [] + + @doc "Outputs a list of the emoji-Maps in a text" + def get_emoji_map(text) when is_binary(text) do + get_emoji(text) + |> Enum.reduce(%{}, fn {name, file, _group, _, _}, acc -> + Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}") + end) + end + + def get_emoji_map(_), do: [] +end diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 84955289c..dbbfe3a66 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -3,10 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Formatter do - alias Pleroma.Emoji alias Pleroma.HTML alias Pleroma.User - alias Pleroma.Web.MediaProxy @safe_mention_regex ~r/^(\s*(?(@.+?\s+){1,})+)(?.*)/s @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui @@ -100,56 +98,6 @@ defmodule Pleroma.Formatter do end end - def emojify(text) do - emojify(text, Emoji.get_all()) - end - - def emojify(text, nil), do: text - - def emojify(text, emoji, strip \\ false) do - Enum.reduce(emoji, text, fn - {_, _, _, emoji, file}, text -> - String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) - - emoji_data, text -> - emoji = HTML.strip_tags(elem(emoji_data, 0)) - file = HTML.strip_tags(elem(emoji_data, 1)) - String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) - end) - |> HTML.filter_tags() - end - - defp prepare_emoji_html(_emoji, _file, true), do: "" - - defp prepare_emoji_html(emoji, file, _strip) do - "#{emoji}" - end - - def demojify(text) do - emojify(text, Emoji.get_all(), true) - end - - def demojify(text, nil), do: text - - @doc "Outputs a list of the emoji-shortcodes in a text" - def get_emoji(text) when is_binary(text) do - Enum.filter(Emoji.get_all(), fn {emoji, _, _, _, _} -> - String.contains?(text, ":#{emoji}:") - end) - end - - def get_emoji(_), do: [] - - @doc "Outputs a list of the emoji-Maps in a text" - def get_emoji_map(text) when is_binary(text) do - get_emoji(text) - |> Enum.reduce(%{}, fn {name, file, _group, _, _}, acc -> - Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}") - end) - end - - def get_emoji_map(_), do: [] - def html_escape({text, mentions, hashtags}, type) do {html_escape(text, type), mentions, hashtags} end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 5faddc9f4..9ee704022 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity alias Pleroma.ActivityExpiration alias Pleroma.Conversation.Participation - alias Pleroma.Formatter + alias Pleroma.Emoji alias Pleroma.Object alias Pleroma.ThreadMute alias Pleroma.User @@ -261,12 +261,7 @@ defmodule Pleroma.Web.CommonAPI do sensitive, poll ), - object <- - Map.put( - object, - "emoji", - Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji) - ) do + object <- put_emoji(object, full_payload, poll_emoji) do preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false direct? = visibility == "direct" @@ -300,6 +295,15 @@ defmodule Pleroma.Web.CommonAPI do end end + # parse and put emoji to object data + defp put_emoji(map, text, emojis) do + Map.put( + map, + "emoji", + Map.merge(Emoji.Formatter.get_emoji_map(text), emojis) + ) + end + # Updates the emojis for a user based on their profile def update(user) do user = diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 9686e6491..d6907f707 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Pleroma.Activity alias Pleroma.Config alias Pleroma.Conversation.Participation + alias Pleroma.Emoji alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.Plugs.AuthenticationPlug @@ -184,7 +185,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do "name" => option, "type" => "Note", "replies" => %{"type" => "Collection", "totalItems" => 0} - }, Map.merge(emoji, Formatter.get_emoji_map(option))} + }, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))} end) case expires_in do @@ -434,7 +435,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do end def emoji_from_profile(%{info: _info} = user) do - (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name)) + (Emoji.Formatter.get_emoji(user.bio) ++ Emoji.Formatter.get_emoji(user.name)) |> Enum.map(fn {shortcode, url, _, _, _} -> %{ "type" => "Emoji", diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 603c6b3c6..4f63b03cf 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -13,8 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Bookmark alias Pleroma.Config alias Pleroma.Conversation.Participation + alias Pleroma.Emoji alias Pleroma.Filter - alias Pleroma.Formatter alias Pleroma.HTTP alias Pleroma.Notification alias Pleroma.Object @@ -140,7 +140,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do user_info_emojis = user.info |> Map.get(:emoji, []) - |> Enum.concat(Formatter.get_emoji_map(emojis_text)) + |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text)) |> Enum.dedup() info_params = diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 720bd4519..382ecf426 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Utils do + alias Pleroma.Emoji alias Pleroma.Formatter alias Pleroma.HTML alias Pleroma.Web.MediaProxy @@ -13,7 +14,7 @@ defmodule Pleroma.Web.Metadata.Utils do |> HtmlEntities.decode() |> String.replace(~r//, " ") |> HTML.get_cached_stripped_html_for_activity(object, "metadata") - |> Formatter.demojify() + |> Emoji.Formatter.demojify() |> Formatter.truncate() end @@ -23,7 +24,7 @@ defmodule Pleroma.Web.Metadata.Utils do |> HtmlEntities.decode() |> String.replace(~r//, " ") |> HTML.strip_tags() - |> Formatter.demojify() + |> Emoji.Formatter.demojify() |> Formatter.truncate(max_length) end diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 5dfab6a6c..4141bfba5 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do alias Ecto.Changeset alias Pleroma.Activity - alias Pleroma.Formatter + alias Pleroma.Emoji alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo @@ -713,7 +713,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do emojis_text = (params["description"] || "") <> " " <> (params["name"] || "") emojis = - ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text)) + ((user.info.emoji || []) ++ Emoji.Formatter.get_emoji_map(emojis_text)) |> Enum.dedup() user_info = diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index abae63877..9192ebd34 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do use Pleroma.Web, :view alias Pleroma.Activity - alias Pleroma.Formatter + alias Pleroma.Emoji alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Repo @@ -262,7 +262,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do activity, "twitterapi:content" ) - |> Formatter.emojify(object.data["emoji"]) + |> Emoji.Formatter.emojify(object.data["emoji"]) text = if content do @@ -319,7 +319,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do "possibly_sensitive" => possibly_sensitive, "visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object), "summary" => summary, - "summary_html" => summary |> Formatter.emojify(object.data["emoji"]), + "summary_html" => Emoji.Formatter.emojify(summary, object.data["emoji"]), "card" => card, "muted" => thread_muted? || User.mutes?(opts[:for], user) } diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index 8a7d2fc72..3a6550826 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -4,7 +4,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do use Pleroma.Web, :view - alias Pleroma.Formatter + + alias Pleroma.Emoji alias Pleroma.HTML alias Pleroma.User alias Pleroma.Web.CommonAPI.Utils @@ -72,7 +73,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do description_html = (user.bio || "") |> HTML.filter_tags(User.html_filter_policy(for_user)) - |> Formatter.emojify(emoji) + |> Emoji.Formatter.emojify(emoji) fields = user.info @@ -99,7 +100,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do "name" => user.name || user.nickname, "name_html" => if(user.name, - do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji), + do: HTML.strip_tags(user.name) |> Emoji.Formatter.emojify(emoji), else: user.nickname ), "profile_image_url" => image, -- cgit v1.2.3 From f182f0f6bd89a2f2e3c4a6000c772512b239fe54 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Sat, 31 Aug 2019 00:57:15 +0300 Subject: Add ability to search moderation logs --- lib/pleroma/moderation_log.ex | 209 +++++++++++++++------- lib/pleroma/web/admin_api/admin_api_controller.ex | 4 +- 2 files changed, 144 insertions(+), 69 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 2164ecfc2..c72a413b6 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -18,6 +18,8 @@ defmodule Pleroma.ModerationLog do params |> get_all_query() |> maybe_filter_by_date(params) + |> maybe_filter_by_user(params) + |> maybe_filter_by_search(params) |> Repo.all() end @@ -42,6 +44,23 @@ defmodule Pleroma.ModerationLog do ) end + defp maybe_filter_by_user(query, %{user_id: nil}), do: query + + defp maybe_filter_by_user(query, %{user_id: user_id}) do + from(q in query, + where: fragment("(?)->'actor'->>'id' = ?", q.data, ^user_id) + ) + end + + defp maybe_filter_by_search(query, %{search: search}) when is_nil(search) or search == "", + do: query + + defp maybe_filter_by_search(query, %{search: search}) do + from(q in query, + where: fragment("(?)->>'message' ILIKE ?", q.data, ^"%#{search}%") + ) + end + defp get_all_query(%{page: page, page_size: page_size}) do from(q in __MODULE__, order_by: [desc: q.inserted_at], @@ -56,52 +75,71 @@ defmodule Pleroma.ModerationLog do parsed_datetime end + @spec insert_log(%{actor: User, subject: User, action: String.t(), permission: String.t()}) :: + {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, subject: %User{} = subject, action: action, permission: permission }) do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - subject: user_to_map(subject), - action: action, - permission: permission + "actor" => user_to_map(actor), + "subject" => user_to_map(subject), + "action" => action, + "permission" => permission, + "message" => "" } - }) + } + |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, subject: User, action: String.t()}) :: + {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, action: "report_update", subject: %Activity{data: %{"type" => "Flag"}} = subject }) do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - action: "report_update", - subject: report_to_map(subject) + "actor" => user_to_map(actor), + "action" => "report_update", + "subject" => report_to_map(subject), + "message" => "" } - }) + } + |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) :: + {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, action: "report_response", subject: %Activity{} = subject, text: text }) do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - action: "report_response", - subject: report_to_map(subject), - text: text + "actor" => user_to_map(actor), + "action" => "report_response", + "subject" => report_to_map(subject), + "text" => text, + "message" => "" } - }) + } + |> insert_log_entry_with_message() end + @spec insert_log(%{ + actor: User, + subject: Activity, + action: String.t(), + sensitive: String.t(), + visibility: String.t() + }) :: {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, action: "status_update", @@ -109,41 +147,49 @@ defmodule Pleroma.ModerationLog do sensitive: sensitive, visibility: visibility }) do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - action: "status_update", - subject: status_to_map(subject), - sensitive: sensitive, - visibility: visibility + "actor" => user_to_map(actor), + "action" => "status_update", + "subject" => status_to_map(subject), + "sensitive" => sensitive, + "visibility" => visibility, + "message" => "" } - }) + } + |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) :: + {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, action: "status_delete", subject_id: subject_id }) do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - action: "status_delete", - subject_id: subject_id + "actor" => user_to_map(actor), + "action" => "status_delete", + "subject_id" => subject_id, + "message" => "" } - }) + } + |> insert_log_entry_with_message() end @spec insert_log(%{actor: User, subject: User, action: String.t()}) :: {:ok, ModerationLog} | {:error, any} def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - action: action, - subject: user_to_map(subject) + "actor" => user_to_map(actor), + "action" => action, + "subject" => user_to_map(subject), + "message" => "" } - }) + } + |> insert_log_entry_with_message() end @spec insert_log(%{actor: User, subjects: [User], action: String.t()}) :: @@ -151,97 +197,124 @@ defmodule Pleroma.ModerationLog do def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do subjects = Enum.map(subjects, &user_to_map/1) - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - action: action, - subjects: subjects + "actor" => user_to_map(actor), + "action" => action, + "subjects" => subjects, + "message" => "" } - }) + } + |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) :: + {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, followed: %User{} = followed, follower: %User{} = follower, action: "follow" }) do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - action: "follow", - followed: user_to_map(followed), - follower: user_to_map(follower) + "actor" => user_to_map(actor), + "action" => "follow", + "followed" => user_to_map(followed), + "follower" => user_to_map(follower), + "message" => "" } - }) + } + |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) :: + {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, followed: %User{} = followed, follower: %User{} = follower, action: "unfollow" }) do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - action: "unfollow", - followed: user_to_map(followed), - follower: user_to_map(follower) + "actor" => user_to_map(actor), + "action" => "unfollow", + "followed" => user_to_map(followed), + "follower" => user_to_map(follower), + "message" => "" } - }) + } + |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, action: String.t(), nicknames: [String.t()], tags: [String.t()]}) :: + {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, nicknames: nicknames, tags: tags, action: action }) do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - nicknames: nicknames, - tags: tags, - action: action + "actor" => user_to_map(actor), + "nicknames" => nicknames, + "tags" => tags, + "action" => action, + "message" => "" } - }) + } + |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, action: String.t(), target: String.t()}) :: + {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, action: action, target: target }) when action in ["relay_follow", "relay_unfollow"] do - Repo.insert(%ModerationLog{ + %ModerationLog{ data: %{ - actor: user_to_map(actor), - action: action, - target: target + "actor" => user_to_map(actor), + "action" => action, + "target" => target, + "message" => "" } - }) + } + |> insert_log_entry_with_message() + end + + @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any} + + defp insert_log_entry_with_message(entry) do + entry.data["message"] + |> put_in(get_log_entry_message(entry)) + |> Repo.insert() end defp user_to_map(%User{} = user) do user |> Map.from_struct() |> Map.take([:id, :nickname]) - |> Map.put(:type, "user") + |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end) + |> Map.put("type", "user") end defp report_to_map(%Activity{} = report) do %{ - type: "report", - id: report.id, - state: report.data["state"] + "type" => "report", + "id" => report.id, + "state" => report.data["state"] } end defp status_to_map(%Activity{} = status) do %{ - type: "status", - id: status.id + "type" => "status", + "id" => status.id } end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 065394a24..135c6ae87 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -544,7 +544,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do page: page, page_size: page_size, start_date: params["start_date"], - end_date: params["end_date"] + end_date: params["end_date"], + user_id: params["user_id"], + search: params["search"] }) conn -- cgit v1.2.3 From 4d6e22bb9b718846883e92851ba22e9809b6b93d Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Sat, 31 Aug 2019 01:09:48 +0300 Subject: Style --- lib/pleroma/moderation_log.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index c72a413b6..89a5e13c3 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -248,8 +248,12 @@ defmodule Pleroma.ModerationLog do |> insert_log_entry_with_message() end - @spec insert_log(%{actor: User, action: String.t(), nicknames: [String.t()], tags: [String.t()]}) :: - {:ok, ModerationLog} | {:error, any} + @spec insert_log(%{ + actor: User, + action: String.t(), + nicknames: [String.t()], + tags: [String.t()] + }) :: {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, nicknames: nicknames, -- cgit v1.2.3 From 6ef0103ca0b194971a2e6f61685316536b742a11 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Sat, 31 Aug 2019 10:14:53 +0300 Subject: added Emoji struct --- lib/pleroma/emoji.ex | 15 +++++++++++++++ lib/pleroma/emoji/formatter.ex | 12 ++++++------ lib/pleroma/emoji/loader.ex | 13 +++---------- lib/pleroma/web/common_api/utils.ex | 2 +- .../mastodon_api/controllers/mastodon_api_controller.ex | 2 +- .../web/twitter_api/controllers/util_controller.ex | 6 ++---- 6 files changed, 28 insertions(+), 22 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index ab6ba7d6a..b246bfbe6 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -21,6 +21,21 @@ defmodule Pleroma.Emoji do {:read_concurrency, true} ] + defstruct [:code, :file, :tags, :safe_code, :safe_file] + + @doc "Build emoji struct" + def build({code, file, tags}) do + %__MODULE__{ + code: code, + file: file, + tags: tags, + safe_code: Pleroma.HTML.strip_tags(code), + safe_file: Pleroma.HTML.strip_tags(file) + } + end + + def build({code, file}), do: build({code, file, []}) + @doc false def start_link(_) do GenServer.start_link(__MODULE__, [], name: __MODULE__) diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex index acdef3988..4869d073e 100644 --- a/lib/pleroma/emoji/formatter.ex +++ b/lib/pleroma/emoji/formatter.ex @@ -15,12 +15,12 @@ defmodule Pleroma.Emoji.Formatter do def emojify(text, emoji, strip \\ false) do Enum.reduce(emoji, text, fn - {_, _, _, emoji, file}, text -> + {_, %Emoji{safe_code: emoji, safe_file: file}}, text -> String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) - emoji_data, text -> - emoji = HTML.strip_tags(elem(emoji_data, 0)) - file = HTML.strip_tags(elem(emoji_data, 1)) + {unsafe_emoji, unsafe_file}, text -> + emoji = HTML.strip_tags(unsafe_emoji) + file = HTML.strip_tags(unsafe_file) String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) end) |> HTML.filter_tags() @@ -40,7 +40,7 @@ defmodule Pleroma.Emoji.Formatter do @doc "Outputs a list of the emoji-shortcodes in a text" def get_emoji(text) when is_binary(text) do - Enum.filter(Emoji.get_all(), fn {emoji, _, _, _, _} -> + Enum.filter(Emoji.get_all(), fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end) end @@ -50,7 +50,7 @@ defmodule Pleroma.Emoji.Formatter do @doc "Outputs a list of the emoji-Maps in a text" def get_emoji_map(text) when is_binary(text) do get_emoji(text) - |> Enum.reduce(%{}, fn {name, file, _group, _, _}, acc -> + |> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc -> Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}") end) end diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index 82fc3b8c3..839316713 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -11,13 +11,14 @@ defmodule Pleroma.Emoji.Loader do * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder """ alias Pleroma.Config + alias Pleroma.Emoji require Logger @type pattern :: Regex.t() | module() | String.t() @type patterns :: pattern() | [pattern()] @type group_patterns :: keyword(patterns()) - @type emoji :: {String.t(), String.t(), list(String.t())} + @type emoji :: {String.t(), Emoji.t()} @doc """ Loads emojis from files/packs. @@ -81,15 +82,7 @@ defmodule Pleroma.Emoji.Loader do Enum.map(emojis ++ emojis_txt, &prepare_emoji/1) end - defp prepare_emoji({code, file, tags} = _emoji) do - { - code, - file, - tags, - Pleroma.HTML.strip_tags(code), - Pleroma.HTML.strip_tags(file) - } - end + defp prepare_emoji({code, _, _} = emoji), do: {code, Emoji.build(emoji)} defp load_pack(pack_dir, emoji_groups) do pack_name = Path.basename(pack_dir) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index d6907f707..1fb95f4ab 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -436,7 +436,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def emoji_from_profile(%{info: _info} = user) do (Emoji.Formatter.get_emoji(user.bio) ++ Emoji.Formatter.get_emoji(user.name)) - |> Enum.map(fn {shortcode, url, _, _, _} -> + |> Enum.map(fn {shortcode, %Emoji{file: url}} -> %{ "type" => "Emoji", "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"}, diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 4f63b03cf..a50c060bf 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -331,7 +331,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do defp mastodonized_emoji do Pleroma.Emoji.get_all() - |> Enum.map(fn {shortcode, relative_url, tags, _, _} -> + |> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} -> url = to_string(URI.merge(Web.base_url(), relative_url)) %{ diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 923480242..c14792068 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -239,11 +239,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def emoji(conn, _params) do emoji = - Emoji.get_all() - |> Enum.map(fn {short_code, path, tags, _, _} -> - {short_code, %{image_url: path, tags: tags}} + Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc -> + Map.put(acc, code, %{image_url: file, tags: tags}) end) - |> Enum.into(%{}) json(conn, emoji) end -- cgit v1.2.3 From a90ea8ba1562818b025f677ffeea35f7ca08ddf2 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 31 Aug 2019 19:08:56 +0300 Subject: [#1149] Addressed code review comments (code style, jobs pruning etc.). --- lib/pleroma/activity_expiration_worker.ex | 6 ++--- lib/pleroma/application.ex | 2 +- lib/pleroma/digest_email_worker.ex | 4 +-- lib/pleroma/emails/mailer.ex | 4 +-- lib/pleroma/scheduled_activity_worker.ex | 2 +- lib/pleroma/user.ex | 2 +- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- .../activity_pub/mrf/mediaproxy_warming_policy.ex | 2 +- lib/pleroma/web/activity_pub/publisher.ex | 2 +- lib/pleroma/web/activity_pub/transmogrifier.ex | 4 +-- lib/pleroma/web/federator/federator.ex | 8 +++--- lib/pleroma/web/federator/publisher.ex | 9 ++----- lib/pleroma/web/oauth/token/clean_worker.ex | 2 +- lib/pleroma/web/push/push.ex | 6 ++--- lib/pleroma/web/salmon/salmon.ex | 2 +- lib/pleroma/workers/activity_expiration_worker.ex | 21 ++++++++++++++++ lib/pleroma/workers/background_worker.ex | 19 +++++--------- lib/pleroma/workers/helper.ex | 13 ---------- lib/pleroma/workers/mailer.ex | 27 -------------------- lib/pleroma/workers/mailer_worker.ex | 26 +++++++++++++++++++ lib/pleroma/workers/publisher.ex | 24 ------------------ lib/pleroma/workers/publisher_worker.ex | 28 +++++++++++++++++++++ lib/pleroma/workers/receiver.ex | 21 ---------------- lib/pleroma/workers/receiver_worker.ex | 21 ++++++++++++++++ lib/pleroma/workers/scheduled_activity_worker.ex | 2 +- lib/pleroma/workers/subscriber.ex | 29 ---------------------- lib/pleroma/workers/subscriber_worker.ex | 29 ++++++++++++++++++++++ lib/pleroma/workers/transmogrifier.ex | 18 -------------- lib/pleroma/workers/transmogrifier_worker.ex | 18 ++++++++++++++ lib/pleroma/workers/web_pusher.ex | 19 -------------- lib/pleroma/workers/web_pusher_worker.ex | 19 ++++++++++++++ lib/pleroma/workers/worker_helper.ex | 23 +++++++++++++++++ 32 files changed, 218 insertions(+), 196 deletions(-) create mode 100644 lib/pleroma/workers/activity_expiration_worker.ex delete mode 100644 lib/pleroma/workers/helper.ex delete mode 100644 lib/pleroma/workers/mailer.ex create mode 100644 lib/pleroma/workers/mailer_worker.ex delete mode 100644 lib/pleroma/workers/publisher.ex create mode 100644 lib/pleroma/workers/publisher_worker.ex delete mode 100644 lib/pleroma/workers/receiver.ex create mode 100644 lib/pleroma/workers/receiver_worker.ex delete mode 100644 lib/pleroma/workers/subscriber.ex create mode 100644 lib/pleroma/workers/subscriber_worker.ex delete mode 100644 lib/pleroma/workers/transmogrifier.ex create mode 100644 lib/pleroma/workers/transmogrifier_worker.ex delete mode 100644 lib/pleroma/workers/web_pusher.ex create mode 100644 lib/pleroma/workers/web_pusher_worker.ex create mode 100644 lib/pleroma/workers/worker_helper.ex (limited to 'lib') diff --git a/lib/pleroma/activity_expiration_worker.ex b/lib/pleroma/activity_expiration_worker.ex index 5c0c53232..7aba7eece 100644 --- a/lib/pleroma/activity_expiration_worker.ex +++ b/lib/pleroma/activity_expiration_worker.ex @@ -9,13 +9,13 @@ defmodule Pleroma.ActivityExpirationWorker do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI - alias Pleroma.Workers.BackgroundWorker + alias Pleroma.Workers.ActivityExpirationWorker require Logger use GenServer import Ecto.Query - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] @schedule_interval :timer.minutes(1) @@ -57,7 +57,7 @@ defmodule Pleroma.ActivityExpirationWorker do "op" => "activity_expiration", "activity_expiration_id" => expiration.id } - |> BackgroundWorker.new(worker_args(:activity_expiration)) + |> ActivityExpirationWorker.new(worker_args(:activity_expiration)) |> Repo.insert() end) diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 7d38ed5c4..f8f866dbd 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -43,7 +43,7 @@ defmodule Pleroma.Application do hackney_pool_children() ++ [ Pleroma.Stats, - {Oban, Application.get_env(:pleroma, Oban)}, + {Oban, Pleroma.Config.get(Oban)}, %{ id: :web_push_init, start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, diff --git a/lib/pleroma/digest_email_worker.ex b/lib/pleroma/digest_email_worker.ex index ffc48bfab..4ab2a4ef4 100644 --- a/lib/pleroma/digest_email_worker.ex +++ b/lib/pleroma/digest_email_worker.ex @@ -4,11 +4,11 @@ defmodule Pleroma.DigestEmailWorker do alias Pleroma.Repo - alias Pleroma.Workers.Mailer, as: MailerWorker + alias Pleroma.Workers.MailerWorker import Ecto.Query - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] def perform do config = Pleroma.Config.get([:email_notifications, :digest]) diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index bb534f602..9cbe7313c 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Emails.Mailer do """ alias Pleroma.Repo - alias Pleroma.Workers.Mailer, as: MailerWorker + alias Pleroma.Workers.MailerWorker alias Swoosh.DeliveryError @otp_app :pleroma @@ -19,7 +19,7 @@ defmodule Pleroma.Emails.Mailer do @spec enabled?() :: boolean() def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled]) - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] @doc "add email to queue" def deliver_async(email, config \\ []) do diff --git a/lib/pleroma/scheduled_activity_worker.ex b/lib/pleroma/scheduled_activity_worker.ex index a01fb4fcb..8bf534f42 100644 --- a/lib/pleroma/scheduled_activity_worker.ex +++ b/lib/pleroma/scheduled_activity_worker.ex @@ -18,7 +18,7 @@ defmodule Pleroma.ScheduledActivityWorker do @schedule_interval :timer.minutes(1) - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] def start_link(_) do GenServer.start_link(__MODULE__, nil) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 18bba0fbb..abfa063fb 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -41,7 +41,7 @@ defmodule Pleroma.User do @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] schema "users" do field(:bio, :string) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 50279cca5..74c5eb91c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do require Logger require Pleroma.Constants - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] # For Announce activities, we filter the recipients based on following status for any actors # that match actual users. See issue #164 for more information about why this is necessary. diff --git a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex index b188164ee..178321558 100644 --- a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do recv_timeout: 10_000 ] - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] def perform(:prefetch, url) do Logger.info("Prefetching #{inspect(url)}") diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 24d101dc8..a6322e25a 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -85,7 +85,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end def publish_one(%{actor_id: actor_id} = params) do - actor = User.get_by_id(actor_id) + actor = User.get_cached_by_id(actor_id) params |> Map.delete(:actor_id) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index b068d28a7..9437f9a16 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -15,14 +15,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Federator - alias Pleroma.Workers.Transmogrifier, as: TransmogrifierWorker + alias Pleroma.Workers.TransmogrifierWorker import Ecto.Query require Logger require Pleroma.Constants - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] @doc """ Modifies an incoming AP object (mastodon format) to our internal format. diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index cf7e50fee..8f43066e3 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -12,13 +12,13 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.OStatus alias Pleroma.Web.Websub - alias Pleroma.Workers.Publisher, as: PublisherWorker - alias Pleroma.Workers.Receiver, as: ReceiverWorker - alias Pleroma.Workers.Subscriber, as: SubscriberWorker + alias Pleroma.Workers.PublisherWorker + alias Pleroma.Workers.ReceiverWorker + alias Pleroma.Workers.SubscriberWorker require Logger - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] def init do # To do: consider removing this call in favor of scheduled execution (`quantum`-based) diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex index 05d2be615..42be109ab 100644 --- a/lib/pleroma/web/federator/publisher.ex +++ b/lib/pleroma/web/federator/publisher.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.Federator.Publisher do alias Pleroma.Activity alias Pleroma.Config alias Pleroma.User - alias Pleroma.Workers.Publisher, as: PublisherWorker + alias Pleroma.Workers.PublisherWorker require Logger @@ -31,12 +31,7 @@ defmodule Pleroma.Web.Federator.Publisher do """ @spec enqueue_one(module(), Map.t()) :: :ok def enqueue_one(module, %{} = params) do - worker_args = - if max_attempts = Pleroma.Config.get([:workers, :retries, :federator_outgoing]) do - [max_attempts: max_attempts] - else - [] - end + worker_args = Pleroma.Workers.WorkerHelper.worker_args(:federator_outgoing) %{"op" => "publish_one", "module" => to_string(module), "params" => params} |> PublisherWorker.new(worker_args) diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex index 943e73289..b150a68a7 100644 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do alias Pleroma.Web.OAuth.Token alias Pleroma.Workers.BackgroundWorker - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] def start_link(_), do: GenServer.start_link(__MODULE__, %{}) diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex index b4f0e5127..4973b529c 100644 --- a/lib/pleroma/web/push/push.ex +++ b/lib/pleroma/web/push/push.ex @@ -4,11 +4,11 @@ defmodule Pleroma.Web.Push do alias Pleroma.Repo - alias Pleroma.Workers.WebPusher + alias Pleroma.Workers.WebPusherWorker require Logger - defdelegate worker_args(queue), to: Pleroma.Workers.Helper + import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] def init do unless enabled() do @@ -36,7 +36,7 @@ defmodule Pleroma.Web.Push do def send(notification) do %{"op" => "web_push", "notification_id" => notification.id} - |> WebPusher.new(worker_args(:web_push)) + |> WebPusherWorker.new(worker_args(:web_push)) |> Repo.insert() end end diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index bbaa293fd..8ba7380c0 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -171,7 +171,7 @@ defmodule Pleroma.Web.Salmon do end def publish_one(%{recipient_id: recipient_id} = params) do - recipient = User.get_by_id(recipient_id) + recipient = User.get_cached_by_id(recipient_id) params |> Map.delete(:recipient_id) diff --git a/lib/pleroma/workers/activity_expiration_worker.ex b/lib/pleroma/workers/activity_expiration_worker.ex new file mode 100644 index 000000000..0b491eabb --- /dev/null +++ b/lib/pleroma/workers/activity_expiration_worker.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.ActivityExpirationWorker do + # Note: `max_attempts` is intended to be overridden in `new/2` call + use Oban.Worker, + queue: "activity_expiration", + max_attempts: 1 + + @impl Oban.Worker + def perform( + %{ + "op" => "activity_expiration", + "activity_expiration_id" => activity_expiration_id + }, + _job + ) do + Pleroma.ActivityExpirationWorker.perform(:execute, activity_expiration_id) + end +end diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index fbce7d789..7b5575a5f 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -8,24 +8,24 @@ defmodule Pleroma.Workers.BackgroundWorker do alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy alias Pleroma.Web.OAuth.Token.CleanWorker - # Note: `max_attempts` is intended to be overridden in `new/1` call + # Note: `max_attempts` is intended to be overridden in `new/2` call use Oban.Worker, queue: "background", max_attempts: 1 @impl Oban.Worker def perform(%{"op" => "fetch_initial_posts", "user_id" => user_id}, _job) do - user = User.get_by_id(user_id) + user = User.get_cached_by_id(user_id) User.perform(:fetch_initial_posts, user) end def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do - user = User.get_by_id(user_id) + user = User.get_cached_by_id(user_id) User.perform(:deactivate_async, user, status) end def perform(%{"op" => "delete_user", "user_id" => user_id}, _job) do - user = User.get_by_id(user_id) + user = User.get_cached_by_id(user_id) User.perform(:delete, user) end @@ -37,7 +37,7 @@ defmodule Pleroma.Workers.BackgroundWorker do }, _job ) do - blocker = User.get_by_id(blocker_id) + blocker = User.get_cached_by_id(blocker_id) User.perform(:blocks_import, blocker, blocked_identifiers) end @@ -49,7 +49,7 @@ defmodule Pleroma.Workers.BackgroundWorker do }, _job ) do - follower = User.get_by_id(follower_id) + follower = User.get_cached_by_id(follower_id) User.perform(:follow_import, follower, followed_identifiers) end @@ -69,11 +69,4 @@ defmodule Pleroma.Workers.BackgroundWorker do activity = Activity.get_by_id(activity_id) Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity) end - - def perform( - %{"op" => "activity_expiration", "activity_expiration_id" => activity_expiration_id}, - _job - ) do - Pleroma.ActivityExpirationWorker.perform(:execute, activity_expiration_id) - end end diff --git a/lib/pleroma/workers/helper.ex b/lib/pleroma/workers/helper.ex deleted file mode 100644 index 3286ce0e8..000000000 --- a/lib/pleroma/workers/helper.ex +++ /dev/null @@ -1,13 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Helper do - def worker_args(queue) do - if max_attempts = Pleroma.Config.get([:workers, :retries, queue]) do - [max_attempts: max_attempts] - else - [] - end - end -end diff --git a/lib/pleroma/workers/mailer.ex b/lib/pleroma/workers/mailer.ex deleted file mode 100644 index 1cce2ea03..000000000 --- a/lib/pleroma/workers/mailer.ex +++ /dev/null @@ -1,27 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Mailer do - alias Pleroma.User - - # Note: `max_attempts` is intended to be overridden in `new/1` call - use Oban.Worker, - queue: "mailer", - max_attempts: 1 - - @impl Oban.Worker - def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}, _job) do - email = - encoded_email - |> Base.decode64!() - |> :erlang.binary_to_term() - - Pleroma.Emails.Mailer.deliver(email, config) - end - - def perform(%{"op" => "digest_email", "user_id" => user_id}, _job) do - user = User.get_by_id(user_id) - Pleroma.DigestEmailWorker.perform(user) - end -end diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex new file mode 100644 index 000000000..4f73d61bc --- /dev/null +++ b/lib/pleroma/workers/mailer_worker.ex @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.MailerWorker do + alias Pleroma.User + + # Note: `max_attempts` is intended to be overridden in `new/2` call + use Oban.Worker, + queue: "mailer", + max_attempts: 1 + + @impl Oban.Worker + def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}, _job) do + encoded_email + |> Base.decode64!() + |> :erlang.binary_to_term() + |> Pleroma.Emails.Mailer.deliver(config) + end + + def perform(%{"op" => "digest_email", "user_id" => user_id}, _job) do + user_id + |> User.get_cached_by_id() + |> Pleroma.DigestEmailWorker.perform() + end +end diff --git a/lib/pleroma/workers/publisher.ex b/lib/pleroma/workers/publisher.ex deleted file mode 100644 index 00fae99c7..000000000 --- a/lib/pleroma/workers/publisher.ex +++ /dev/null @@ -1,24 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Publisher do - alias Pleroma.Activity - alias Pleroma.Web.Federator - - # Note: `max_attempts` is intended to be overridden in `new/1` call - use Oban.Worker, - queue: "federator_outgoing", - max_attempts: 1 - - @impl Oban.Worker - def perform(%{"op" => "publish", "activity_id" => activity_id}, _job) do - activity = Activity.get_by_id(activity_id) - Federator.perform(:publish, activity) - end - - def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}, _job) do - params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end) - Federator.perform(:publish_one, String.to_atom(module_name), params) - end -end diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex new file mode 100644 index 000000000..5671d2a29 --- /dev/null +++ b/lib/pleroma/workers/publisher_worker.ex @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PublisherWorker do + alias Pleroma.Activity + alias Pleroma.Web.Federator + + # Note: `max_attempts` is intended to be overridden in `new/2` call + use Oban.Worker, + queue: "federator_outgoing", + max_attempts: 1 + + def backoff(attempt) when is_integer(attempt) do + Pleroma.Workers.WorkerHelper.sidekiq_backoff(attempt, 5) + end + + @impl Oban.Worker + def perform(%{"op" => "publish", "activity_id" => activity_id}, _job) do + activity = Activity.get_by_id(activity_id) + Federator.perform(:publish, activity) + end + + def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}, _job) do + params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end) + Federator.perform(:publish_one, String.to_atom(module_name), params) + end +end diff --git a/lib/pleroma/workers/receiver.ex b/lib/pleroma/workers/receiver.ex deleted file mode 100644 index 4ee270d74..000000000 --- a/lib/pleroma/workers/receiver.ex +++ /dev/null @@ -1,21 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Receiver do - alias Pleroma.Web.Federator - - # Note: `max_attempts` is intended to be overridden in `new/1` call - use Oban.Worker, - queue: "federator_incoming", - max_attempts: 1 - - @impl Oban.Worker - def perform(%{"op" => "incoming_doc", "body" => doc}, _job) do - Federator.perform(:incoming_doc, doc) - end - - def perform(%{"op" => "incoming_ap_doc", "params" => params}, _job) do - Federator.perform(:incoming_ap_doc, params) - end -end diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex new file mode 100644 index 000000000..cdce630f2 --- /dev/null +++ b/lib/pleroma/workers/receiver_worker.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.ReceiverWorker do + alias Pleroma.Web.Federator + + # Note: `max_attempts` is intended to be overridden in `new/2` call + use Oban.Worker, + queue: "federator_incoming", + max_attempts: 1 + + @impl Oban.Worker + def perform(%{"op" => "incoming_doc", "body" => doc}, _job) do + Federator.perform(:incoming_doc, doc) + end + + def perform(%{"op" => "incoming_ap_doc", "params" => params}, _job) do + Federator.perform(:incoming_ap_doc, params) + end +end diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex index d9724c78a..4094411ae 100644 --- a/lib/pleroma/workers/scheduled_activity_worker.ex +++ b/lib/pleroma/workers/scheduled_activity_worker.ex @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.ScheduledActivityWorker do - # Note: `max_attempts` is intended to be overridden in `new/1` call + # Note: `max_attempts` is intended to be overridden in `new/2` call use Oban.Worker, queue: "scheduled_activities", max_attempts: 1 diff --git a/lib/pleroma/workers/subscriber.ex b/lib/pleroma/workers/subscriber.ex deleted file mode 100644 index e960b35bf..000000000 --- a/lib/pleroma/workers/subscriber.ex +++ /dev/null @@ -1,29 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Subscriber do - alias Pleroma.Repo - alias Pleroma.Web.Federator - alias Pleroma.Web.Websub - - # Note: `max_attempts` is intended to be overridden in `new/1` call - use Oban.Worker, - queue: "federator_outgoing", - max_attempts: 1 - - @impl Oban.Worker - def perform(%{"op" => "refresh_subscriptions"}, _job) do - Federator.perform(:refresh_subscriptions) - end - - def perform(%{"op" => "request_subscription", "websub_id" => websub_id}, _job) do - websub = Repo.get(Websub.WebsubClientSubscription, websub_id) - Federator.perform(:request_subscription, websub) - end - - def perform(%{"op" => "verify_websub", "websub_id" => websub_id}, _job) do - websub = Repo.get(Websub.WebsubServerSubscription, websub_id) - Federator.perform(:verify_websub, websub) - end -end diff --git a/lib/pleroma/workers/subscriber_worker.ex b/lib/pleroma/workers/subscriber_worker.ex new file mode 100644 index 000000000..22d1dc956 --- /dev/null +++ b/lib/pleroma/workers/subscriber_worker.ex @@ -0,0 +1,29 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.SubscriberWorker do + alias Pleroma.Repo + alias Pleroma.Web.Federator + alias Pleroma.Web.Websub + + # Note: `max_attempts` is intended to be overridden in `new/2` call + use Oban.Worker, + queue: "federator_outgoing", + max_attempts: 1 + + @impl Oban.Worker + def perform(%{"op" => "refresh_subscriptions"}, _job) do + Federator.perform(:refresh_subscriptions) + end + + def perform(%{"op" => "request_subscription", "websub_id" => websub_id}, _job) do + websub = Repo.get(Websub.WebsubClientSubscription, websub_id) + Federator.perform(:request_subscription, websub) + end + + def perform(%{"op" => "verify_websub", "websub_id" => websub_id}, _job) do + websub = Repo.get(Websub.WebsubServerSubscription, websub_id) + Federator.perform(:verify_websub, websub) + end +end diff --git a/lib/pleroma/workers/transmogrifier.ex b/lib/pleroma/workers/transmogrifier.ex deleted file mode 100644 index e13202c06..000000000 --- a/lib/pleroma/workers/transmogrifier.ex +++ /dev/null @@ -1,18 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Transmogrifier do - alias Pleroma.User - - # Note: `max_attempts` is intended to be overridden in `new/1` call - use Oban.Worker, - queue: "transmogrifier", - max_attempts: 1 - - @impl Oban.Worker - def perform(%{"op" => "user_upgrade", "user_id" => user_id}, _job) do - user = User.get_by_id(user_id) - Pleroma.Web.ActivityPub.Transmogrifier.perform(:user_upgrade, user) - end -end diff --git a/lib/pleroma/workers/transmogrifier_worker.ex b/lib/pleroma/workers/transmogrifier_worker.ex new file mode 100644 index 000000000..6f5c1a2f2 --- /dev/null +++ b/lib/pleroma/workers/transmogrifier_worker.ex @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.TransmogrifierWorker do + alias Pleroma.User + + # Note: `max_attempts` is intended to be overridden in `new/2` call + use Oban.Worker, + queue: "transmogrifier", + max_attempts: 1 + + @impl Oban.Worker + def perform(%{"op" => "user_upgrade", "user_id" => user_id}, _job) do + user = User.get_cached_by_id(user_id) + Pleroma.Web.ActivityPub.Transmogrifier.perform(:user_upgrade, user) + end +end diff --git a/lib/pleroma/workers/web_pusher.ex b/lib/pleroma/workers/web_pusher.ex deleted file mode 100644 index 7b78bb3ea..000000000 --- a/lib/pleroma/workers/web_pusher.ex +++ /dev/null @@ -1,19 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.WebPusher do - alias Pleroma.Notification - alias Pleroma.Repo - - # Note: `max_attempts` is intended to be overridden in `new/1` call - use Oban.Worker, - queue: "web_push", - max_attempts: 1 - - @impl Oban.Worker - def perform(%{"op" => "web_push", "notification_id" => notification_id}, _job) do - notification = Repo.get(Notification, notification_id) - Pleroma.Web.Push.Impl.perform(notification) - end -end diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex new file mode 100644 index 000000000..2b1d3b99a --- /dev/null +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -0,0 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.WebPusherWorker do + alias Pleroma.Notification + alias Pleroma.Repo + + # Note: `max_attempts` is intended to be overridden in `new/2` call + use Oban.Worker, + queue: "web_push", + max_attempts: 1 + + @impl Oban.Worker + def perform(%{"op" => "web_push", "notification_id" => notification_id}, _job) do + notification = Repo.get(Notification, notification_id) + Pleroma.Web.Push.Impl.perform(notification) + end +end diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex new file mode 100644 index 000000000..f9ed2e64d --- /dev/null +++ b/lib/pleroma/workers/worker_helper.ex @@ -0,0 +1,23 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.WorkerHelper do + alias Pleroma.Config + + def worker_args(queue) do + case Config.get([:workers, :retries, queue]) do + nil -> [] + max_attempts -> [max_attempts: max_attempts] + end + end + + def sidekiq_backoff(attempt, pow \\ 4, base_backoff \\ 15) do + backoff = + :math.pow(attempt, pow) + + base_backoff + + :rand.uniform(2 * base_backoff) * attempt + + trunc(backoff) + end +end -- cgit v1.2.3 From dd017c65a4b86501c435f5cb01804300e6b7c6dd Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 31 Aug 2019 21:58:42 +0300 Subject: [#1149] Refactored Oban workers API (introduced `enqueue/3`). --- lib/pleroma/activity_expiration_worker.ex | 13 ++++------ lib/pleroma/digest_email_worker.ex | 10 +++----- lib/pleroma/emails/mailer.ex | 7 +----- lib/pleroma/scheduled_activity_worker.ex | 10 ++++---- lib/pleroma/user.ex | 28 ++++++---------------- lib/pleroma/web/activity_pub/activity_pub.ex | 6 +---- .../activity_pub/mrf/mediaproxy_warming_policy.ex | 11 ++------- lib/pleroma/web/activity_pub/transmogrifier.ex | 6 +---- lib/pleroma/web/federator/federator.ex | 26 +++++--------------- lib/pleroma/web/federator/publisher.ex | 9 ++++--- lib/pleroma/web/oauth/token/clean_worker.ex | 7 +----- lib/pleroma/web/push/push.ex | 7 +----- lib/pleroma/workers/activity_expiration_worker.ex | 2 ++ lib/pleroma/workers/background_worker.ex | 2 ++ lib/pleroma/workers/digest_emails_worker.ex | 21 ++++++++++++++++ lib/pleroma/workers/mailer_worker.ex | 10 ++------ lib/pleroma/workers/publisher_worker.ex | 2 ++ lib/pleroma/workers/receiver_worker.ex | 2 ++ lib/pleroma/workers/scheduled_activity_worker.ex | 2 ++ lib/pleroma/workers/subscriber_worker.ex | 2 ++ lib/pleroma/workers/transmogrifier_worker.ex | 2 ++ lib/pleroma/workers/web_pusher_worker.ex | 2 ++ lib/pleroma/workers/worker_helper.ex | 18 ++++++++++++++ 23 files changed, 92 insertions(+), 113 deletions(-) create mode 100644 lib/pleroma/workers/digest_emails_worker.ex (limited to 'lib') diff --git a/lib/pleroma/activity_expiration_worker.ex b/lib/pleroma/activity_expiration_worker.ex index 7aba7eece..c0820c202 100644 --- a/lib/pleroma/activity_expiration_worker.ex +++ b/lib/pleroma/activity_expiration_worker.ex @@ -9,14 +9,11 @@ defmodule Pleroma.ActivityExpirationWorker do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI - alias Pleroma.Workers.ActivityExpirationWorker require Logger use GenServer import Ecto.Query - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - @schedule_interval :timer.minutes(1) def start_link(_) do @@ -53,12 +50,10 @@ defmodule Pleroma.ActivityExpirationWorker do def handle_info(:perform, state) do ActivityExpiration.due_expirations(@schedule_interval) |> Enum.each(fn expiration -> - %{ - "op" => "activity_expiration", - "activity_expiration_id" => expiration.id - } - |> ActivityExpirationWorker.new(worker_args(:activity_expiration)) - |> Repo.insert() + Pleroma.Workers.ActivityExpirationWorker.enqueue( + "activity_expiration", + %{"activity_expiration_id" => expiration.id} + ) end) schedule_next() diff --git a/lib/pleroma/digest_email_worker.ex b/lib/pleroma/digest_email_worker.ex index 4ab2a4ef4..5be7cf26b 100644 --- a/lib/pleroma/digest_email_worker.ex +++ b/lib/pleroma/digest_email_worker.ex @@ -4,12 +4,10 @@ defmodule Pleroma.DigestEmailWorker do alias Pleroma.Repo - alias Pleroma.Workers.MailerWorker + alias Pleroma.Workers.DigestEmailsWorker import Ecto.Query - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - def perform do config = Pleroma.Config.get([:email_notifications, :digest]) negative_interval = -Map.fetch!(config, :interval) @@ -23,11 +21,9 @@ defmodule Pleroma.DigestEmailWorker do where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"), select: u ) - |> Pleroma.Repo.all() + |> Repo.all() |> Enum.each(fn user -> - %{"op" => "digest_email", "user_id" => user.id} - |> MailerWorker.new([queue: "digest_emails"] ++ worker_args(:digest_emails)) - |> Repo.insert() + DigestEmailsWorker.enqueue("digest_email", %{"user_id" => user.id}) end) end diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 9cbe7313c..eb96f2e8b 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Emails.Mailer do The module contains functions to delivery email using Swoosh.Mailer. """ - alias Pleroma.Repo alias Pleroma.Workers.MailerWorker alias Swoosh.DeliveryError @@ -19,8 +18,6 @@ defmodule Pleroma.Emails.Mailer do @spec enabled?() :: boolean() def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled]) - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - @doc "add email to queue" def deliver_async(email, config \\ []) do encoded_email = @@ -28,9 +25,7 @@ defmodule Pleroma.Emails.Mailer do |> :erlang.term_to_binary() |> Base.encode64() - %{"op" => "email", "encoded_email" => encoded_email, "config" => config} - |> MailerWorker.new(worker_args(:mailer)) - |> Repo.insert() + MailerWorker.enqueue("email", %{"encoded_email" => encoded_email, "config" => config}) end @doc "callback to perform send email from queue" diff --git a/lib/pleroma/scheduled_activity_worker.ex b/lib/pleroma/scheduled_activity_worker.ex index 8bf534f42..c41a542de 100644 --- a/lib/pleroma/scheduled_activity_worker.ex +++ b/lib/pleroma/scheduled_activity_worker.ex @@ -8,7 +8,6 @@ defmodule Pleroma.ScheduledActivityWorker do """ alias Pleroma.Config - alias Pleroma.Repo alias Pleroma.ScheduledActivity alias Pleroma.User alias Pleroma.Web.CommonAPI @@ -18,8 +17,6 @@ defmodule Pleroma.ScheduledActivityWorker do @schedule_interval :timer.minutes(1) - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - def start_link(_) do GenServer.start_link(__MODULE__, nil) end @@ -49,9 +46,10 @@ defmodule Pleroma.ScheduledActivityWorker do def handle_info(:perform, state) do ScheduledActivity.due_activities(@schedule_interval) |> Enum.each(fn scheduled_activity -> - %{"op" => "execute", "activity_id" => scheduled_activity.id} - |> Pleroma.Workers.ScheduledActivityWorker.new(worker_args(:scheduled_activities)) - |> Repo.insert() + Pleroma.Workers.ScheduledActivityWorker.enqueue( + "execute", + %{"activity_id" => scheduled_activity.id} + ) end) schedule_next() diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index abfa063fb..2fe7e1748 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -41,8 +41,6 @@ defmodule Pleroma.User do @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - schema "users" do field(:bio, :string) field(:email, :string) @@ -623,9 +621,7 @@ defmodule Pleroma.User do @doc "Fetch some posts when the user has just been federated with" def fetch_initial_posts(user) do - %{"op" => "fetch_initial_posts", "user_id" => user.id} - |> BackgroundWorker.new(worker_args(:background)) - |> Repo.insert() + BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id}) end @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() @@ -1056,9 +1052,7 @@ defmodule Pleroma.User do end def deactivate_async(user, status \\ true) do - %{"op" => "deactivate_user", "user_id" => user.id, "status" => status} - |> BackgroundWorker.new(worker_args(:background)) - |> Repo.insert() + BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status}) end def deactivate(%User{} = user, status \\ true) do @@ -1087,9 +1081,7 @@ defmodule Pleroma.User do end def delete(%User{} = user) do - %{"op" => "delete_user", "user_id" => user.id} - |> BackgroundWorker.new(worker_args(:background)) - |> Repo.insert() + BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end @spec perform(atom(), User.t()) :: {:ok, User.t()} @@ -1198,24 +1190,18 @@ defmodule Pleroma.User do end def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do - %{ - "op" => "blocks_import", + BackgroundWorker.enqueue("blocks_import", %{ "blocker_id" => blocker.id, "blocked_identifiers" => blocked_identifiers - } - |> BackgroundWorker.new(worker_args(:background)) - |> Repo.insert() + }) end def follow_import(%User{} = follower, followed_identifiers) when is_list(followed_identifiers) do - %{ - "op" => "follow_import", + BackgroundWorker.enqueue("follow_import", %{ "follower_id" => follower.id, "followed_identifiers" => followed_identifiers - } - |> BackgroundWorker.new(worker_args(:background)) - |> Repo.insert() + }) end def delete_user_activities(%User{ap_id: ap_id} = user) do diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 74c5eb91c..90b409606 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -26,8 +26,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do require Logger require Pleroma.Constants - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - # For Announce activities, we filter the recipients based on following status for any actors # that match actual users. See issue #164 for more information about why this is necessary. defp get_recipients(%{"type" => "Announce"} = data) do @@ -148,9 +146,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do activity end - %{"op" => "fetch_data_for_activity", "activity_id" => activity.id} - |> BackgroundWorker.new(worker_args(:background)) - |> Repo.insert() + BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id}) Notification.create_notifications(activity) diff --git a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex index 178321558..26b8539fe 100644 --- a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do @behaviour Pleroma.Web.ActivityPub.MRF alias Pleroma.HTTP - alias Pleroma.Repo alias Pleroma.Web.MediaProxy alias Pleroma.Workers.BackgroundWorker @@ -18,8 +17,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do recv_timeout: 10_000 ] - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - def perform(:prefetch, url) do Logger.info("Prefetching #{inspect(url)}") @@ -34,9 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do url |> Enum.each(fn %{"href" => href} -> - %{"op" => "media_proxy_prefetch", "url" => href} - |> BackgroundWorker.new(worker_args(:background)) - |> Repo.insert() + BackgroundWorker.enqueue("media_proxy_prefetch", %{"url" => href}) x -> Logger.debug("Unhandled attachment URL object #{inspect(x)}") @@ -52,9 +47,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do %{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message ) when is_list(attachments) and length(attachments) > 0 do - %{"op" => "media_proxy_preload", "message" => message} - |> BackgroundWorker.new(worker_args(:background)) - |> Repo.insert() + BackgroundWorker.enqueue("media_proxy_preload", %{"message" => message}) {:ok, message} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 9437f9a16..f27455e8b 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -22,8 +22,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do require Logger require Pleroma.Constants - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - @doc """ Modifies an incoming AP object (mastodon format) to our internal format. """ @@ -1054,9 +1052,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do already_ap <- User.ap_enabled?(user), {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do unless already_ap do - %{"op" => "user_upgrade", "user_id" => user.id} - |> TransmogrifierWorker.new(worker_args(:transmogrifier)) - |> Repo.insert() + TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id}) end {:ok, user} diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 8f43066e3..1a2da014a 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -18,8 +18,6 @@ defmodule Pleroma.Web.Federator do require Logger - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - def init do # To do: consider removing this call in favor of scheduled execution (`quantum`-based) refresh_subscriptions(schedule_in: 60) @@ -40,15 +38,11 @@ defmodule Pleroma.Web.Federator do # Client API def incoming_doc(doc) do - %{"op" => "incoming_doc", "body" => doc} - |> ReceiverWorker.new(worker_args(:federator_incoming)) - |> Pleroma.Repo.insert() + ReceiverWorker.enqueue("incoming_doc", %{"body" => doc}) end def incoming_ap_doc(params) do - %{"op" => "incoming_ap_doc", "params" => params} - |> ReceiverWorker.new(worker_args(:federator_incoming)) - |> Pleroma.Repo.insert() + ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}) end def publish(%{id: "pleroma:fakeid"} = activity) do @@ -56,27 +50,19 @@ defmodule Pleroma.Web.Federator do end def publish(activity) do - %{"op" => "publish", "activity_id" => activity.id} - |> PublisherWorker.new(worker_args(:federator_outgoing)) - |> Pleroma.Repo.insert() + PublisherWorker.enqueue("publish", %{"activity_id" => activity.id}) end def verify_websub(websub) do - %{"op" => "verify_websub", "websub_id" => websub.id} - |> SubscriberWorker.new(worker_args(:federator_outgoing)) - |> Pleroma.Repo.insert() + SubscriberWorker.enqueue("verify_websub", %{"websub_id" => websub.id}) end def request_subscription(websub) do - %{"op" => "request_subscription", "websub_id" => websub.id} - |> SubscriberWorker.new(worker_args(:federator_outgoing)) - |> Pleroma.Repo.insert() + SubscriberWorker.enqueue("request_subscription", %{"websub_id" => websub.id}) end def refresh_subscriptions(worker_args \\ []) do - %{"op" => "refresh_subscriptions"} - |> SubscriberWorker.new(worker_args ++ [max_attempts: 1] ++ worker_args(:federator_outgoing)) - |> Pleroma.Repo.insert() + SubscriberWorker.enqueue("refresh_subscriptions", %{}, worker_args ++ [max_attempts: 1]) end # Job Worker Callbacks diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex index 42be109ab..937064638 100644 --- a/lib/pleroma/web/federator/publisher.ex +++ b/lib/pleroma/web/federator/publisher.ex @@ -31,11 +31,10 @@ defmodule Pleroma.Web.Federator.Publisher do """ @spec enqueue_one(module(), Map.t()) :: :ok def enqueue_one(module, %{} = params) do - worker_args = Pleroma.Workers.WorkerHelper.worker_args(:federator_outgoing) - - %{"op" => "publish_one", "module" => to_string(module), "params" => params} - |> PublisherWorker.new(worker_args) - |> Pleroma.Repo.insert() + PublisherWorker.enqueue( + "publish_one", + %{"module" => to_string(module), "params" => params} + ) end @doc """ diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex index b150a68a7..eb94bf86f 100644 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -16,12 +16,9 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do @one_day ) - alias Pleroma.Repo alias Pleroma.Web.OAuth.Token alias Pleroma.Workers.BackgroundWorker - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - def start_link(_), do: GenServer.start_link(__MODULE__, %{}) def init(_) do @@ -31,9 +28,7 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do @doc false def handle_info(:perform, state) do - %{"op" => "clean_expired_tokens"} - |> BackgroundWorker.new(worker_args(:background)) - |> Repo.insert() + BackgroundWorker.enqueue("clean_expired_tokens", %{}) Process.send_after(self(), :perform, @interval) {:noreply, state} diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex index 4973b529c..7ef1532ac 100644 --- a/lib/pleroma/web/push/push.ex +++ b/lib/pleroma/web/push/push.ex @@ -3,13 +3,10 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Push do - alias Pleroma.Repo alias Pleroma.Workers.WebPusherWorker require Logger - import Pleroma.Workers.WorkerHelper, only: [worker_args: 1] - def init do unless enabled() do Logger.warn(""" @@ -35,8 +32,6 @@ defmodule Pleroma.Web.Push do end def send(notification) do - %{"op" => "web_push", "notification_id" => notification.id} - |> WebPusherWorker.new(worker_args(:web_push)) - |> Repo.insert() + WebPusherWorker.enqueue("web_push", %{"notification_id" => notification.id}) end end diff --git a/lib/pleroma/workers/activity_expiration_worker.ex b/lib/pleroma/workers/activity_expiration_worker.ex index 0b491eabb..60dd3feba 100644 --- a/lib/pleroma/workers/activity_expiration_worker.ex +++ b/lib/pleroma/workers/activity_expiration_worker.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Workers.ActivityExpirationWorker do queue: "activity_expiration", max_attempts: 1 + use Pleroma.Workers.WorkerHelper, queue: "activity_expiration" + @impl Oban.Worker def perform( %{ diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 7b5575a5f..b9aef3a92 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -13,6 +13,8 @@ defmodule Pleroma.Workers.BackgroundWorker do queue: "background", max_attempts: 1 + use Pleroma.Workers.WorkerHelper, queue: "background" + @impl Oban.Worker def perform(%{"op" => "fetch_initial_posts", "user_id" => user_id}, _job) do user = User.get_cached_by_id(user_id) diff --git a/lib/pleroma/workers/digest_emails_worker.ex b/lib/pleroma/workers/digest_emails_worker.ex new file mode 100644 index 000000000..ca073ce67 --- /dev/null +++ b/lib/pleroma/workers/digest_emails_worker.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.DigestEmailsWorker do + alias Pleroma.User + + # Note: `max_attempts` is intended to be overridden in `new/2` call + use Oban.Worker, + queue: "digest_emails", + max_attempts: 1 + + use Pleroma.Workers.WorkerHelper, queue: "digest_emails" + + @impl Oban.Worker + def perform(%{"op" => "digest_email", "user_id" => user_id}, _job) do + user_id + |> User.get_cached_by_id() + |> Pleroma.DigestEmailWorker.perform() + end +end diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex index 4f73d61bc..a4bd54a6c 100644 --- a/lib/pleroma/workers/mailer_worker.ex +++ b/lib/pleroma/workers/mailer_worker.ex @@ -3,13 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.MailerWorker do - alias Pleroma.User - # Note: `max_attempts` is intended to be overridden in `new/2` call use Oban.Worker, queue: "mailer", max_attempts: 1 + use Pleroma.Workers.WorkerHelper, queue: "mailer" + @impl Oban.Worker def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}, _job) do encoded_email @@ -17,10 +17,4 @@ defmodule Pleroma.Workers.MailerWorker do |> :erlang.binary_to_term() |> Pleroma.Emails.Mailer.deliver(config) end - - def perform(%{"op" => "digest_email", "user_id" => user_id}, _job) do - user_id - |> User.get_cached_by_id() - |> Pleroma.DigestEmailWorker.perform() - end end diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex index 5671d2a29..a3ac22635 100644 --- a/lib/pleroma/workers/publisher_worker.ex +++ b/lib/pleroma/workers/publisher_worker.ex @@ -11,6 +11,8 @@ defmodule Pleroma.Workers.PublisherWorker do queue: "federator_outgoing", max_attempts: 1 + use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing" + def backoff(attempt) when is_integer(attempt) do Pleroma.Workers.WorkerHelper.sidekiq_backoff(attempt, 5) end diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index cdce630f2..3cc415ce4 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -10,6 +10,8 @@ defmodule Pleroma.Workers.ReceiverWorker do queue: "federator_incoming", max_attempts: 1 + use Pleroma.Workers.WorkerHelper, queue: "federator_incoming" + @impl Oban.Worker def perform(%{"op" => "incoming_doc", "body" => doc}, _job) do Federator.perform(:incoming_doc, doc) diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex index 4094411ae..936bb64d3 100644 --- a/lib/pleroma/workers/scheduled_activity_worker.ex +++ b/lib/pleroma/workers/scheduled_activity_worker.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do queue: "scheduled_activities", max_attempts: 1 + use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities" + @impl Oban.Worker def perform(%{"op" => "execute", "activity_id" => activity_id}, _job) do Pleroma.ScheduledActivityWorker.perform(:execute, activity_id) diff --git a/lib/pleroma/workers/subscriber_worker.ex b/lib/pleroma/workers/subscriber_worker.ex index 22d1dc956..4fb994554 100644 --- a/lib/pleroma/workers/subscriber_worker.ex +++ b/lib/pleroma/workers/subscriber_worker.ex @@ -12,6 +12,8 @@ defmodule Pleroma.Workers.SubscriberWorker do queue: "federator_outgoing", max_attempts: 1 + use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing" + @impl Oban.Worker def perform(%{"op" => "refresh_subscriptions"}, _job) do Federator.perform(:refresh_subscriptions) diff --git a/lib/pleroma/workers/transmogrifier_worker.ex b/lib/pleroma/workers/transmogrifier_worker.ex index 6f5c1a2f2..6fecc2bf9 100644 --- a/lib/pleroma/workers/transmogrifier_worker.ex +++ b/lib/pleroma/workers/transmogrifier_worker.ex @@ -10,6 +10,8 @@ defmodule Pleroma.Workers.TransmogrifierWorker do queue: "transmogrifier", max_attempts: 1 + use Pleroma.Workers.WorkerHelper, queue: "transmogrifier" + @impl Oban.Worker def perform(%{"op" => "user_upgrade", "user_id" => user_id}, _job) do user = User.get_cached_by_id(user_id) diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index 2b1d3b99a..4c2591a5c 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -11,6 +11,8 @@ defmodule Pleroma.Workers.WebPusherWorker do queue: "web_push", max_attempts: 1 + use Pleroma.Workers.WorkerHelper, queue: "web_push" + @impl Oban.Worker def perform(%{"op" => "web_push", "notification_id" => notification_id}, _job) do notification = Repo.get(Notification, notification_id) diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex index f9ed2e64d..b12f198d4 100644 --- a/lib/pleroma/workers/worker_helper.ex +++ b/lib/pleroma/workers/worker_helper.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Workers.WorkerHelper do alias Pleroma.Config + alias Pleroma.Workers.WorkerHelper def worker_args(queue) do case Config.get([:workers, :retries, queue]) do @@ -20,4 +21,21 @@ defmodule Pleroma.Workers.WorkerHelper do trunc(backoff) end + + defmacro __using__(opts) do + caller_module = __CALLER__.module + queue = Keyword.fetch!(opts, :queue) + + quote do + def enqueue(op, params, worker_args \\ []) do + params = Map.merge(%{"op" => op}, params) + queue_atom = String.to_atom(unquote(queue)) + worker_args = worker_args ++ WorkerHelper.worker_args(queue_atom) + + unquote(caller_module) + |> apply(:new, [params, worker_args]) + |> Pleroma.Repo.insert() + end + end + end end -- cgit v1.2.3 From 9c96b17e16a4911d3e20149e1b54b12baaf71617 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Sun, 1 Sep 2019 21:23:30 +0300 Subject: Add pagination to logs --- lib/pleroma/moderation_log.ex | 29 +++++++++++++++------- .../web/admin_api/views/moderation_log_view.ex | 5 +++- 2 files changed, 24 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 89a5e13c3..352cad433 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -15,12 +15,18 @@ defmodule Pleroma.ModerationLog do end def get_all(params) do - params - |> get_all_query() - |> maybe_filter_by_date(params) - |> maybe_filter_by_user(params) - |> maybe_filter_by_search(params) - |> Repo.all() + base_query = + get_all_query() + |> maybe_filter_by_date(params) + |> maybe_filter_by_user(params) + |> maybe_filter_by_search(params) + + query_with_pagination = base_query |> paginate_query(params) + + %{ + items: Repo.all(query_with_pagination), + count: Repo.aggregate(base_query, :count, :id) + } end defp maybe_filter_by_date(query, %{start_date: nil, end_date: nil}), do: query @@ -61,14 +67,19 @@ defmodule Pleroma.ModerationLog do ) end - defp get_all_query(%{page: page, page_size: page_size}) do - from(q in __MODULE__, - order_by: [desc: q.inserted_at], + defp paginate_query(query, %{page: page, page_size: page_size}) do + from(q in query, limit: ^page_size, offset: ^((page - 1) * page_size) ) end + defp get_all_query do + from(q in __MODULE__, + order_by: [desc: q.inserted_at] + ) + end + defp parse_datetime(datetime) do {:ok, parsed_datetime, _} = DateTime.from_iso8601(datetime) diff --git a/lib/pleroma/web/admin_api/views/moderation_log_view.ex b/lib/pleroma/web/admin_api/views/moderation_log_view.ex index b3fc7cfe5..e7752d1f3 100644 --- a/lib/pleroma/web/admin_api/views/moderation_log_view.ex +++ b/lib/pleroma/web/admin_api/views/moderation_log_view.ex @@ -8,7 +8,10 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogView do alias Pleroma.ModerationLog def render("index.json", %{log: log}) do - render_many(log, __MODULE__, "show.json", as: :log_entry) + %{ + items: render_many(log.items, __MODULE__, "show.json", as: :log_entry), + total: log.count + } end def render("show.json", %{log_entry: log_entry}) do -- cgit v1.2.3 From 35ef470d000c53e21c6f867d53ca3a83260d93b8 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Mon, 2 Sep 2019 12:15:21 +0100 Subject: truncate fields for remote users instead --- lib/pleroma/user/info.ex | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 779bfbc18..0beb2f721 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -242,6 +242,7 @@ defmodule Pleroma.User.Info do end def remote_user_creation(info, params) do + params = Map.put(params, "fields", Enum.map(params["fields"], &truncate_field/1)) info |> cast(params, [ :ap_enabled, @@ -326,6 +327,12 @@ defmodule Pleroma.User.Info do defp valid_field?(_), do: false + defp truncate_field(%{"name" => name, "value" => value}) do + {name, _chopped} = String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255)) + {value, _chopped} = String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255)) + %{"name" => name, "value" => value} + end + @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t() def confirmation_changeset(info, opts) do need_confirmation? = Keyword.get(opts, :need_confirmation) -- cgit v1.2.3 From 05c935c3961e4c1a20c7713611920318d45d4b57 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Mon, 2 Sep 2019 12:15:40 +0100 Subject: mix format --- lib/pleroma/user/info.ex | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 0beb2f721..ca1282d02 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -243,6 +243,7 @@ defmodule Pleroma.User.Info do def remote_user_creation(info, params) do params = Map.put(params, "fields", Enum.map(params["fields"], &truncate_field/1)) + info |> cast(params, [ :ap_enabled, @@ -328,8 +329,12 @@ defmodule Pleroma.User.Info do defp valid_field?(_), do: false defp truncate_field(%{"name" => name, "value" => value}) do - {name, _chopped} = String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255)) - {value, _chopped} = String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255)) + {name, _chopped} = + String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255)) + + {value, _chopped} = + String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255)) + %{"name" => name, "value" => value} end -- cgit v1.2.3 From d0f07e55d28d25684130cb1090d0bdbb48807548 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Mon, 2 Sep 2019 12:31:23 +0100 Subject: use atom key for fields --- lib/pleroma/user/info.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index ca1282d02..151e025de 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -242,7 +242,12 @@ defmodule Pleroma.User.Info do end def remote_user_creation(info, params) do - params = Map.put(params, "fields", Enum.map(params["fields"], &truncate_field/1)) + params = + if Map.has_key?(params, :fields) do + Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1)) + else + params + end info |> cast(params, [ -- cgit v1.2.3 From b49085c156a6a4449c95c2c315f6250317122735 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 2 Sep 2019 14:57:40 +0300 Subject: [#1149] Refactoring: GenServer workers renamed to daemons, `use Oban.Worker` moved to helper. --- lib/pleroma/activity_expiration_worker.ex | 66 ----------------------- lib/pleroma/application.ex | 4 +- lib/pleroma/daemons/activity_expiration_daemon.ex | 66 +++++++++++++++++++++++ lib/pleroma/daemons/digest_email_daemon.ex | 42 +++++++++++++++ lib/pleroma/daemons/scheduled_activity_daemon.ex | 62 +++++++++++++++++++++ lib/pleroma/digest_email_worker.ex | 42 --------------- lib/pleroma/scheduled_activity_worker.ex | 62 --------------------- lib/pleroma/workers/activity_expiration_worker.ex | 7 +-- lib/pleroma/workers/background_worker.ex | 5 -- lib/pleroma/workers/digest_emails_worker.ex | 7 +-- lib/pleroma/workers/mailer_worker.ex | 5 -- lib/pleroma/workers/publisher_worker.ex | 5 -- lib/pleroma/workers/receiver_worker.ex | 5 -- lib/pleroma/workers/scheduled_activity_worker.ex | 7 +-- lib/pleroma/workers/subscriber_worker.ex | 5 -- lib/pleroma/workers/transmogrifier_worker.ex | 5 -- lib/pleroma/workers/web_pusher_worker.ex | 5 -- lib/pleroma/workers/worker_helper.ex | 5 ++ 18 files changed, 180 insertions(+), 225 deletions(-) delete mode 100644 lib/pleroma/activity_expiration_worker.ex create mode 100644 lib/pleroma/daemons/activity_expiration_daemon.ex create mode 100644 lib/pleroma/daemons/digest_email_daemon.ex create mode 100644 lib/pleroma/daemons/scheduled_activity_daemon.ex delete mode 100644 lib/pleroma/digest_email_worker.ex delete mode 100644 lib/pleroma/scheduled_activity_worker.ex (limited to 'lib') diff --git a/lib/pleroma/activity_expiration_worker.ex b/lib/pleroma/activity_expiration_worker.ex deleted file mode 100644 index c0820c202..000000000 --- a/lib/pleroma/activity_expiration_worker.ex +++ /dev/null @@ -1,66 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ActivityExpirationWorker do - alias Pleroma.Activity - alias Pleroma.ActivityExpiration - alias Pleroma.Config - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.CommonAPI - - require Logger - use GenServer - import Ecto.Query - - @schedule_interval :timer.minutes(1) - - def start_link(_) do - GenServer.start_link(__MODULE__, nil) - end - - @impl true - def init(_) do - if Config.get([ActivityExpiration, :enabled]) do - schedule_next() - {:ok, nil} - else - :ignore - end - end - - def perform(:execute, expiration_id) do - try do - expiration = - ActivityExpiration - |> where([e], e.id == ^expiration_id) - |> Repo.one!() - - activity = Activity.get_by_id_with_object(expiration.activity_id) - user = User.get_by_ap_id(activity.object.data["actor"]) - CommonAPI.delete(activity.id, user) - rescue - error -> - Logger.error("#{__MODULE__} Couldn't delete expired activity: #{inspect(error)}") - end - end - - @impl true - def handle_info(:perform, state) do - ActivityExpiration.due_expirations(@schedule_interval) - |> Enum.each(fn expiration -> - Pleroma.Workers.ActivityExpirationWorker.enqueue( - "activity_expiration", - %{"activity_expiration_id" => expiration.id} - ) - end) - - schedule_next() - {:noreply, state} - end - - defp schedule_next do - Process.send_after(self(), :perform, @schedule_interval) - end -end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index f8f866dbd..0c27027a0 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -36,8 +36,8 @@ defmodule Pleroma.Application do Pleroma.Emoji, Pleroma.Captcha, Pleroma.FlakeId, - Pleroma.ScheduledActivityWorker, - Pleroma.ActivityExpirationWorker + Pleroma.Daemons.ScheduledActivityDaemon, + Pleroma.Daemons.ActivityExpirationDaemon ] ++ cachex_children() ++ hackney_pool_children() ++ diff --git a/lib/pleroma/daemons/activity_expiration_daemon.ex b/lib/pleroma/daemons/activity_expiration_daemon.ex new file mode 100644 index 000000000..cab7628c4 --- /dev/null +++ b/lib/pleroma/daemons/activity_expiration_daemon.ex @@ -0,0 +1,66 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Daemons.ActivityExpirationDaemon do + alias Pleroma.Activity + alias Pleroma.ActivityExpiration + alias Pleroma.Config + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + require Logger + use GenServer + import Ecto.Query + + @schedule_interval :timer.minutes(1) + + def start_link(_) do + GenServer.start_link(__MODULE__, nil) + end + + @impl true + def init(_) do + if Config.get([ActivityExpiration, :enabled]) do + schedule_next() + {:ok, nil} + else + :ignore + end + end + + def perform(:execute, expiration_id) do + try do + expiration = + ActivityExpiration + |> where([e], e.id == ^expiration_id) + |> Repo.one!() + + activity = Activity.get_by_id_with_object(expiration.activity_id) + user = User.get_by_ap_id(activity.object.data["actor"]) + CommonAPI.delete(activity.id, user) + rescue + error -> + Logger.error("#{__MODULE__} Couldn't delete expired activity: #{inspect(error)}") + end + end + + @impl true + def handle_info(:perform, state) do + ActivityExpiration.due_expirations(@schedule_interval) + |> Enum.each(fn expiration -> + Pleroma.Workers.ActivityExpirationWorker.enqueue( + "activity_expiration", + %{"activity_expiration_id" => expiration.id} + ) + end) + + schedule_next() + {:noreply, state} + end + + defp schedule_next do + Process.send_after(self(), :perform, @schedule_interval) + end +end diff --git a/lib/pleroma/daemons/digest_email_daemon.ex b/lib/pleroma/daemons/digest_email_daemon.ex new file mode 100644 index 000000000..462ad2c55 --- /dev/null +++ b/lib/pleroma/daemons/digest_email_daemon.ex @@ -0,0 +1,42 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Daemons.DigestEmailDaemon do + alias Pleroma.Repo + alias Pleroma.Workers.DigestEmailsWorker + + import Ecto.Query + + def perform do + config = Pleroma.Config.get([:email_notifications, :digest]) + negative_interval = -Map.fetch!(config, :interval) + inactivity_threshold = Map.fetch!(config, :inactivity_threshold) + inactive_users_query = Pleroma.User.list_inactive_users_query(inactivity_threshold) + + now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second) + + from(u in inactive_users_query, + where: fragment(~s(? #> '{"email_notifications","digest"}' @> 'true'), u.info), + where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"), + select: u + ) + |> Repo.all() + |> Enum.each(fn user -> + DigestEmailsWorker.enqueue("digest_email", %{"user_id" => user.id}) + end) + end + + @doc """ + Send digest email to the given user. + Updates `last_digest_emailed_at` field for the user and returns the updated user. + """ + @spec perform(Pleroma.User.t()) :: Pleroma.User.t() + def perform(user) do + with %Swoosh.Email{} = email <- Pleroma.Emails.UserEmail.digest_email(user) do + Pleroma.Emails.Mailer.deliver_async(email) + end + + Pleroma.User.touch_last_digest_emailed_at(user) + end +end diff --git a/lib/pleroma/daemons/scheduled_activity_daemon.ex b/lib/pleroma/daemons/scheduled_activity_daemon.ex new file mode 100644 index 000000000..aee5f723a --- /dev/null +++ b/lib/pleroma/daemons/scheduled_activity_daemon.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Daemons.ScheduledActivityDaemon do + @moduledoc """ + Sends scheduled activities to the job queue. + """ + + alias Pleroma.Config + alias Pleroma.ScheduledActivity + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + use GenServer + require Logger + + @schedule_interval :timer.minutes(1) + + def start_link(_) do + GenServer.start_link(__MODULE__, nil) + end + + def init(_) do + if Config.get([ScheduledActivity, :enabled]) do + schedule_next() + {:ok, nil} + else + :ignore + end + end + + def perform(:execute, scheduled_activity_id) do + try do + {:ok, scheduled_activity} = ScheduledActivity.delete(scheduled_activity_id) + %User{} = user = User.get_cached_by_id(scheduled_activity.user_id) + {:ok, _result} = CommonAPI.post(user, scheduled_activity.params) + rescue + error -> + Logger.error( + "#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}" + ) + end + end + + def handle_info(:perform, state) do + ScheduledActivity.due_activities(@schedule_interval) + |> Enum.each(fn scheduled_activity -> + Pleroma.Workers.ScheduledActivityWorker.enqueue( + "execute", + %{"activity_id" => scheduled_activity.id} + ) + end) + + schedule_next() + {:noreply, state} + end + + defp schedule_next do + Process.send_after(self(), :perform, @schedule_interval) + end +end diff --git a/lib/pleroma/digest_email_worker.ex b/lib/pleroma/digest_email_worker.ex deleted file mode 100644 index 5be7cf26b..000000000 --- a/lib/pleroma/digest_email_worker.ex +++ /dev/null @@ -1,42 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.DigestEmailWorker do - alias Pleroma.Repo - alias Pleroma.Workers.DigestEmailsWorker - - import Ecto.Query - - def perform do - config = Pleroma.Config.get([:email_notifications, :digest]) - negative_interval = -Map.fetch!(config, :interval) - inactivity_threshold = Map.fetch!(config, :inactivity_threshold) - inactive_users_query = Pleroma.User.list_inactive_users_query(inactivity_threshold) - - now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second) - - from(u in inactive_users_query, - where: fragment(~s(? #> '{"email_notifications","digest"}' @> 'true'), u.info), - where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"), - select: u - ) - |> Repo.all() - |> Enum.each(fn user -> - DigestEmailsWorker.enqueue("digest_email", %{"user_id" => user.id}) - end) - end - - @doc """ - Send digest email to the given user. - Updates `last_digest_emailed_at` field for the user and returns the updated user. - """ - @spec perform(Pleroma.User.t()) :: Pleroma.User.t() - def perform(user) do - with %Swoosh.Email{} = email <- Pleroma.Emails.UserEmail.digest_email(user) do - Pleroma.Emails.Mailer.deliver_async(email) - end - - Pleroma.User.touch_last_digest_emailed_at(user) - end -end diff --git a/lib/pleroma/scheduled_activity_worker.ex b/lib/pleroma/scheduled_activity_worker.ex deleted file mode 100644 index c41a542de..000000000 --- a/lib/pleroma/scheduled_activity_worker.ex +++ /dev/null @@ -1,62 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ScheduledActivityWorker do - @moduledoc """ - Sends scheduled activities to the job queue. - """ - - alias Pleroma.Config - alias Pleroma.ScheduledActivity - alias Pleroma.User - alias Pleroma.Web.CommonAPI - - use GenServer - require Logger - - @schedule_interval :timer.minutes(1) - - def start_link(_) do - GenServer.start_link(__MODULE__, nil) - end - - def init(_) do - if Config.get([ScheduledActivity, :enabled]) do - schedule_next() - {:ok, nil} - else - :ignore - end - end - - def perform(:execute, scheduled_activity_id) do - try do - {:ok, scheduled_activity} = ScheduledActivity.delete(scheduled_activity_id) - %User{} = user = User.get_cached_by_id(scheduled_activity.user_id) - {:ok, _result} = CommonAPI.post(user, scheduled_activity.params) - rescue - error -> - Logger.error( - "#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}" - ) - end - end - - def handle_info(:perform, state) do - ScheduledActivity.due_activities(@schedule_interval) - |> Enum.each(fn scheduled_activity -> - Pleroma.Workers.ScheduledActivityWorker.enqueue( - "execute", - %{"activity_id" => scheduled_activity.id} - ) - end) - - schedule_next() - {:noreply, state} - end - - defp schedule_next do - Process.send_after(self(), :perform, @schedule_interval) - end -end diff --git a/lib/pleroma/workers/activity_expiration_worker.ex b/lib/pleroma/workers/activity_expiration_worker.ex index 60dd3feba..4e3e4195f 100644 --- a/lib/pleroma/workers/activity_expiration_worker.ex +++ b/lib/pleroma/workers/activity_expiration_worker.ex @@ -3,11 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.ActivityExpirationWorker do - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "activity_expiration", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "activity_expiration" @impl Oban.Worker @@ -18,6 +13,6 @@ defmodule Pleroma.Workers.ActivityExpirationWorker do }, _job ) do - Pleroma.ActivityExpirationWorker.perform(:execute, activity_expiration_id) + Pleroma.Daemons.ActivityExpirationDaemon.perform(:execute, activity_expiration_id) end end diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index b9aef3a92..082f20ab7 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -8,11 +8,6 @@ defmodule Pleroma.Workers.BackgroundWorker do alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy alias Pleroma.Web.OAuth.Token.CleanWorker - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "background", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "background" @impl Oban.Worker diff --git a/lib/pleroma/workers/digest_emails_worker.ex b/lib/pleroma/workers/digest_emails_worker.ex index ca073ce67..3e5a836d0 100644 --- a/lib/pleroma/workers/digest_emails_worker.ex +++ b/lib/pleroma/workers/digest_emails_worker.ex @@ -5,17 +5,12 @@ defmodule Pleroma.Workers.DigestEmailsWorker do alias Pleroma.User - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "digest_emails", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "digest_emails" @impl Oban.Worker def perform(%{"op" => "digest_email", "user_id" => user_id}, _job) do user_id |> User.get_cached_by_id() - |> Pleroma.DigestEmailWorker.perform() + |> Pleroma.Daemons.DigestEmailDaemon.perform() end end diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex index a4bd54a6c..1b7a0eb3e 100644 --- a/lib/pleroma/workers/mailer_worker.ex +++ b/lib/pleroma/workers/mailer_worker.ex @@ -3,11 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.MailerWorker do - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "mailer", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "mailer" @impl Oban.Worker diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex index a3ac22635..455f7fc7e 100644 --- a/lib/pleroma/workers/publisher_worker.ex +++ b/lib/pleroma/workers/publisher_worker.ex @@ -6,11 +6,6 @@ defmodule Pleroma.Workers.PublisherWorker do alias Pleroma.Activity alias Pleroma.Web.Federator - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "federator_outgoing", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing" def backoff(attempt) when is_integer(attempt) do diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index 3cc415ce4..83d528a66 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -5,11 +5,6 @@ defmodule Pleroma.Workers.ReceiverWorker do alias Pleroma.Web.Federator - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "federator_incoming", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "federator_incoming" @impl Oban.Worker diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex index 936bb64d3..ca7d53af1 100644 --- a/lib/pleroma/workers/scheduled_activity_worker.ex +++ b/lib/pleroma/workers/scheduled_activity_worker.ex @@ -3,15 +3,10 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.ScheduledActivityWorker do - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "scheduled_activities", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities" @impl Oban.Worker def perform(%{"op" => "execute", "activity_id" => activity_id}, _job) do - Pleroma.ScheduledActivityWorker.perform(:execute, activity_id) + Pleroma.Daemons.ScheduledActivityDaemon.perform(:execute, activity_id) end end diff --git a/lib/pleroma/workers/subscriber_worker.ex b/lib/pleroma/workers/subscriber_worker.ex index 4fb994554..fc490e300 100644 --- a/lib/pleroma/workers/subscriber_worker.ex +++ b/lib/pleroma/workers/subscriber_worker.ex @@ -7,11 +7,6 @@ defmodule Pleroma.Workers.SubscriberWorker do alias Pleroma.Web.Federator alias Pleroma.Web.Websub - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "federator_outgoing", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing" @impl Oban.Worker diff --git a/lib/pleroma/workers/transmogrifier_worker.ex b/lib/pleroma/workers/transmogrifier_worker.ex index 6fecc2bf9..b581a2f86 100644 --- a/lib/pleroma/workers/transmogrifier_worker.ex +++ b/lib/pleroma/workers/transmogrifier_worker.ex @@ -5,11 +5,6 @@ defmodule Pleroma.Workers.TransmogrifierWorker do alias Pleroma.User - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "transmogrifier", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "transmogrifier" @impl Oban.Worker diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index 4c2591a5c..bea2baffb 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -6,11 +6,6 @@ defmodule Pleroma.Workers.WebPusherWorker do alias Pleroma.Notification alias Pleroma.Repo - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: "web_push", - max_attempts: 1 - use Pleroma.Workers.WorkerHelper, queue: "web_push" @impl Oban.Worker diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex index b12f198d4..358efa14a 100644 --- a/lib/pleroma/workers/worker_helper.ex +++ b/lib/pleroma/workers/worker_helper.ex @@ -27,6 +27,11 @@ defmodule Pleroma.Workers.WorkerHelper do queue = Keyword.fetch!(opts, :queue) quote do + # Note: `max_attempts` is intended to be overridden in `new/2` call + use Oban.Worker, + queue: unquote(queue), + max_attempts: 1 + def enqueue(op, params, worker_args \\ []) do params = Map.merge(%{"op" => op}, params) queue_atom = String.to_atom(unquote(queue)) -- cgit v1.2.3 From 8cbad5500cefbba1e0bb67604960fc331b75b498 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 4 Sep 2019 15:25:12 +0300 Subject: add tests for activity_pub/utils.ex --- lib/pleroma/user.ex | 1 + lib/pleroma/web/activity_pub/activity_pub.ex | 12 +- lib/pleroma/web/activity_pub/utils.ex | 300 +++++++++++++-------------- 3 files changed, 144 insertions(+), 169 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 29fd6d2ea..424ed772f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -147,6 +147,7 @@ defmodule Pleroma.User do Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end) end + @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()} def set_follow_state_cache(user_ap_id, target_ap_id, state) do Cachex.put( :user_cache, diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index eeb826814..39b46a595 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -435,6 +435,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + @spec block(User.t(), User.t(), String.t() | nil, boolean) :: {:ok, Activity.t() | nil} def block(blocker, blocked, activity_id \\ nil, local \\ true) do outgoing_blocks = Config.get([:activitypub, :outgoing_blocks]) unfollow_blocked = Config.get([:activitypub, :unfollow_blocked]) @@ -463,10 +464,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + @spec flag(map()) :: {:ok, Activity.t()} | any def flag( %{ actor: actor, - context: context, + context: _context, account: account, statuses: statuses, content: content @@ -478,14 +480,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do additional = params[:additional] || %{} - params = %{ - actor: actor, - context: context, - account: account, - statuses: statuses, - content: content - } - additional = if forward do Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]}) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index c9c0c3763..cf82d1a9b 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -33,50 +33,40 @@ defmodule Pleroma.Web.ActivityPub.Utils do Map.put(params, "actor", get_ap_id(params["actor"])) end - def determine_explicit_mentions(%{"tag" => tag} = _object) when is_list(tag) do - tag - |> Enum.filter(fn x -> is_map(x) end) - |> Enum.filter(fn x -> x["type"] == "Mention" end) - |> Enum.map(fn x -> x["href"] end) + @spec determine_explicit_mentions(map()) :: map() + def determine_explicit_mentions(%{"tag" => tag} = _) when is_list(tag) do + Enum.flat_map(tag, fn + %{"type" => "Mention", "href" => href} -> [href] + _ -> [] + end) end def determine_explicit_mentions(%{"tag" => tag} = object) when is_map(tag) do - Map.put(object, "tag", [tag]) + object + |> Map.put("tag", [tag]) |> determine_explicit_mentions() end def determine_explicit_mentions(_), do: [] + @spec recipient_in_collection(any(), any()) :: boolean() defp recipient_in_collection(ap_id, coll) when is_binary(coll), do: ap_id == coll defp recipient_in_collection(ap_id, coll) when is_list(coll), do: ap_id in coll defp recipient_in_collection(_, _), do: false + @spec recipient_in_message(User.t(), User.t(), map()) :: boolean() def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params) do - cond do - recipient_in_collection(ap_id, params["to"]) -> - true - - recipient_in_collection(ap_id, params["cc"]) -> - true - - recipient_in_collection(ap_id, params["bto"]) -> - true - - recipient_in_collection(ap_id, params["bcc"]) -> - true + addresses = [params["to"], params["cc"], params["bto"], params["bcc"]] + cond do + Enum.any?(addresses, &recipient_in_collection(ap_id, &1)) -> true # if the message is unaddressed at all, then assume it is directly addressed # to the recipient - !params["to"] && !params["cc"] && !params["bto"] && !params["bcc"] -> - true - + Enum.all?(addresses, &is_nil(&1)) -> true # if the message is sent from somebody the user is following, then assume it # is addressed to the recipient - User.following?(recipient, actor) -> - true - - true -> - false + User.following?(recipient, actor) -> true + true -> false end end @@ -188,54 +178,59 @@ defmodule Pleroma.Web.ActivityPub.Utils do Adds an id and a published data if they aren't there, also adds it to an included object """ - def lazy_put_activity_defaults(map, fake \\ false) do - map = - unless fake do - %{data: %{"id" => context}, id: context_id} = create_context(map["context"]) + @spec lazy_put_activity_defaults(map(), boolean) :: map() + def lazy_put_activity_defaults(map, fake \\ false) - map - |> Map.put_new_lazy("id", &generate_activity_id/0) - |> Map.put_new_lazy("published", &make_date/0) - |> Map.put_new("context", context) - |> Map.put_new("context_id", context_id) - else - map - |> Map.put_new("id", "pleroma:fakeid") - |> Map.put_new_lazy("published", &make_date/0) - |> Map.put_new("context", "pleroma:fakecontext") - |> Map.put_new("context_id", -1) - end - - if is_map(map["object"]) do - object = lazy_put_object_defaults(map["object"], map, fake) - %{map | "object" => object} - else - map - end + def lazy_put_activity_defaults(map, true) do + map + |> Map.put_new("id", "pleroma:fakeid") + |> Map.put_new_lazy("published", &make_date/0) + |> Map.put_new("context", "pleroma:fakecontext") + |> Map.put_new("context_id", -1) + |> lazy_put_object_defaults(true) end - @doc """ - Adds an id and published date if they aren't there. - """ - def lazy_put_object_defaults(map, activity \\ %{}, fake) + def lazy_put_activity_defaults(map, _fake) do + %{data: %{"id" => context}, id: context_id} = create_context(map["context"]) - def lazy_put_object_defaults(map, activity, true = _fake) do map + |> Map.put_new_lazy("id", &generate_activity_id/0) |> Map.put_new_lazy("published", &make_date/0) - |> Map.put_new("id", "pleroma:fake_object_id") - |> Map.put_new("context", activity["context"]) - |> Map.put_new("fake", true) - |> Map.put_new("context_id", activity["context_id"]) + |> Map.put_new("context", context) + |> Map.put_new("context_id", context_id) + |> lazy_put_object_defaults(false) end - def lazy_put_object_defaults(map, activity, _fake) do - map - |> Map.put_new_lazy("id", &generate_object_id/0) - |> Map.put_new_lazy("published", &make_date/0) - |> Map.put_new("context", activity["context"]) - |> Map.put_new("context_id", activity["context_id"]) + # Adds an id and published date if they aren't there. + # + @spec lazy_put_object_defaults(map(), boolean()) :: map() + defp lazy_put_object_defaults(%{"object" => map} = activity, true) + when is_map(map) do + object = + map + |> Map.put_new("id", "pleroma:fake_object_id") + |> Map.put_new_lazy("published", &make_date/0) + |> Map.put_new("context", activity["context"]) + |> Map.put_new("context_id", activity["context_id"]) + |> Map.put_new("fake", true) + + %{activity | "object" => object} + end + + defp lazy_put_object_defaults(%{"object" => map} = activity, _) + when is_map(map) do + object = + map + |> Map.put_new_lazy("id", &generate_object_id/0) + |> Map.put_new_lazy("published", &make_date/0) + |> Map.put_new("context", activity["context"]) + |> Map.put_new("context_id", activity["context_id"]) + + %{activity | "object" => object} end + defp lazy_put_object_defaults(activity, _), do: activity + @doc """ Inserts a full object if it is contained in an activity. """ @@ -356,23 +351,30 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Updates a follow activity's state (for locked accounts). """ + @spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity} | {:error, any()} def update_follow_state_for_all( %Activity{data: %{"actor" => actor, "object" => object}} = activity, state ) do - try do - Ecto.Adapters.SQL.query!( - Repo, - "UPDATE activities SET data = jsonb_set(data, '{state}', $1) WHERE data->>'type' = 'Follow' AND data->>'actor' = $2 AND data->>'object' = $3 AND data->>'state' = 'pending'", - [state, actor, object] + query = + from(activity in Activity, + where: fragment("data->>'type' = 'Follow'"), + where: fragment("data->>'state' = 'pending'"), + where: fragment("data->>'actor' = ?", ^actor), + where: fragment("data->>'object' = ?", ^object), + update: [ + set: [ + data: fragment("jsonb_set(data, '{state}', ?)", ^state) + ] + ] ) - User.set_follow_state_cache(actor, object, state) - activity = Activity.get_by_id(activity.id) + with {_, _} <- Repo.update_all(query, []), + {_, _} <- User.set_follow_state_cache(actor, object, state), + %Activity{} = activity <- Activity.get_by_id(activity.id) do {:ok, activity} - rescue - e -> - {:error, e} + else + e -> {:error, e} end end @@ -380,9 +382,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do %Activity{data: %{"actor" => actor, "object" => object}} = activity, state ) do - with new_data <- - activity.data - |> Map.put("state", state), + with new_data <- Map.put(activity.data, "state", state), changeset <- Changeset.change(activity, data: new_data), {:ok, activity} <- Repo.update(changeset), _ <- User.set_follow_state_cache(actor, object, state) do @@ -411,27 +411,17 @@ defmodule Pleroma.Web.ActivityPub.Utils do def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do query = - from( - activity in Activity, - where: - fragment( - "? ->> 'type' = 'Follow'", - activity.data - ), - where: activity.actor == ^follower_id, - # this is to use the index - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - activity.data, - activity.data, - ^followed_id - ), - order_by: [fragment("? desc nulls last", activity.id)], - limit: 1 - ) - - Repo.one(query) + follower_id + |> Activity.Queries.by_actor() + |> Activity.Queries.by_type("Follow") + |> Activity.Queries.by_object_id(followed_id) + |> Activity.Queries.limit(1) + + from( + activity in query, + order_by: [fragment("? desc nulls last", activity.id)] + ) + |> Repo.one() end #### Announce-related helpers @@ -439,23 +429,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Retruns an existing announce activity if the notice has already been announced """ + @spec get_existing_announce(String.t(), map()) :: Activity.t() | nil def get_existing_announce(actor, %{data: %{"id" => id}}) do - query = - from( - activity in Activity, - where: activity.actor == ^actor, - # this is to use the index - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - activity.data, - activity.data, - ^id - ), - where: fragment("(?)->>'type' = 'Announce'", activity.data) - ) - - Repo.one(query) + actor + |> Activity.Queries.by_actor() + |> Activity.Queries.by_type("Announce") + |> Activity.Queries.by_object_id(id) + |> Activity.Queries.limit(1) + |> Repo.one() end @doc """ @@ -531,31 +512,35 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> maybe_put("id", activity_id) end + @spec add_announce_to_object(Activity.t(), Object.t()) :: + {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_announce_to_object( - %Activity{ - data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]} - }, + %Activity{data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}}, object ) do - announcements = - if is_list(object.data["announcements"]), do: object.data["announcements"], else: [] + announcements = fetch_announcements(object) - with announcements <- [actor | announcements] |> Enum.uniq() do + with announcements <- Enum.uniq([actor | announcements]) do update_element_in_object("announcement", announcements, object) end end def add_announce_to_object(_, object), do: {:ok, object} + @spec remove_announce_from_object(Activity.t(), Object.t()) :: + {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do - announcements = - if is_list(object.data["announcements"]), do: object.data["announcements"], else: [] - - with announcements <- announcements |> List.delete(actor) do + with announcements <- List.delete(fetch_announcements(object), actor) do update_element_in_object("announcement", announcements, object) end end + defp fetch_announcements(%{data: %{"announcements" => announcements}} = _) + when is_list(announcements), + do: announcements + + defp fetch_announcements(_), do: [] + #### Unfollow-related helpers def make_unfollow_data(follower, followed, follow_activity, activity_id) do @@ -569,29 +554,20 @@ defmodule Pleroma.Web.ActivityPub.Utils do end #### Block-related helpers + @spec fetch_latest_block(User.t(), User.t()) :: Activity.t() | nil def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do query = - from( - activity in Activity, - where: - fragment( - "? ->> 'type' = 'Block'", - activity.data - ), - where: activity.actor == ^blocker_id, - # this is to use the index - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - activity.data, - activity.data, - ^blocked_id - ), - order_by: [fragment("? desc nulls last", activity.id)], - limit: 1 - ) - - Repo.one(query) + blocker_id + |> Activity.Queries.by_actor() + |> Activity.Queries.by_type("Block") + |> Activity.Queries.by_object_id(blocked_id) + |> Activity.Queries.limit(1) + + from( + activity in query, + order_by: [fragment("? desc nulls last", activity.id)] + ) + |> Repo.one() end def make_block_data(blocker, blocked, activity_id) do @@ -631,28 +607,32 @@ defmodule Pleroma.Web.ActivityPub.Utils do end #### Flag-related helpers - - def make_flag_data(params, additional) do - status_ap_ids = - Enum.map(params.statuses || [], fn - %Activity{} = act -> act.data["id"] - act when is_map(act) -> act["id"] - act when is_binary(act) -> act - end) - - object = [params.account.ap_id] ++ status_ap_ids - + @spec make_flag_data(map(), map()) :: map() + def make_flag_data(%{actor: actor, context: context, content: content} = params, additional) do %{ "type" => "Flag", - "actor" => params.actor.ap_id, - "content" => params.content, - "object" => object, - "context" => params.context, + "actor" => actor.ap_id, + "content" => content, + "object" => build_flag_object(params), + "context" => context, "state" => "open" } |> Map.merge(additional) end + def make_flag_data(_, _), do: %{} + + defp build_flag_object(%{account: account, statuses: statuses} = _) do + [account.ap_id] ++ + Enum.map(statuses || [], fn + %Activity{} = act -> act.data["id"] + act when is_map(act) -> act["id"] + act when is_binary(act) -> act + end) + end + + defp build_flag_object(_), do: [] + @doc """ Fetches the OrderedCollection/OrderedCollectionPage from `from`, limiting the amount of pages fetched after the first one to `pages_left` pages. -- cgit v1.2.3 From a890451187f0b1507be96ccf144b18fdb8294dd8 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 4 Sep 2019 17:42:27 +0300 Subject: fetch_announcements -> take_announcements --- lib/pleroma/web/activity_pub/utils.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index cf82d1a9b..0d87b9220 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -518,7 +518,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do %Activity{data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}}, object ) do - announcements = fetch_announcements(object) + announcements = take_announcements(object) with announcements <- Enum.uniq([actor | announcements]) do update_element_in_object("announcement", announcements, object) @@ -530,16 +530,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do @spec remove_announce_from_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do - with announcements <- List.delete(fetch_announcements(object), actor) do + with announcements <- List.delete(take_announcements(object), actor) do update_element_in_object("announcement", announcements, object) end end - defp fetch_announcements(%{data: %{"announcements" => announcements}} = _) + defp take_announcements(%{data: %{"announcements" => announcements}} = _) when is_list(announcements), do: announcements - defp fetch_announcements(_), do: [] + defp take_announcements(_), do: [] #### Unfollow-related helpers -- cgit v1.2.3 From 2975da284b75c846a99a56ce70a91ebc3cc43f33 Mon Sep 17 00:00:00 2001 From: Sadposter Date: Wed, 4 Sep 2019 15:45:40 +0100 Subject: truncate remote user bio/display name --- lib/pleroma/user.ex | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 29fd6d2ea..87e56b5b4 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -174,11 +174,25 @@ defmodule Pleroma.User do |> Repo.aggregate(:count, :id) end + defp truncate_if_exists(params, key, max_length) do + if Map.has_key?(params, key) do + {value, _chopped} = String.split_at(params[key], max_length) + Map.put(params, key, value) + else + params + end + end + def remote_user_creation(params) do bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) - params = Map.put(params, :info, params[:info] || %{}) + params = + params + |> Map.put(:info, params[:info] || %{}) + |> truncate_if_exists(:name, name_limit) + |> truncate_if_exists(:bio, bio_limit) + info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info]) changes = -- cgit v1.2.3 From cb99cfcc65f57f0044117ebd12d040488343d9ef Mon Sep 17 00:00:00 2001 From: Sadposter Date: Wed, 4 Sep 2019 15:57:42 +0100 Subject: don't try to truncate non-strings --- lib/pleroma/user.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 87e56b5b4..e2ebce6fc 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -175,7 +175,7 @@ defmodule Pleroma.User do end defp truncate_if_exists(params, key, max_length) do - if Map.has_key?(params, key) do + if Map.has_key?(params, key) and is_binary(params[key]) do {value, _chopped} = String.split_at(params[key], max_length) Map.put(params, key, value) else -- cgit v1.2.3 From af746fa4a814dbacd4fe4a3e58b1ee1732363d22 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Wed, 4 Sep 2019 20:08:13 +0300 Subject: Return total for reports --- lib/pleroma/web/admin_api/admin_api_controller.ex | 6 ++---- lib/pleroma/web/admin_api/views/report_view.ex | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 544b9d7d8..2a1cc59e5 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -442,11 +442,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do params |> Map.put("type", "Flag") |> Map.put("skip_preload", true) + |> Map.put("total", true) - reports = - [] - |> ActivityPub.fetch_activities(params) - |> Enum.reverse() + reports = ActivityPub.fetch_activities([], params) conn |> put_view(ReportView) diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index a25f3f1fe..0b8745b2e 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -12,7 +12,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do def render("index.json", %{reports: reports}) do %{ - reports: render_many(reports, __MODULE__, "show.json", as: :report) + reports: render_many(reports[:items], __MODULE__, "show.json", as: :report), + total: reports[:total] } end -- cgit v1.2.3 From 8306078de1abade082f932cda5b8d9297bdcdc80 Mon Sep 17 00:00:00 2001 From: Maksim Date: Wed, 4 Sep 2019 17:31:14 +0000 Subject: Apply suggestion to lib/pleroma/web/activity_pub/utils.ex --- lib/pleroma/web/activity_pub/utils.ex | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 0d87b9220..2de02f607 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -356,26 +356,19 @@ defmodule Pleroma.Web.ActivityPub.Utils do %Activity{data: %{"actor" => actor, "object" => object}} = activity, state ) do - query = - from(activity in Activity, - where: fragment("data->>'type' = 'Follow'"), - where: fragment("data->>'state' = 'pending'"), - where: fragment("data->>'actor' = ?", ^actor), - where: fragment("data->>'object' = ?", ^object), - update: [ - set: [ - data: fragment("jsonb_set(data, '{state}', ?)", ^state) - ] - ] - ) - - with {_, _} <- Repo.update_all(query, []), - {_, _} <- User.set_follow_state_cache(actor, object, state), - %Activity{} = activity <- Activity.get_by_id(activity.id) do - {:ok, activity} - else - e -> {:error, e} - end + "Follow" + |> Activity.Queries.by_type() + |> Activity.Queries.by_actor(actor) + |> Activity.Queries.by_object_id(object["id"]) + |> where(fragment("data->>'state' = 'pending'")) + |> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)]) + |> Repo.update_all([]) + + User.set_follow_state_cache(actor, object, state) + + activity = Activity.get_by_id(activity.id) + + {:ok, activity} end def update_follow_state( -- cgit v1.2.3 From e2011a667cdf5e67f257c9c30a02c206fb4df913 Mon Sep 17 00:00:00 2001 From: Maksim Date: Wed, 4 Sep 2019 18:35:01 +0000 Subject: Apply suggestion to lib/pleroma/web/activity_pub/utils.ex --- lib/pleroma/web/activity_pub/utils.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2de02f607..011acd48e 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -359,7 +359,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do "Follow" |> Activity.Queries.by_type() |> Activity.Queries.by_actor(actor) - |> Activity.Queries.by_object_id(object["id"]) + |> Activity.Queries.by_object_id(object) |> where(fragment("data->>'state' = 'pending'")) |> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)]) |> Repo.update_all([]) -- cgit v1.2.3 From ae506ca997619f118d18703a9b0802246eb427d5 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 4 Sep 2019 21:40:53 +0300 Subject: fix formatting --- lib/pleroma/web/activity_pub/utils.ex | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 011acd48e..72e07b59d 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -356,19 +356,19 @@ defmodule Pleroma.Web.ActivityPub.Utils do %Activity{data: %{"actor" => actor, "object" => object}} = activity, state ) do - "Follow" - |> Activity.Queries.by_type() - |> Activity.Queries.by_actor(actor) - |> Activity.Queries.by_object_id(object) - |> where(fragment("data->>'state' = 'pending'")) - |> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)]) - |> Repo.update_all([]) - - User.set_follow_state_cache(actor, object, state) - - activity = Activity.get_by_id(activity.id) - - {:ok, activity} + "Follow" + |> Activity.Queries.by_type() + |> Activity.Queries.by_actor(actor) + |> Activity.Queries.by_object_id(object) + |> where(fragment("data->>'state' = 'pending'")) + |> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)]) + |> Repo.update_all([]) + + User.set_follow_state_cache(actor, object, state) + + activity = Activity.get_by_id(activity.id) + + {:ok, activity} end def update_follow_state( -- cgit v1.2.3 From 736165c082d34ef4d52367ea8315c228a1df3944 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Thu, 5 Sep 2019 16:54:34 +0300 Subject: Reverse reports list --- lib/pleroma/web/admin_api/views/report_view.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 0b8745b2e..51b95ad5e 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -12,7 +12,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do def render("index.json", %{reports: reports}) do %{ - reports: render_many(reports[:items], __MODULE__, "show.json", as: :report), + reports: + render_many(reports[:items], __MODULE__, "show.json", as: :report) |> Enum.reverse(), total: reports[:total] } end -- cgit v1.2.3 From 40b3289c26137ee4d07c7fb79faf232714cc7592 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 6 Sep 2019 17:08:47 +0700 Subject: Refactor `add_link_headers/7` -> `add_link_headers/3` --- lib/pleroma/web/controller_helper.ex | 95 ++++++---------------- .../controllers/mastodon_api_controller.ex | 28 +++---- .../web/pleroma_api/pleroma_api_controller.ex | 27 ++---- 3 files changed, 50 insertions(+), 100 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index eeac9f503..b53a01955 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -34,79 +34,38 @@ defmodule Pleroma.Web.ControllerHelper do defp param_to_integer(_, default), do: default - def add_link_headers( - conn, - method, - activities, - param \\ nil, - params \\ %{}, - func3 \\ nil, - func4 \\ nil - ) do - params = - conn.params - |> Map.drop(["since_id", "max_id", "min_id"]) - |> Map.merge(params) + def add_link_headers(conn, activities, extra_params \\ %{}) do + case List.last(activities) do + %{id: max_id} -> + params = + conn.params + |> Map.drop(Map.keys(conn.path_params)) + |> Map.drop(["since_id", "max_id", "min_id"]) + |> Map.merge(extra_params) - last = List.last(activities) + limit = + params + |> Map.get("limit", "20") + |> String.to_integer() - func3 = func3 || (&mastodon_api_url/3) - func4 = func4 || (&mastodon_api_url/4) + min_id = + if length(activities) <= limit do + activities + |> List.first() + |> Map.get(:id) + else + activities + |> Enum.at(limit * -1) + |> Map.get(:id) + end - if last do - max_id = last.id + next_url = current_url(conn, Map.merge(params, %{max_id: max_id})) + prev_url = current_url(conn, Map.merge(params, %{min_id: min_id})) - limit = - params - |> Map.get("limit", "20") - |> String.to_integer() + put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"") - min_id = - if length(activities) <= limit do - activities - |> List.first() - |> Map.get(:id) - else - activities - |> Enum.at(limit * -1) - |> Map.get(:id) - end - - {next_url, prev_url} = - if param do - { - func4.( - Pleroma.Web.Endpoint, - method, - param, - Map.merge(params, %{max_id: max_id}) - ), - func4.( - Pleroma.Web.Endpoint, - method, - param, - Map.merge(params, %{min_id: min_id}) - ) - } - else - { - func3.( - Pleroma.Web.Endpoint, - method, - Map.merge(params, %{max_id: max_id}) - ), - func3.( - Pleroma.Web.Endpoint, - method, - Map.merge(params, %{min_id: min_id}) - ) - } - end - - conn - |> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"") - else - conn + _ -> + conn end end end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 8dfad7a54..f30a21bcc 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller import Pleroma.Web.ControllerHelper, - only: [json_response: 3, add_link_headers: 5, add_link_headers: 4, add_link_headers: 3] + only: [json_response: 3, add_link_headers: 2, add_link_headers: 3] alias Ecto.Changeset alias Pleroma.Activity @@ -365,7 +365,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Enum.reverse() conn - |> add_link_headers(:home_timeline, activities) + |> add_link_headers(activities) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) end @@ -384,7 +384,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Enum.reverse() conn - |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only}) + |> add_link_headers(activities, %{"local" => local_only}) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) end @@ -398,7 +398,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do activities = ActivityPub.fetch_user_activities(user, reading_user, params) conn - |> add_link_headers(:user_statuses, activities, params["id"]) + |> add_link_headers(activities) |> put_view(StatusView) |> render("index.json", %{ activities: activities, @@ -422,7 +422,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Pagination.fetch_paginated(params) conn - |> add_link_headers(:dm_timeline, activities) + |> add_link_headers(activities) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) end @@ -523,7 +523,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do conn - |> add_link_headers(:scheduled_statuses, scheduled_activities) + |> add_link_headers(scheduled_activities) |> put_view(ScheduledActivityView) |> render("index.json", %{scheduled_activities: scheduled_activities}) end @@ -706,7 +706,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do notifications = MastodonAPI.get_notifications(user, params) conn - |> add_link_headers(:notifications, notifications) + |> add_link_headers(notifications) |> put_view(NotificationView) |> render("index.json", %{notifications: notifications, for: user}) end @@ -894,7 +894,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Enum.reverse() conn - |> add_link_headers(:hashtag_timeline, activities, params["tag"], %{"local" => local_only}) + |> add_link_headers(activities, %{"local" => local_only}) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) end @@ -910,7 +910,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end conn - |> add_link_headers(:followers, followers, user) + |> add_link_headers(followers) |> put_view(AccountView) |> render("accounts.json", %{for: for_user, users: followers, as: :user}) end @@ -927,7 +927,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end conn - |> add_link_headers(:following, followers, user) + |> add_link_headers(followers) |> put_view(AccountView) |> render("accounts.json", %{for: for_user, users: followers, as: :user}) end @@ -1152,7 +1152,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Enum.reverse() conn - |> add_link_headers(:favourites, activities) + |> add_link_headers(activities) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) end @@ -1179,7 +1179,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Enum.reverse() conn - |> add_link_headers(:favourites, activities) + |> add_link_headers(activities) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: for_user, as: :activity}) else @@ -1200,7 +1200,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end) conn - |> add_link_headers(:bookmarks, bookmarks) + |> add_link_headers(bookmarks) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) end @@ -1640,7 +1640,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end) conn - |> add_link_headers(:conversations, participations) + |> add_link_headers(participations) |> json(conversations) end diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index f4df3b024..d17ccf84d 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7] + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] alias Pleroma.Conversation.Participation alias Pleroma.Notification @@ -27,31 +27,22 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do %{assigns: %{user: user}} = conn, %{"id" => participation_id} = params ) do - params = - params - |> Map.put("blocking_user", user) - |> Map.put("muting_user", user) - |> Map.put("user", user) - - participation = - participation_id - |> Participation.get(preload: [:conversation]) + participation = Participation.get(participation_id, preload: [:conversation]) if user.id == participation.user_id do + params = + params + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + activities = participation.conversation.ap_id |> ActivityPub.fetch_activities_for_context(params) |> Enum.reverse() conn - |> add_link_headers( - :conversation_statuses, - activities, - participation_id, - params, - nil, - &pleroma_api_url/4 - ) + |> add_link_headers(activities) |> put_view(StatusView) |> render("index.json", %{activities: activities, for: user, as: :activity}) end -- cgit v1.2.3 From ab2f21e470f349f783f895f26da3041afcc3d73e Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 6 Sep 2019 21:50:00 +0300 Subject: tests for mastodon_api_controller.ex --- lib/pleroma/object.ex | 7 + lib/pleroma/user.ex | 22 +++- .../controllers/mastodon_api_controller.ex | 143 +++++++-------------- lib/pleroma/web/oauth/app.ex | 26 ++++ lib/pleroma/web/twitter_api/twitter_api.ex | 2 +- 5 files changed, 98 insertions(+), 102 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index d58eb7f7d..4398b9739 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -228,4 +228,11 @@ defmodule Pleroma.Object do _ -> :noop end end + + @doc "Updates data field of an object" + def update_data(%Object{data: data} = object, attrs \\ %{}) do + object + |> Object.change(%{data: Map.merge(data || %{}, attrs)}) + |> Repo.update() + end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3aa245f2a..d9db985a6 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -499,6 +499,11 @@ defmodule Pleroma.User do |> Repo.all() end + def get_all_by_ids(ids) do + from(u in __MODULE__, where: u.id in ^ids) + |> Repo.all() + end + # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part # of the ap_id and the domain and tries to get that user def get_by_guessed_nickname(ap_id) do @@ -770,6 +775,19 @@ defmodule Pleroma.User do |> update_and_set_cache() end + def update_mascot(user, url) do + info_changeset = + User.Info.mascot_update( + user.info, + url + ) + + user + |> change() + |> put_embed(:info, info_changeset) + |> update_and_set_cache() + end + @spec maybe_fetch_follow_information(User.t()) :: User.t() def maybe_fetch_follow_information(user) do with {:ok, user} <- fetch_follow_information(user) do @@ -917,9 +935,7 @@ defmodule Pleroma.User do def unsubscribe(unsubscriber, %{ap_id: ap_id}) do with %User{} = user <- get_cached_by_ap_id(ap_id) do - info_cng = - user.info - |> User.Info.remove_from_subscribers(unsubscriber.ap_id) + info_cng = User.Info.remove_from_subscribers(user.info, unsubscriber.ap_id) change(user) |> put_embed(:info, info_cng) diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 8dfad7a54..e4e0a7ac9 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -447,8 +447,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do result = %{ ancestors: - StatusView.render( - "index.json", + StatusView.render("index.json", for: user, activities: grouped_activities[true] || [], as: :activity @@ -456,8 +455,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Enum.reverse(), # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart descendants: - StatusView.render( - "index.json", + StatusView.render("index.json", for: user, activities: grouped_activities[false] || [], as: :activity @@ -746,9 +744,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do - id = List.wrap(id) - q = from(u in User, where: u.id in ^id) - targets = Repo.all(q) + targets = User.get_all_by_ids(List.wrap(id)) conn |> put_view(AccountView) @@ -758,19 +754,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array. def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, []) - def update_media(%{assigns: %{user: user}} = conn, data) do - with %Object{} = object <- Repo.get(Object, data["id"]), + def update_media( + %{assigns: %{user: user}} = conn, + %{"id" => id, "description" => description} = _ + ) + when is_binary(description) do + with %Object{} = object <- Repo.get(Object, id), true <- Object.authorize_mutation(object, user), - true <- is_binary(data["description"]), - description <- data["description"] do - new_data = %{object.data | "name" => description} - - {:ok, _} = - object - |> Object.change(%{data: new_data}) - |> Repo.update() - - attachment_data = Map.put(new_data, "id", object.id) + {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do + attachment_data = Map.put(data, "id", object.id) conn |> put_view(StatusView) @@ -778,6 +770,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def update_media(_conn, _data), do: {:error, :bad_request} + def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do with {:ok, object} <- ActivityPub.upload( @@ -796,34 +790,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)), %{} = attachment_data <- Map.put(object.data, "id", object.id), - %{type: type} = rendered <- - StatusView.render("attachment.json", %{attachment: attachment_data}) do - # Reject if not an image - if type == "image" do - # Sure! - # Save to the user's info - info_changeset = User.Info.mascot_update(user.info, rendered) - - user_changeset = - user - |> Changeset.change() - |> Changeset.put_embed(:info, info_changeset) - - {:ok, _user} = User.update_and_set_cache(user_changeset) - - conn - |> json(rendered) - else + %{type: "image"} = rendered <- + StatusView.render("attachment.json", %{attachment: attachment_data}), + {:ok, _user} = User.update_mascot(user, rendered) do + json(conn, rendered) + else + %{type: _type} = _ -> render_error(conn, :unsupported_media_type, "mascots can only be images") - end + + e -> + e end end def get_mascot(%{assigns: %{user: user}} = conn, _params) do mascot = User.get_mascot(user) - conn - |> json(mascot) + json(conn, mascot) end def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do @@ -1119,10 +1102,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> put_view(AccountView) |> render("relationship.json", %{user: user, target: subscription_target}) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + nil -> {:error, :not_found} + e -> e end end @@ -1133,10 +1114,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> put_view(AccountView) |> render("relationship.json", %{user: user, target: subscription_target}) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + nil -> {:error, :not_found} + e -> e end end @@ -1207,8 +1186,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do lists = Pleroma.List.get_lists_account_belongs(user, account_id) - res = ListView.render("lists.json", lists: lists) - json(conn, res) + + conn + |> put_view(ListView) + |> render("index.json", %{lists: lists}) end def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do @@ -1363,7 +1344,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do @doc "Local Mastodon FE login init action" def login(conn, %{"code" => auth_token}) do with {:ok, app} <- get_or_make_app(), - %Authorization{} = auth <- Repo.get_by(Authorization, token: auth_token, app_id: app.id), + {:ok, auth} <- Authorization.get_by_token(app, auth_token), {:ok, token} <- Token.exchange_token(app, auth) do conn |> put_session(:oauth_token, token.token) @@ -1375,9 +1356,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def login(conn, _) do with {:ok, app} <- get_or_make_app() do path = - o_auth_path( - conn, - :authorize, + o_auth_path(conn, :authorize, response_type: "code", client_id: app.client_id, redirect_uri: ".", @@ -1399,31 +1378,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} defp get_or_make_app do - find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."} - scopes = ["read", "write", "follow", "push"] - - with %App{} = app <- Repo.get_by(App, find_attrs) do - {:ok, app} = - if app.scopes == scopes do - {:ok, app} - else - app - |> Changeset.change(%{scopes: scopes}) - |> Repo.update() - end - - {:ok, app} - else - _e -> - cs = - App.register_changeset( - %App{}, - Map.put(find_attrs, :scopes, scopes) - ) - - Repo.insert(cs) - end + App.get_or_make( + %{client_name: @local_mastodon_name, redirect_uris: "."}, + ["read", "write", "follow", "push"] + ) end def logout(conn, _) do @@ -1432,26 +1392,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> redirect(to: "/") end - def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do - Logger.debug("Unimplemented, returning unmodified relationship") - - with %User{} = target <- User.get_cached_by_id(id) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: user, target: target}) - end - end - + # Stubs for unimplemented mastodon api + # def empty_array(conn, _) do Logger.debug("Unimplemented, returning an empty array") json(conn, []) end - def empty_object(conn, _) do - Logger.debug("Unimplemented, returning an empty object") - json(conn, %{}) - end - def get_filters(%{assigns: %{user: user}} = conn, _) do filters = Filter.get_filters(user) res = FilterView.render("filters.json", filters: filters) @@ -1570,7 +1517,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, data) else _e -> - %{} + json(conn, %{}) end end @@ -1623,7 +1570,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def account_register(%{assigns: %{app: _app}} = conn, _params) do + def account_register(%{assigns: %{app: _app}} = conn, _) do render_error(conn, :bad_request, "Missing parameters") end @@ -1682,15 +1629,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def try_render(conn, target, params) - when is_binary(target) do + defp try_render(conn, target, params) + when is_binary(target) do case render(conn, target, params) do nil -> render_error(conn, :not_implemented, "Can't display this activity") res -> res end end - def try_render(conn, _, _) do + defp try_render(conn, _, _) do render_error(conn, :not_implemented, "Can't display this activity") end diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex index ddcdb1871..cc3fb1ce5 100644 --- a/lib/pleroma/web/oauth/app.ex +++ b/lib/pleroma/web/oauth/app.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.OAuth.App do use Ecto.Schema import Ecto.Changeset + alias Pleroma.Repo @type t :: %__MODULE__{} @@ -39,4 +40,29 @@ defmodule Pleroma.Web.OAuth.App do changeset end end + + @doc """ + Gets app by attrs or create new with attrs. + And updates the scopes if need. + """ + @spec get_or_make(map(), list(String.t())) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} + def get_or_make(attrs, scopes) do + with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do + update_scopes(app, scopes) + else + _e -> + %__MODULE__{} + |> register_changeset(Map.put(attrs, :scopes, scopes)) + |> Repo.insert() + end + end + + defp update_scopes(%__MODULE__{} = app, []), do: {:ok, app} + defp update_scopes(%__MODULE__{scopes: scopes} = app, scopes), do: {:ok, app} + + defp update_scopes(%__MODULE__{} = app, scopes) do + app + |> change(%{scopes: scopes}) + |> Repo.update() + end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 8eda762c7..bfd838902 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled]) # true if captcha is disabled or enabled and valid, false otherwise captcha_ok = - if !captcha_enabled do + if not captcha_enabled do :ok else Pleroma.Captcha.validate( -- cgit v1.2.3 From a31af93e1d10d9db8796d86ccda35873697b5a4c Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Tue, 10 Sep 2019 16:43:10 +0300 Subject: added tests /activity_pub/transmogrifier.ex --- lib/pleroma/web/activity_pub/transmogrifier.ex | 264 ++++++++++--------------- 1 file changed, 108 insertions(+), 156 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 468961bd0..93b3a1f97 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -41,8 +41,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def fix_summary(%{"summary" => nil} = object) do - object - |> Map.put("summary", "") + Map.put(object, "summary", "") end def fix_summary(%{"summary" => _} = object) do @@ -50,10 +49,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do object end - def fix_summary(object) do - object - |> Map.put("summary", "") - end + def fix_summary(object), do: Map.put(object, "summary", "") def fix_addressing_list(map, field) do cond do @@ -73,13 +69,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do explicit_mentions, follower_collection ) do - explicit_to = - to - |> Enum.filter(fn x -> x in explicit_mentions end) + explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end) - explicit_cc = - to - |> Enum.filter(fn x -> x not in explicit_mentions end) + explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end) final_cc = (cc ++ explicit_cc) @@ -97,13 +89,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_explicit_addressing(%{"directMessage" => true} = object), do: object def fix_explicit_addressing(object) do - explicit_mentions = - object - |> Utils.determine_explicit_mentions() + explicit_mentions = Utils.determine_explicit_mentions(object) - follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address + %User{follower_address: follower_collection} = + object + |> Containment.get_actor() + |> User.get_cached_by_ap_id() - explicit_mentions = explicit_mentions ++ [Pleroma.Constants.as_public(), follower_collection] + explicit_mentions = + explicit_mentions ++ + [ + Pleroma.Constants.as_public(), + follower_collection + ] fix_explicit_addressing(object, explicit_mentions, follower_collection) end @@ -147,48 +145,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def fix_actor(%{"attributedTo" => actor} = object) do - object - |> Map.put("actor", Containment.get_actor(%{"actor" => actor})) + Map.put(object, "actor", Containment.get_actor(%{"actor" => actor})) end def fix_in_reply_to(object, options \\ []) def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) when not is_nil(in_reply_to) do - in_reply_to_id = - cond do - is_bitstring(in_reply_to) -> - in_reply_to - - is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) -> - in_reply_to["id"] - - is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) -> - Enum.at(in_reply_to, 0) - - # Maybe I should output an error too? - true -> - "" - end - + in_reply_to_id = prepare_in_reply_to(in_reply_to) object = Map.put(object, "inReplyToAtomUri", in_reply_to_id) if Federator.allowed_incoming_reply_depth?(options[:depth]) do - case get_obj_helper(in_reply_to_id, options) do - {:ok, replied_object} -> - with %Activity{} = _activity <- - Activity.get_create_by_object_ap_id(replied_object.data["id"]) do - object - |> Map.put("inReplyTo", replied_object.data["id"]) - |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) - |> Map.put("conversation", replied_object.data["context"] || object["conversation"]) - |> Map.put("context", replied_object.data["context"] || object["conversation"]) - else - e -> - Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") - object - end - + with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options), + %Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do + object + |> Map.put("inReplyTo", replied_object.data["id"]) + |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) + |> Map.put("conversation", replied_object.data["context"] || object["conversation"]) + |> Map.put("context", replied_object.data["context"] || object["conversation"]) + else e -> Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") object @@ -200,6 +175,22 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_in_reply_to(object, _options), do: object + defp prepare_in_reply_to(in_reply_to) do + cond do + is_bitstring(in_reply_to) -> + in_reply_to + + is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) -> + in_reply_to["id"] + + is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) -> + Enum.at(in_reply_to, 0) + + true -> + "" + end + end + def fix_context(object) do context = object["context"] || object["conversation"] || Utils.generate_context_id() @@ -210,8 +201,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do attachments = - attachment - |> Enum.map(fn data -> + Enum.map(attachment, fn data -> media_type = data["mediaType"] || data["mimeType"] href = data["url"] || data["href"] @@ -222,30 +212,23 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("url", url) end) - object - |> Map.put("attachment", attachments) + Map.put(object, "attachment", attachments) end def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do - Map.put(object, "attachment", [attachment]) - |> fix_attachments() + fix_attachments(Map.put(object, "attachment", [attachment])) end def fix_attachments(object), do: object def fix_url(%{"url" => url} = object) when is_map(url) do - object - |> Map.put("url", url["href"]) + Map.put(object, "url", url["href"]) end def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do first_element = Enum.at(url, 0) - link_element = - url - |> Enum.filter(fn x -> is_map(x) end) - |> Enum.filter(fn x -> x["mimeType"] == "text/html" end) - |> Enum.at(0) + link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end) object |> Map.put("attachment", [first_element]) @@ -263,36 +246,32 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do true -> "" end - object - |> Map.put("url", url_string) + Map.put(object, "url", url_string) end def fix_url(object), do: object def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do - emoji = tags |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end) - emoji = - emoji + tags + |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end) |> Enum.reduce(%{}, fn data, mapping -> name = String.trim(data["name"], ":") - mapping |> Map.put(name, data["icon"]["url"]) + Map.put(mapping, name, data["icon"]["url"]) end) # we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats emoji = Map.merge(object["emoji"] || %{}, emoji) - object - |> Map.put("emoji", emoji) + Map.put(object, "emoji", emoji) end def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do name = String.trim(tag["name"], ":") emoji = %{name => tag["icon"]["url"]} - object - |> Map.put("emoji", emoji) + Map.put(object, "emoji", emoji) end def fix_emoji(object), do: object @@ -303,17 +282,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end) |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end) - combined = tag ++ tags - - object - |> Map.put("tag", combined) + Map.put(object, "tag", tag ++ tags) end def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do combined = [tag, String.slice(hashtag, 1..-1)] - object - |> Map.put("tag", combined) + Map.put(object, "tag", combined) end def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag]) @@ -325,8 +300,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do content_groups = Map.to_list(content_map) {_, content} = Enum.at(content_groups, 0) - object - |> Map.put("content", content) + Map.put(object, "content", content) end def fix_content_map(object), do: object @@ -335,16 +309,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) when is_binary(reply_id) do - reply = - with true <- Federator.allowed_incoming_reply_depth?(options[:depth]), - {:ok, object} <- get_obj_helper(reply_id, options) do - object - end - - if reply && reply.data["type"] == "Question" do + with true <- Federator.allowed_incoming_reply_depth?(options[:depth]), + {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do Map.put(object, "type", "Answer") else - object + _ -> object end end @@ -376,6 +345,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end + # Reduce the object list to find the reported user. + defp get_reported(objects) do + Enum.reduce_while(objects, nil, fn ap_id, _ -> + with %User{} = user <- User.get_cached_by_ap_id(ap_id) do + {:halt, user} + else + _ -> {:cont, nil} + end + end) + end + def handle_incoming(data, options \\ []) # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them @@ -384,31 +364,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do with context <- data["context"] || Utils.generate_context_id(), content <- data["content"] || "", %User{} = actor <- User.get_cached_by_ap_id(actor), - # Reduce the object list to find the reported user. - %User{} = account <- - Enum.reduce_while(objects, nil, fn ap_id, _ -> - with %User{} = user <- User.get_cached_by_ap_id(ap_id) do - {:halt, user} - else - _ -> {:cont, nil} - end - end), - + %User{} = account <- get_reported(objects), # Remove the reported user from the object list. statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do - params = %{ + %{ actor: actor, context: context, account: account, statuses: statuses, content: content, - additional: %{ - "cc" => [account.ap_id] - } + additional: %{"cc" => [account.ap_id]} } - - ActivityPub.flag(params) + |> ActivityPub.flag() end end @@ -755,8 +723,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming(_, _), do: :error + @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil def get_obj_helper(id, options \\ []) do - if object = Object.normalize(id, true, options), do: {:ok, object}, else: nil + if object = Object.normalize(id, true, options) do + {:ok, object} + else + nil + end end def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do @@ -855,27 +828,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, data} end - def maybe_fix_object_url(data) do - if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do - case get_obj_helper(data["object"]) do - {:ok, relative_object} -> - if relative_object.data["external_url"] do - _data = - data - |> Map.put("object", relative_object.data["external_url"]) - else - data - end - - e -> - Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}") - data - end + def maybe_fix_object_url(%{"object" => object} = data) when is_binary(object) do + with false <- String.starts_with?(object, "http"), + {:fetch, {:ok, relative_object}} <- {:fetch, get_obj_helper(object)}, + %{data: %{"external_url" => external_url}} when not is_nil(external_url) <- + relative_object do + Map.put(data, "object", external_url) else - data + {:fetch, e} -> + Logger.error("Couldn't fetch #{object} #{inspect(e)}") + data + + _ -> + data end end + def maybe_fix_object_url(data), do: data + def add_hashtags(object) do tags = (object["tag"] || []) @@ -893,8 +863,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do tag end) - object - |> Map.put("tag", tags) + Map.put(object, "tag", tags) end def add_mention_tags(object) do @@ -907,15 +876,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do tags = object["tag"] || [] - object - |> Map.put("tag", tags ++ mentions) + Map.put(object, "tag", tags ++ mentions) end def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do user_info = add_emoji_tags(user_info) - object - |> Map.put(:info, user_info) + Map.put(object, :info, user_info) end # TODO: we should probably send mtime instead of unix epoch time for updated @@ -923,8 +890,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do tags = object["tag"] || [] out = - emoji - |> Enum.map(fn {name, url} -> + Enum.map(emoji, fn {name, url} -> %{ "icon" => %{"url" => url, "type" => "Image"}, "name" => ":" <> name <> ":", @@ -934,13 +900,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do } end) - object - |> Map.put("tag", tags ++ out) + Map.put(object, "tag", tags ++ out) end - def add_emoji_tags(object) do - object - end + def add_emoji_tags(object), do: object def set_conversation(object) do Map.put(object, "conversation", object["context"]) @@ -959,9 +922,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def add_attributed_to(object) do attributed_to = object["attributedTo"] || object["actor"] - - object - |> Map.put("attributedTo", attributed_to) + Map.put(object, "attributedTo", attributed_to) end def prepare_attachments(object) do @@ -972,8 +933,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"} end) - object - |> Map.put("attachment", attachments) + Map.put(object, "attachment", attachments) end defp strip_internal_fields(object) do @@ -990,12 +950,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end defp strip_internal_tags(%{"tag" => tags} = object) do - tags = - tags - |> Enum.filter(fn x -> is_map(x) end) + tags = Enum.filter(tags, fn x -> is_map(x) end) - object - |> Map.put("tag", tags) + Map.put(object, "tag", tags) end defp strip_internal_tags(object), do: object @@ -1074,16 +1031,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - def maybe_fix_user_url(data) do - if is_map(data["url"]) do - Map.put(data, "url", data["url"]["href"]) - else - data - end + def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do + Map.put(data, "url", url["href"]) end - def maybe_fix_user_object(data) do - data - |> maybe_fix_user_url - end + def maybe_fix_user_url(data), do: data + + def maybe_fix_user_object(data), do: maybe_fix_user_url(data) end -- cgit v1.2.3 From fcf604fa43031be747b33c05866a192d9651322c Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 11 Sep 2019 07:23:33 +0300 Subject: added tests --- lib/pleroma/object/fetcher.ex | 77 ++++++++++++++------------ lib/pleroma/web/activity_pub/transmogrifier.ex | 12 ++-- 2 files changed, 47 insertions(+), 42 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index c1795ae0f..2217d1eb3 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Object.Fetcher do require Logger + @spec reinject_object(map()) :: {:ok, Object.t()} | {:error, any()} defp reinject_object(data) do Logger.debug("Reinjecting object #{data["id"]}") @@ -29,50 +30,54 @@ defmodule Pleroma.Object.Fetcher do # TODO: # This will create a Create activity, which we need internally at the moment. def fetch_object_from_id(id, options \\ []) do - if object = Object.get_cached_by_ap_id(id) do + with {:fetch_object, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)}, + {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)}, + {:normalize, nil} <- {:normalize, Object.normalize(data, false)}, + params <- prepare_activity_params(data), + {:containment, :ok} <- {:containment, Containment.contain_origin(id, params)}, + {:ok, activity} <- Transmogrifier.handle_incoming(params, options), + {:object, _data, %Object{} = object} <- + {:object, data, Object.normalize(activity, false)} do {:ok, object} else - Logger.info("Fetching #{id} via AP") - - with {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)}, - {:normalize, nil} <- {:normalize, Object.normalize(data, false)}, - params <- %{ - "type" => "Create", - "to" => data["to"], - "cc" => data["cc"], - # Should we seriously keep this attributedTo thing? - "actor" => data["actor"] || data["attributedTo"], - "object" => data - }, - {:containment, :ok} <- {:containment, Containment.contain_origin(id, params)}, - {:ok, activity} <- Transmogrifier.handle_incoming(params, options), - {:object, _data, %Object{} = object} <- - {:object, data, Object.normalize(activity, false)} do - {:ok, object} - else - {:containment, _} -> - {:error, "Object containment failed."} + {:containment, _} -> + {:error, "Object containment failed."} - {:error, {:reject, nil}} -> - {:reject, nil} + {:error, {:reject, nil}} -> + {:reject, nil} - {:object, data, nil} -> - reinject_object(data) + {:object, data, nil} -> + reinject_object(data) - {:normalize, object = %Object{}} -> - {:ok, object} + {:normalize, object = %Object{}} -> + {:ok, object} - _e -> - # Only fallback when receiving a fetch/normalization error with ActivityPub - Logger.info("Couldn't get object via AP, trying out OStatus fetching...") + {:fetch_object, %Object{} = object} -> + {:ok, object} - # FIXME: OStatus Object Containment? - case OStatus.fetch_activity_from_url(id) do - {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)} - e -> e - end - end + _e -> + # Only fallback when receiving a fetch/normalization error with ActivityPub + Logger.info("Couldn't get object via AP, trying out OStatus fetching...") + + # FIXME: OStatus Object Containment? + case OStatus.fetch_activity_from_url(id) do + {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)} + e -> e + end end + + # end + end + + defp prepare_activity_params(data) do + %{ + "type" => "Create", + "to" => data["to"], + "cc" => data["cc"], + # Should we seriously keep this attributedTo thing? + "actor" => data["actor"] || data["attributedTo"], + "object" => data + } end def fetch_object_from_id!(id, options \\ []) do diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 93b3a1f97..18a3c3f39 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -204,7 +204,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do Enum.map(attachment, fn data -> media_type = data["mediaType"] || data["mimeType"] href = data["url"] || data["href"] - url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}] data @@ -216,7 +215,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do - fix_attachments(Map.put(object, "attachment", [attachment])) + object + |> Map.put("attachment", [attachment]) + |> fix_attachments() end def fix_attachments(object), do: object @@ -725,10 +726,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil def get_obj_helper(id, options \\ []) do - if object = Object.normalize(id, true, options) do - {:ok, object} - else - nil + case Object.normalize(id, true, options) do + %Object{} = object -> {:ok, object} + _ -> nil end end -- cgit v1.2.3 From 007e0c1ce158bdfc11738a194944534837ae0258 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 11 Sep 2019 23:19:06 +0300 Subject: added tests --- lib/pleroma/web/activity_pub/transmogrifier.ex | 35 ++++++++++++++----------- lib/pleroma/web/activity_pub/views/user_view.ex | 7 ++--- 2 files changed, 21 insertions(+), 21 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 18a3c3f39..9f699de9e 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -870,41 +870,44 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do mentions = object |> Utils.get_notified_from_object() - |> Enum.map(fn user -> - %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"} - end) + |> Enum.map(&build_mention_tag/1) tags = object["tag"] || [] Map.put(object, "tag", tags ++ mentions) end - def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do - user_info = add_emoji_tags(user_info) + defp build_mention_tag(%{ap_id: ap_id, nickname: nickname} = _) do + %{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"} + end - Map.put(object, :info, user_info) + def take_emoji_tags(%User{info: %{emoji: emoji} = _user_info} = _user) do + emoji + |> Enum.flat_map(&Map.to_list/1) + |> Enum.map(&build_emoji_tag/1) end # TODO: we should probably send mtime instead of unix epoch time for updated def add_emoji_tags(%{"emoji" => emoji} = object) do tags = object["tag"] || [] - out = - Enum.map(emoji, fn {name, url} -> - %{ - "icon" => %{"url" => url, "type" => "Image"}, - "name" => ":" <> name <> ":", - "type" => "Emoji", - "updated" => "1970-01-01T00:00:00Z", - "id" => url - } - end) + out = Enum.map(emoji, &build_emoji_tag/1) Map.put(object, "tag", tags ++ out) end def add_emoji_tags(object), do: object + defp build_emoji_tag({name, url}) do + %{ + "icon" => %{"url" => url, "type" => "Image"}, + "name" => ":" <> name <> ":", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z", + "id" => url + } + end + def set_conversation(object) do Map.put(object, "conversation", object["context"]) end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 7be734b26..8abfa1fcd 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -75,10 +75,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do endpoints = render("endpoints.json", %{user: user}) - user_tags = - user - |> Transmogrifier.add_emoji_tags() - |> Map.get("tag", []) + emoji_tags = Transmogrifier.take_emoji_tags(user) fields = user.info @@ -110,7 +107,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do }, "endpoints" => endpoints, "attachment" => fields, - "tag" => (user.info.source_data["tag"] || []) ++ user_tags + "tag" => (user.info.source_data["tag"] || []) ++ emoji_tags } |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user)) -- cgit v1.2.3 From 4f548cb2b7b4a16a956a4f4a0ff64d279777925e Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 12 Sep 2019 09:59:34 +0300 Subject: added test for Ostatus --- lib/pleroma/web/activity_pub/transmogrifier.ex | 10 ++- lib/pleroma/web/ostatus/ostatus.ex | 99 +++++++++++--------------- lib/pleroma/web/ostatus/ostatus_controller.ex | 12 ++-- 3 files changed, 54 insertions(+), 67 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 468961bd0..acd61bda3 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1049,8 +1049,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id), {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id), already_ap <- User.ap_enabled?(user), - {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do - unless already_ap do + {:ok, user} <- upgrade_user(user, data) do + if not already_ap do PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user]) end @@ -1061,6 +1061,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end + defp upgrade_user(user, data) do + user + |> User.upgrade_changeset(data) + |> User.update_and_set_cache() + end + def maybe_retire_websub(ap_id) do # some sanity checks if is_binary(ap_id) && String.length(ap_id) > 8 do diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index 331cbc0b7..5de1ceef3 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -3,14 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OStatus do - import Ecto.Query import Pleroma.Web.XML require Logger alias Pleroma.Activity alias Pleroma.HTTP alias Pleroma.Object - alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub @@ -38,21 +36,13 @@ defmodule Pleroma.Web.OStatus do end end - def feed_path(user) do - "#{user.ap_id}/feed.atom" - end + def feed_path(user), do: "#{user.ap_id}/feed.atom" - def pubsub_path(user) do - "#{Web.base_url()}/push/hub/#{user.nickname}" - end + def pubsub_path(user), do: "#{Web.base_url()}/push/hub/#{user.nickname}" - def salmon_path(user) do - "#{user.ap_id}/salmon" - end + def salmon_path(user), do: "#{user.ap_id}/salmon" - def remote_follow_path do - "#{Web.base_url()}/ostatus_subscribe?acct={uri}" - end + def remote_follow_path, do: "#{Web.base_url()}/ostatus_subscribe?acct={uri}" def handle_incoming(xml_string, options \\ []) do with doc when doc != :error <- parse_document(xml_string) do @@ -217,10 +207,9 @@ defmodule Pleroma.Web.OStatus do Get the cw that mastodon uses. """ def get_cw(entry) do - with cw when not is_nil(cw) <- string_from_xpath("/*/summary", entry) do - cw - else - _e -> nil + case string_from_xpath("/*/summary", entry) do + cw when not is_nil(cw) -> cw + _ -> nil end end @@ -232,19 +221,17 @@ defmodule Pleroma.Web.OStatus do end def maybe_update(doc, user) do - if "true" == string_from_xpath("//author[1]/ap_enabled", doc) do - Transmogrifier.upgrade_user_from_ap_id(user.ap_id) - else - maybe_update_ostatus(doc, user) + case string_from_xpath("//author[1]/ap_enabled", doc) do + "true" -> + Transmogrifier.upgrade_user_from_ap_id(user.ap_id) + + _ -> + maybe_update_ostatus(doc, user) end end def maybe_update_ostatus(doc, user) do - old_data = %{ - avatar: user.avatar, - bio: user.bio, - name: user.name - } + old_data = Map.take(user, [:bio, :avatar, :name]) with false <- user.local, avatar <- make_avatar_object(doc), @@ -279,38 +266,37 @@ defmodule Pleroma.Web.OStatus do end end + @spec find_or_make_user(String.t()) :: {:ok, User.t()} def find_or_make_user(uri) do - query = from(user in User, where: user.ap_id == ^uri) - - user = Repo.one(query) - - if is_nil(user) do - make_user(uri) - else - {:ok, user} + case User.get_by_ap_id(uri) do + %User{} = user -> {:ok, user} + _ -> make_user(uri) end end + @spec make_user(String.t(), boolean()) :: {:ok, User.t()} | {:error, any()} def make_user(uri, update \\ false) do with {:ok, info} <- gather_user_info(uri) do - data = %{ - name: info["name"], - nickname: info["nickname"] <> "@" <> info["host"], - ap_id: info["uri"], - info: info, - avatar: info["avatar"], - bio: info["bio"] - } - with false <- update, - %User{} = user <- User.get_cached_by_ap_id(data.ap_id) do + %User{} = user <- User.get_cached_by_ap_id(info["uri"]) do {:ok, user} else - _e -> User.insert_or_update_user(data) + _e -> User.insert_or_update_user(build_user_data(info)) end end end + defp build_user_data(info) do + %{ + name: info["name"], + nickname: info["nickname"] <> "@" <> info["host"], + ap_id: info["uri"], + info: info, + avatar: info["avatar"], + bio: info["bio"] + } + end + # TODO: Just takes the first one for now. def make_avatar_object(author_doc, rel \\ "avatar") do href = string_from_xpath("//author[1]/link[@rel=\"#{rel}\"]/@href", author_doc) @@ -319,23 +305,23 @@ defmodule Pleroma.Web.OStatus do if href do %{ "type" => "Image", - "url" => [ - %{ - "type" => "Link", - "mediaType" => type, - "href" => href - } - ] + "url" => [%{"type" => "Link", "mediaType" => type, "href" => href}] } else nil end end + @spec gather_user_info(String.t()) :: {:ok, map()} | {:error, any()} def gather_user_info(username) do with {:ok, webfinger_data} <- WebFinger.finger(username), {:ok, feed_data} <- Websub.gather_feed_data(webfinger_data["topic"]) do - {:ok, Map.merge(webfinger_data, feed_data) |> Map.put("fqn", username)} + data = + webfinger_data + |> Map.merge(feed_data) + |> Map.put("fqn", username) + + {:ok, data} else e -> Logger.debug(fn -> "Couldn't gather info for #{username}" end) @@ -371,10 +357,7 @@ defmodule Pleroma.Web.OStatus do def fetch_activity_from_atom_url(url, options \\ []) do with true <- String.starts_with?(url, "http"), {:ok, %{body: body, status: code}} when code in 200..299 <- - HTTP.get( - url, - [{:Accept, "application/atom+xml"}] - ) do + HTTP.get(url, [{:Accept, "application/atom+xml"}]) do Logger.debug("Got document from #{url}, handling...") handle_incoming(body, options) else diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 07e2a4c2d..64b2c64b3 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -55,12 +55,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do def feed(conn, %{"nickname" => nickname} = params) do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do - query_params = - Map.take(params, ["max_id"]) - |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id}) - activities = - ActivityPub.fetch_public_activities(query_params) + params + |> Map.take(["max_id"]) + |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id}) + |> ActivityPub.fetch_public_activities() |> Enum.reverse() response = @@ -98,8 +97,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do Federator.incoming_doc(doc) - conn - |> send_resp(200, "") + send_resp(conn, 200, "") end def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid}) -- cgit v1.2.3 From 769fb778d41df77c2514b5e3c663f3f624c0a266 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 12 Sep 2019 21:37:36 +0300 Subject: Track object/create activity fetches --- lib/pleroma/delivery.ex | 58 ++++++++++++++++++++++ lib/pleroma/plugs/cache.ex | 16 +++++- lib/pleroma/user.ex | 10 ++++ .../web/activity_pub/activity_pub_controller.ex | 29 ++++++++++- 4 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 lib/pleroma/delivery.ex (limited to 'lib') diff --git a/lib/pleroma/delivery.ex b/lib/pleroma/delivery.ex new file mode 100644 index 000000000..f9a9e35cd --- /dev/null +++ b/lib/pleroma/delivery.ex @@ -0,0 +1,58 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Delivery do + use Ecto.Schema + + alias Pleroma.Delivery + alias Pleroma.FlakeId + alias Pleroma.User + alias Pleroma.Repo + alias Pleroma.Object + alias Pleroma.User + + import Ecto.Changeset + import Ecto.Query + + schema "deliveries" do + belongs_to(:user, User, type: FlakeId) + belongs_to(:object, Object) + end + + def changeset(delivery, params \\ %{}) do + delivery + |> cast(params, [:user_id, :object_id]) + |> foreign_key_constraint(:object_id) + |> foreign_key_constraint(:user_id) + |> unique_constraint(:user_id, name: :deliveries_user_id_object_id_index) + end + + def create(object_id, user_id) do + %Delivery{} + |> changeset(%{user_id: user_id, object_id: object_id}) + |> Repo.insert() + end + + def get(object_id, user_id) do + from(d in Delivery, where: d.user_id == ^user_id and d.object_id == ^object_id) + |> Repo.one() + end + + def get_or_create(object_id, user_id) do + case get(object_id, user_id) do + %Delivery{} = delivery -> {:ok, delivery} + nil -> create(object_id, user_id) + end + end + + def delete_all_by_object_id(object_id) do + from(d in Delivery, where: d.object_id == ^object_id) + |> Repo.delete_all() + end + + def get_all_by_object_id(object_id) do + from(d in Delivery, where: d.object_id == ^object_id) + |> Repo.all() + end +end diff --git a/lib/pleroma/plugs/cache.ex b/lib/pleroma/plugs/cache.ex index a81a861d0..42d77fc1f 100644 --- a/lib/pleroma/plugs/cache.ex +++ b/lib/pleroma/plugs/cache.ex @@ -20,6 +20,7 @@ defmodule Pleroma.Plugs.Cache do - `ttl`: An expiration time (time-to-live). This value should be in milliseconds or `nil` to disable expiration. Defaults to `nil`. - `query_params`: Take URL query string into account (`true`), ignore it (`false`) or limit to specific params only (list). Defaults to `true`. + - `tracking_fun`: A function that is called on successfull responses, no matter if the request is cached or not. It should accept a conn as the first argument and the value assigned to `tracking_fun_data` as the second. Additionally, you can overwrite the TTL inside a controller action by assigning `cache_ttl` to the connection struct: @@ -56,6 +57,10 @@ defmodule Pleroma.Plugs.Cache do {:ok, nil} -> cache_resp(conn, opts) + {:ok, {content_type, body, tracking_fun_data}} -> + conn = opts.tracking_fun(conn, tracking_fun_data) + send_cached(conn, {content_type, body}) + {:ok, record} -> send_cached(conn, record) @@ -90,7 +95,16 @@ defmodule Pleroma.Plugs.Cache do content_type = content_type(conn) record = {content_type, body} - Cachex.put(:web_resp_cache, key, record, ttl: ttl) + conn = + unless opts[:tracking_fun] do + Cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl) + conn + else + tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil) + Cachex.put(:web_resp_cache, {content_type, body, tracking_fun_data}, record, ttl: ttl) + + opts.tracking_fun.(conn, tracking_fun_data) + end put_resp_header(conn, "x-cache", "MISS from Pleroma") diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3aa245f2a..9614acdab 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -11,6 +11,7 @@ defmodule Pleroma.User do alias Comeonin.Pbkdf2 alias Ecto.Multi alias Pleroma.Activity + alias Pleroma.Delivery alias Pleroma.Keys alias Pleroma.Notification alias Pleroma.Object @@ -61,6 +62,7 @@ defmodule Pleroma.User do field(:last_digest_emailed_at, :naive_datetime) has_many(:notifications, Notification) has_many(:registrations, Registration) + has_many(:deliveries, Delivery) embeds_one(:info, User.Info) timestamps() @@ -1624,4 +1626,12 @@ defmodule Pleroma.User do def is_internal_user?(%User{nickname: nil}), do: true def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true def is_internal_user?(_), do: false + + def get_delivered_users_by_object_id(object_id) do + from(u in User, + inner_join: delivery in assoc(u, :deliveries), + where: delivery.object_id == ^object_id + ) + |> Repo.all() + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 705dbc1c2..009260d3f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do use Pleroma.Web, :controller alias Pleroma.Activity + alias Pleroma.Delivery alias Pleroma.Object alias Pleroma.Object.Fetcher alias Pleroma.User @@ -23,7 +24,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do action_fallback(:errors) - plug(Pleroma.Plugs.Cache, [query_params: false] when action in [:activity, :object]) + plug( + Pleroma.Plugs.Cache, + [ + query_params: false, + tracking_fun: &Pleroma.Web.ActivityPub.ActivityPubController.track_object_fetch/2 + ] + when action in [:activity, :object] + ) + plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay]) plug(:set_requester_reachable when action in [:inbox]) plug(:relay_active? when action in [:relay]) @@ -54,6 +63,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do %Object{} = object <- Object.get_cached_by_ap_id(ap_id), {_, true} <- {:public?, Visibility.is_public?(object)} do conn + |> assign(:tracking_fun_data, object.id) |> set_cache_ttl_for(object) |> put_resp_content_type("application/activity+json") |> put_view(ObjectView) @@ -64,6 +74,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end + def track_object_fetch(conn, object_id) do + case conn.assigns[:user] do + %User{id: user_id} -> Delivery.create(object_id, user_id) + _ -> nil + end + + conn + end + def object_likes(conn, %{"uuid" => uuid, "page" => page}) do with ap_id <- o_status_url(conn, :object, uuid), %Object{} = object <- Object.get_cached_by_ap_id(ap_id), @@ -99,6 +118,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do %Activity{} = activity <- Activity.normalize(ap_id), {_, true} <- {:public?, Visibility.is_public?(activity)} do conn + |> maybe_set_tracking_data(activity) |> set_cache_ttl_for(activity) |> put_resp_content_type("application/activity+json") |> put_view(ObjectView) @@ -109,6 +129,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end + defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do + object_id = Object.normalize(activity).id + assign(conn, :tracking_fun_data, object_id) + end + + defp maybe_set_tracking_data(conn, _activity), do: assign(conn, :tracking_fun_data, nil) + defp set_cache_ttl_for(conn, %Activity{object: object}) do set_cache_ttl_for(conn, object) end -- cgit v1.2.3 From dabc4a00f5cf08dac75f701457a24fce8735175f Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 12 Sep 2019 22:10:15 +0300 Subject: Put the cache with the right key when using a tracking function --- lib/pleroma/plugs/cache.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/cache.ex b/lib/pleroma/plugs/cache.ex index 42d77fc1f..50b534e7b 100644 --- a/lib/pleroma/plugs/cache.ex +++ b/lib/pleroma/plugs/cache.ex @@ -58,7 +58,8 @@ defmodule Pleroma.Plugs.Cache do cache_resp(conn, opts) {:ok, {content_type, body, tracking_fun_data}} -> - conn = opts.tracking_fun(conn, tracking_fun_data) + conn = opts.tracking_fun.(conn, tracking_fun_data) + send_cached(conn, {content_type, body}) {:ok, record} -> @@ -93,7 +94,6 @@ defmodule Pleroma.Plugs.Cache do ttl = Map.get(conn.assigns, :cache_ttl, opts.ttl) key = cache_key(conn, opts) content_type = content_type(conn) - record = {content_type, body} conn = unless opts[:tracking_fun] do @@ -101,7 +101,7 @@ defmodule Pleroma.Plugs.Cache do conn else tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil) - Cachex.put(:web_resp_cache, {content_type, body, tracking_fun_data}, record, ttl: ttl) + Cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl) opts.tracking_fun.(conn, tracking_fun_data) end -- cgit v1.2.3 From b0e60580215e26caae6452099fa1fbace525937c Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 12 Sep 2019 22:40:53 +0300 Subject: Parse http signature for request to objects/activities --- lib/pleroma/plugs/http_signature.ex | 3 ++- lib/pleroma/web/router.ex | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex index d87fa52fa..23d22a712 100644 --- a/lib/pleroma/plugs/http_signature.ex +++ b/lib/pleroma/plugs/http_signature.ex @@ -15,7 +15,8 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do end def call(conn, _opts) do - [signature | _] = get_req_header(conn, "signature") + headers = get_req_header(conn, "signature") + signature = Enum.at(headers, 0) if signature do # set (request-target) header to the appropriate value diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7cd59acb2..badc7e048 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -135,6 +135,7 @@ defmodule Pleroma.Web.Router do pipeline :http_signature do plug(Pleroma.Web.Plugs.HTTPSignaturePlug) + plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug) end scope "/api/pleroma", Pleroma.Web.TwitterAPI do @@ -513,6 +514,7 @@ defmodule Pleroma.Web.Router do scope "/", Pleroma.Web do pipe_through(:ostatus) + pipe_through(:http_signature) get("/objects/:uuid", OStatus.OStatusController, :object) get("/activities/:uuid", OStatus.OStatusController, :activity) -- cgit v1.2.3 From d8a178274bd1eb642270e52f207849014cba12bc Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 13 Sep 2019 07:12:34 +0300 Subject: fix Activity.get_by_id --- lib/pleroma/activity.ex | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 2d4e9da0c..56c51aef8 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -150,11 +150,18 @@ defmodule Pleroma.Activity do ) end + @spec get_by_id(String.t()) :: Activity.t() | nil def get_by_id(id) do - Activity - |> where([a], a.id == ^id) - |> restrict_deactivated_users() - |> Repo.one() + case Pleroma.FlakeId.is_flake_id?(id) do + true -> + Activity + |> where([a], a.id == ^id) + |> restrict_deactivated_users() + |> Repo.one() + + _ -> + nil + end end def get_by_id_with_object(id) do -- cgit v1.2.3 From 39dc9b470c7ad8348a13f181039f11d14a42fa2b Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 3 Sep 2019 21:58:30 +0700 Subject: Cleanup Pleroma.Activity and Pleroma.Web.ActivityPub.Utils --- lib/pleroma/activity.ex | 193 ++++++++-------------------------- lib/pleroma/activity/queries.ex | 32 ++++-- lib/pleroma/user.ex | 2 +- lib/pleroma/web/activity_pub/utils.ex | 167 +++++++++++------------------ 4 files changed, 127 insertions(+), 267 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 44f1e3011..ec558168a 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Activity do use Ecto.Schema alias Pleroma.Activity + alias Pleroma.Activity.Queries alias Pleroma.ActivityExpiration alias Pleroma.Bookmark alias Pleroma.Notification @@ -65,8 +66,8 @@ defmodule Pleroma.Activity do timestamps() end - def with_joined_object(query) do - join(query, :inner, [activity], o in Object, + def with_joined_object(query, join_type \\ :inner) do + join(query, join_type, [activity], o in Object, on: fragment( "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", @@ -78,10 +79,10 @@ defmodule Pleroma.Activity do ) end - def with_preloaded_object(query) do + def with_preloaded_object(query, join_type \\ :inner) do query |> has_named_binding?(:object) - |> if(do: query, else: with_joined_object(query)) + |> if(do: query, else: with_joined_object(query, join_type)) |> preload([activity, object: object], object: object) end @@ -107,12 +108,9 @@ defmodule Pleroma.Activity do def with_set_thread_muted_field(query, _), do: query def get_by_ap_id(ap_id) do - Repo.one( - from( - activity in Activity, - where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)) - ) - ) + ap_id + |> Queries.by_ap_id() + |> Repo.one() end def get_bookmark(%Activity{} = activity, %User{} = user) do @@ -133,21 +131,10 @@ defmodule Pleroma.Activity do end def get_by_ap_id_with_object(ap_id) do - Repo.one( - from( - activity in Activity, - where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)), - left_join: o in Object, - on: - fragment( - "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", - o.data, - activity.data, - activity.data - ), - preload: [object: o] - ) - ) + ap_id + |> Queries.by_ap_id() + |> with_preloaded_object(:left) + |> Repo.one() end def get_by_id(id) do @@ -158,18 +145,9 @@ defmodule Pleroma.Activity do end def get_by_id_with_object(id) do - from(activity in Activity, - where: activity.id == ^id, - inner_join: o in Object, - on: - fragment( - "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", - o.data, - activity.data, - activity.data - ), - preload: [object: o] - ) + Activity + |> where(id: ^id) + |> with_preloaded_object() |> Repo.one() end @@ -180,51 +158,21 @@ defmodule Pleroma.Activity do |> Repo.all() end - def by_object_ap_id(ap_id) do - from( - activity in Activity, - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - activity.data, - activity.data, - ^to_string(ap_id) - ) - ) - end - - def create_by_object_ap_id(ap_ids) when is_list(ap_ids) do - from( - activity in Activity, - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)", - activity.data, - activity.data, - ^ap_ids - ), - where: fragment("(?)->>'type' = 'Create'", activity.data) - ) - end - - def create_by_object_ap_id(ap_id) when is_binary(ap_id) do - from( - activity in Activity, - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - activity.data, - activity.data, - ^to_string(ap_id) - ), - where: fragment("(?)->>'type' = 'Create'", activity.data) - ) + @doc """ + Accepts `ap_id` or list of `ap_id`. + Returns a query. + """ + @spec create_by_object_ap_id(String.t() | [String.t()]) :: Ecto.Queryable.t() + def create_by_object_ap_id(ap_id) do + ap_id + |> Queries.by_object_id() + |> Queries.by_type("Create") end - def create_by_object_ap_id(_), do: nil - def get_all_create_by_object_ap_id(ap_id) do - Repo.all(create_by_object_ap_id(ap_id)) + ap_id + |> create_by_object_ap_id() + |> Repo.all() end def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do @@ -235,54 +183,17 @@ defmodule Pleroma.Activity do def get_create_by_object_ap_id(_), do: nil - def create_by_object_ap_id_with_object(ap_ids) when is_list(ap_ids) do - from( - activity in Activity, - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)", - activity.data, - activity.data, - ^ap_ids - ), - where: fragment("(?)->>'type' = 'Create'", activity.data), - inner_join: o in Object, - on: - fragment( - "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", - o.data, - activity.data, - activity.data - ), - preload: [object: o] - ) - end - - def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do - from( - activity in Activity, - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - activity.data, - activity.data, - ^to_string(ap_id) - ), - where: fragment("(?)->>'type' = 'Create'", activity.data), - inner_join: o in Object, - on: - fragment( - "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')", - o.data, - activity.data, - activity.data - ), - preload: [object: o] - ) + @doc """ + Accepts `ap_id` or list of `ap_id`. + Returns a query. + """ + @spec create_by_object_ap_id_with_object(String.t() | [String.t()]) :: Ecto.Queryable.t() + def create_by_object_ap_id_with_object(ap_id) do + ap_id + |> create_by_object_ap_id() + |> with_preloaded_object() end - def create_by_object_ap_id_with_object(_), do: nil - def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do ap_id |> create_by_object_ap_id_with_object() @@ -306,7 +217,8 @@ defmodule Pleroma.Activity do def normalize(_), do: nil def delete_by_ap_id(id) when is_binary(id) do - by_object_ap_id(id) + id + |> Queries.by_object_id() |> select([u], u) |> Repo.delete_all() |> elem(1) @@ -350,31 +262,10 @@ defmodule Pleroma.Activity do end def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do - from( - a in Activity, - where: - fragment( - "? ->> 'type' = 'Follow'", - a.data - ), - where: - fragment( - "? ->> 'state' = 'pending'", - a.data - ), - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - a.data, - a.data, - ^ap_id - ) - ) - end - - @spec query_by_actor(actor()) :: Ecto.Query.t() - def query_by_actor(actor) do - from(a in Activity, where: a.actor == ^actor) + ap_id + |> Queries.by_object_id() + |> Queries.by_type("Follow") + |> where([a], fragment("? ->> 'state' = 'pending'", a.data)) end def restrict_deactivated_users(query) do diff --git a/lib/pleroma/activity/queries.ex b/lib/pleroma/activity/queries.ex index aa5b29566..13fa33831 100644 --- a/lib/pleroma/activity/queries.ex +++ b/lib/pleroma/activity/queries.ex @@ -13,6 +13,14 @@ defmodule Pleroma.Activity.Queries do alias Pleroma.Activity + @spec by_ap_id(query, String.t()) :: query + def by_ap_id(query \\ Activity, ap_id) do + from( + activity in query, + where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)) + ) + end + @spec by_actor(query, String.t()) :: query def by_actor(query \\ Activity, actor) do from( @@ -21,8 +29,23 @@ defmodule Pleroma.Activity.Queries do ) end - @spec by_object_id(query, String.t()) :: query - def by_object_id(query \\ Activity, object_id) do + @spec by_object_id(query, String.t() | [String.t()]) :: query + def by_object_id(query \\ Activity, object_id) + + def by_object_id(query, object_ids) when is_list(object_ids) do + from( + activity in query, + where: + fragment( + "coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)", + activity.data, + activity.data, + ^object_ids + ) + ) + end + + def by_object_id(query, object_id) when is_binary(object_id) do from(activity in query, where: fragment( @@ -41,9 +64,4 @@ defmodule Pleroma.Activity.Queries do where: fragment("(?)->>'type' = ?", activity.data, ^activity_type) ) end - - @spec limit(query, pos_integer()) :: query - def limit(query \\ Activity, limit) do - from(activity in query, limit: ^limit) - end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3aa245f2a..ceca11def 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1219,7 +1219,7 @@ defmodule Pleroma.User do def delete_user_activities(%User{ap_id: ap_id} = user) do ap_id - |> Activity.query_by_actor() + |> Activity.Queries.by_actor() |> RepoStreamer.chunk_stream(50) |> Stream.each(fn activities -> Enum.each(activities, &delete_activity(&1)) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index c9c0c3763..47917f5d3 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -85,15 +85,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do defp extract_list(_), do: [] def maybe_splice_recipient(ap_id, params) do - need_splice = + need_splice? = !recipient_in_collection(ap_id, params["to"]) && !recipient_in_collection(ap_id, params["cc"]) - cc_list = extract_list(params["cc"]) - - if need_splice do - params - |> Map.put("cc", [ap_id | cc_list]) + if need_splice? do + cc_list = extract_list(params["cc"]) + Map.put(params, "cc", [ap_id | cc_list]) else params end @@ -139,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do "object" => object } - Notification.get_notified_from_activity(%Activity{data: fake_create_activity}, false) + get_notified_from_object(fake_create_activity) end def get_notified_from_object(object) do @@ -188,9 +186,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do Adds an id and a published data if they aren't there, also adds it to an included object """ - def lazy_put_activity_defaults(map, fake \\ false) do + def lazy_put_activity_defaults(map, fake? \\ false) do map = - unless fake do + if not fake? do %{data: %{"id" => context}, id: context_id} = create_context(map["context"]) map @@ -207,7 +205,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do end if is_map(map["object"]) do - object = lazy_put_object_defaults(map["object"], map, fake) + object = lazy_put_object_defaults(map["object"], map, fake?) %{map | "object" => object} else map @@ -217,9 +215,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Adds an id and published date if they aren't there. """ - def lazy_put_object_defaults(map, activity \\ %{}, fake) + def lazy_put_object_defaults(map, activity \\ %{}, fake?) - def lazy_put_object_defaults(map, activity, true = _fake) do + def lazy_put_object_defaults(map, activity, true = _fake?) do map |> Map.put_new_lazy("published", &make_date/0) |> Map.put_new("id", "pleroma:fake_object_id") @@ -228,7 +226,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Map.put_new("context_id", activity["context_id"]) end - def lazy_put_object_defaults(map, activity, _fake) do + def lazy_put_object_defaults(map, activity, _fake?) do map |> Map.put_new_lazy("id", &generate_object_id/0) |> Map.put_new_lazy("published", &make_date/0) @@ -242,9 +240,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do def insert_full_object(%{"object" => %{"type" => type} = object_data} = map) when is_map(object_data) and type in @supported_object_types do with {:ok, object} <- Object.create(object_data) do - map = - map - |> Map.put("object", object.data["id"]) + map = Map.put(map, "object", object.data["id"]) {:ok, map, object} end @@ -263,7 +259,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Activity.Queries.by_actor() |> Activity.Queries.by_object_id(id) |> Activity.Queries.by_type("Like") - |> Activity.Queries.limit(1) + |> limit(1) |> Repo.one() end @@ -380,12 +376,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do %Activity{data: %{"actor" => actor, "object" => object}} = activity, state ) do - with new_data <- - activity.data - |> Map.put("state", state), - changeset <- Changeset.change(activity, data: new_data), - {:ok, activity} <- Repo.update(changeset), - _ <- User.set_follow_state_cache(actor, object, state) do + new_data = Map.put(activity.data, "state", state) + changeset = Changeset.change(activity, data: new_data) + + with {:ok, activity} <- Repo.update(changeset) do + User.set_follow_state_cache(actor, object, state) {:ok, activity} end end @@ -410,28 +405,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do end def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do - query = - from( - activity in Activity, - where: - fragment( - "? ->> 'type' = 'Follow'", - activity.data - ), - where: activity.actor == ^follower_id, - # this is to use the index - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - activity.data, - activity.data, - ^followed_id - ), - order_by: [fragment("? desc nulls last", activity.id)], - limit: 1 - ) - - Repo.one(query) + "Follow" + |> Activity.Queries.by_type() + |> where(actor: ^follower_id) + # this is to use the index + |> Activity.Queries.by_object_id(followed_id) + |> order_by([activity], fragment("? desc nulls last", activity.id)) + |> limit(1) + |> Repo.one() end #### Announce-related helpers @@ -439,23 +420,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Retruns an existing announce activity if the notice has already been announced """ - def get_existing_announce(actor, %{data: %{"id" => id}}) do - query = - from( - activity in Activity, - where: activity.actor == ^actor, - # this is to use the index - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - activity.data, - activity.data, - ^id - ), - where: fragment("(?)->>'type' = 'Announce'", activity.data) - ) - - Repo.one(query) + def get_existing_announce(actor, %{data: %{"id" => ap_id}}) do + "Announce" + |> Activity.Queries.by_type() + |> where(actor: ^actor) + # this is to use the index + |> Activity.Queries.by_object_id(ap_id) + |> Repo.one() end @doc """ @@ -538,11 +509,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do object ) do announcements = - if is_list(object.data["announcements"]), do: object.data["announcements"], else: [] + if is_list(object.data["announcements"]) do + Enum.uniq([actor | object.data["announcements"]]) + else + [actor] + end - with announcements <- [actor | announcements] |> Enum.uniq() do - update_element_in_object("announcement", announcements, object) - end + update_element_in_object("announcement", announcements, object) end def add_announce_to_object(_, object), do: {:ok, object} @@ -570,28 +543,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do #### Block-related helpers def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do - query = - from( - activity in Activity, - where: - fragment( - "? ->> 'type' = 'Block'", - activity.data - ), - where: activity.actor == ^blocker_id, - # this is to use the index - where: - fragment( - "coalesce((?)->'object'->>'id', (?)->>'object') = ?", - activity.data, - activity.data, - ^blocked_id - ), - order_by: [fragment("? desc nulls last", activity.id)], - limit: 1 - ) - - Repo.one(query) + "Block" + |> Activity.Queries.by_type() + |> where(actor: ^blocker_id) + # this is to use the index + |> Activity.Queries.by_object_id(blocked_id) + |> order_by([activity], fragment("? desc nulls last", activity.id)) + |> limit(1) + |> Repo.one() end def make_block_data(blocker, blocked, activity_id) do @@ -695,11 +654,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do #### Report-related helpers def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do - with new_data <- Map.put(activity.data, "state", state), - changeset <- Changeset.change(activity, data: new_data), - {:ok, activity} <- Repo.update(changeset) do - {:ok, activity} - end + new_data = Map.put(activity.data, "state", state) + + activity + |> Changeset.change(data: new_data) + |> Repo.update() end def update_report_state(_, _), do: {:error, "Unsupported state"} @@ -766,21 +725,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do end def get_existing_votes(actor, %{data: %{"id" => id}}) do - query = - from( - [activity, object: object] in Activity.with_preloaded_object(Activity), - where: fragment("(?)->>'type' = 'Create'", activity.data), - where: fragment("(?)->>'actor' = ?", activity.data, ^actor), - where: - fragment( - "(?)->>'inReplyTo' = ?", - object.data, - ^to_string(id) - ), - where: fragment("(?)->>'type' = 'Answer'", object.data) - ) - - Repo.all(query) + actor + |> Activity.Queries.by_actor() + |> Activity.Queries.by_type("Create") + |> Activity.with_preloaded_object() + |> where([a, object: o], fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(id))) + |> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data)) + |> Repo.all() end defp maybe_put(map, _key, nil), do: map -- cgit v1.2.3 From ce23529d917c1830b270a29e774e4ed7768bfeff Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 13 Sep 2019 11:36:49 +0300 Subject: Use delivery info when federating deletes --- lib/pleroma/delivery.ex | 4 ++++ lib/pleroma/user.ex | 4 ++++ lib/pleroma/web/activity_pub/publisher.ex | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/delivery.ex b/lib/pleroma/delivery.ex index f9a9e35cd..2e7c019fa 100644 --- a/lib/pleroma/delivery.ex +++ b/lib/pleroma/delivery.ex @@ -46,6 +46,10 @@ defmodule Pleroma.Delivery do end end + # A hack because user delete activities have a fake id for whatever reason + # TODO: Get rid of this + def delete_all_by_object_id("pleroma:fake_object_id"), do: {0, []} + def delete_all_by_object_id(object_id) do from(d in Delivery, where: d.object_id == ^object_id) |> Repo.delete_all() diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 9614acdab..785b22643 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1627,6 +1627,10 @@ defmodule Pleroma.User do def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true def is_internal_user?(_), do: false + # A hack because user delete activities have a fake id for whatever reason + # TODO: Get rid of this + def get_delivered_users_by_object_id("pleroma:fake_object_id"), do: [] + def get_delivered_users_by_object_id(object_id) do from(u in User, inner_join: delivery in assoc(u, :deliveries), diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index c97405690..db64fd2f6 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -5,9 +5,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.Activity alias Pleroma.Config + alias Pleroma.Delivery alias Pleroma.HTTP alias Pleroma.Instances alias Pleroma.User + alias Pleroma.Object alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier @@ -107,7 +109,18 @@ defmodule Pleroma.Web.ActivityPub.Publisher do {:ok, []} end - Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers + fetchers = + with %Activity{data: %{"type" => "Delete"}} <- activity, + %Object{id: object_id} <- Object.normalize(activity), + fetchers <- User.get_delivered_users_by_object_id(object_id), + _ <- Delivery.delete_all_by_object_id(object_id) do + fetchers + else + _ -> + [] + end + + Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers ++ fetchers end defp get_cc_ap_ids(ap_id, recipients) do -- cgit v1.2.3 From fb96facc32fb275efffeefa2892a1098ecd68b77 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 13 Sep 2019 12:06:31 +0300 Subject: Remove unused functions and fix credo issues --- lib/pleroma/delivery.ex | 16 ++-------------- lib/pleroma/web/activity_pub/publisher.ex | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/delivery.ex b/lib/pleroma/delivery.ex index 2e7c019fa..ce8fb96f4 100644 --- a/lib/pleroma/delivery.ex +++ b/lib/pleroma/delivery.ex @@ -7,9 +7,9 @@ defmodule Pleroma.Delivery do alias Pleroma.Delivery alias Pleroma.FlakeId - alias Pleroma.User - alias Pleroma.Repo alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.User alias Pleroma.User import Ecto.Changeset @@ -39,13 +39,6 @@ defmodule Pleroma.Delivery do |> Repo.one() end - def get_or_create(object_id, user_id) do - case get(object_id, user_id) do - %Delivery{} = delivery -> {:ok, delivery} - nil -> create(object_id, user_id) - end - end - # A hack because user delete activities have a fake id for whatever reason # TODO: Get rid of this def delete_all_by_object_id("pleroma:fake_object_id"), do: {0, []} @@ -54,9 +47,4 @@ defmodule Pleroma.Delivery do from(d in Delivery, where: d.object_id == ^object_id) |> Repo.delete_all() end - - def get_all_by_object_id(object_id) do - from(d in Delivery, where: d.object_id == ^object_id) - |> Repo.all() - end end diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index db64fd2f6..c39e89a6a 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.Delivery alias Pleroma.HTTP alias Pleroma.Instances - alias Pleroma.User alias Pleroma.Object + alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier -- cgit v1.2.3 From 517017048316a52172d60d26b03beddb85af7b39 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 13 Sep 2019 10:09:46 +0000 Subject: Apply suggestion to lib/pleroma/web/activity_pub/activity_pub_controller.ex --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 009260d3f..025641722 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -26,10 +26,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do plug( Pleroma.Plugs.Cache, - [ - query_params: false, - tracking_fun: &Pleroma.Web.ActivityPub.ActivityPubController.track_object_fetch/2 - ] + [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2] when action in [:activity, :object] ) -- cgit v1.2.3 From 3896a51b8aefe6fe54251ffd559c636980faa87e Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 13 Sep 2019 10:09:56 +0000 Subject: Apply suggestion to lib/pleroma/web/activity_pub/activity_pub_controller.ex --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 025641722..4bd13defb 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -72,9 +72,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end def track_object_fetch(conn, object_id) do - case conn.assigns[:user] do - %User{id: user_id} -> Delivery.create(object_id, user_id) - _ -> nil + with %{assigns: %{user: %User{id: user_id}}} <- conn do + Delivery.create(object_id, user_id) end conn -- cgit v1.2.3 From 2784962dba295ee35677e93996df53d1711e5768 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 13 Sep 2019 15:23:03 +0000 Subject: Apply suggestion to lib/pleroma/web/activity_pub/activity_pub_controller.ex --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 4bd13defb..70d4a5baf 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -130,7 +130,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do assign(conn, :tracking_fun_data, object_id) end - defp maybe_set_tracking_data(conn, _activity), do: assign(conn, :tracking_fun_data, nil) + defp maybe_set_tracking_data(conn, _activity), do: conn defp set_cache_ttl_for(conn, %Activity{object: object}) do set_cache_ttl_for(conn, object) -- cgit v1.2.3 From 05f8a066a107af2f7151aee8d85af97cf6a4835c Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 13 Sep 2019 15:23:26 +0000 Subject: Apply suggestion to lib/pleroma/delivery.ex --- lib/pleroma/delivery.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/delivery.ex b/lib/pleroma/delivery.ex index ce8fb96f4..38c148c34 100644 --- a/lib/pleroma/delivery.ex +++ b/lib/pleroma/delivery.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Delivery do def create(object_id, user_id) do %Delivery{} |> changeset(%{user_id: user_id, object_id: object_id}) - |> Repo.insert() + |> Repo.insert(on_conflict: :nothing) end def get(object_id, user_id) do -- cgit v1.2.3 From 8900cb68aef535dbf60de87fce47d85b91909077 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 13 Sep 2019 15:25:15 +0000 Subject: Apply suggestion to lib/pleroma/web/activity_pub/activity_pub_controller.ex --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 70d4a5baf..01b34fb1d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -71,6 +71,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end + def track_object_fetch(conn, nil), do: conn + def track_object_fetch(conn, object_id) do with %{assigns: %{user: %User{id: user_id}}} <- conn do Delivery.create(object_id, user_id) -- cgit v1.2.3 From 0bd2b85edbf3b7062570778649cf2b77cc7a0bce Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Fri, 13 Sep 2019 18:25:27 +0300 Subject: Separate Subscription Notifications from regular Notifications --- lib/pleroma/notification.ex | 1 - lib/pleroma/subscription_notification.ex | 266 +++++++++++++++++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 2 + .../controllers/mastodon_api_controller.ex | 48 ++++ lib/pleroma/web/mastodon_api/mastodon_api.ex | 10 + .../views/subscription_notification_view.ex | 61 +++++ .../web/pleroma_api/pleroma_api_controller.ex | 26 ++ lib/pleroma/web/push/impl.ex | 3 +- lib/pleroma/web/router.ex | 28 +++ lib/pleroma/web/streamer.ex | 14 +- 10 files changed, 454 insertions(+), 5 deletions(-) create mode 100644 lib/pleroma/subscription_notification.ex create mode 100644 lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b7c880c51..716d98733 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -228,7 +228,6 @@ defmodule Pleroma.Notification do [] |> Utils.maybe_notify_to_recipients(activity) |> Utils.maybe_notify_mentioned_recipients(activity) - |> Utils.maybe_notify_subscribers(activity) |> Enum.uniq() User.get_users_from_set(recipients, local_only) diff --git a/lib/pleroma/subscription_notification.ex b/lib/pleroma/subscription_notification.ex new file mode 100644 index 000000000..7ae25a7b1 --- /dev/null +++ b/lib/pleroma/subscription_notification.ex @@ -0,0 +1,266 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.SubscriptionNotification do + use Ecto.Schema + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Pagination + alias Pleroma.Repo + alias Pleroma.SubscriptionNotification + alias Pleroma.User + alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.Push + alias Pleroma.Web.Streamer + + import Ecto.Query + import Ecto.Changeset + + @type t :: %__MODULE__{} + + schema "subscription_notifications" do + belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:activity, Activity, type: Pleroma.FlakeId) + + timestamps() + end + + def changeset(%SubscriptionNotification{} = notification, attrs) do + cast(notification, attrs, []) + end + + def for_user_query(user, opts \\ []) do + query = + SubscriptionNotification + |> where(user_id: ^user.id) + |> where( + [n, a], + fragment( + "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')", + a.actor + ) + ) + |> join(:inner, [n], activity in assoc(n, :activity)) + |> join(:left, [n, a], object in Object, + on: + fragment( + "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)", + object.data, + a.data + ) + ) + |> preload([n, a, o], activity: {a, object: o}) + + if opts[:with_muted] do + query + else + where(query, [n, a], a.actor not in ^user.info.muted_notifications) + |> where([n, a], a.actor not in ^user.info.blocks) + |> where( + [n, a], + fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks + ) + |> join(:left, [n, a], tm in Pleroma.ThreadMute, + on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data) + ) + |> where([n, a, o, tm], is_nil(tm.user_id)) + end + end + + def for_user(user, opts \\ %{}) do + user + |> for_user_query(opts) + |> Pagination.fetch_paginated(opts) + end + + @doc """ + Returns notifications for user received since given date. + + ## Examples + + iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33]) + [%Pleroma.SubscriptionNotification{}, %Pleroma.SubscriptionNotification{}] + + iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33]) + [] + """ + @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()] + def for_user_since(user, date) do + from(n in for_user_query(user), + where: n.updated_at > ^date + ) + |> Repo.all() + end + + def clear_up_to(%{id: user_id} = _user, id) do + from( + n in SubscriptionNotification, + where: n.user_id == ^user_id, + where: n.id <= ^id + ) + |> Repo.delete_all([]) + end + + def get(%{id: user_id} = _user, id) do + query = + from( + n in SubscriptionNotification, + where: n.id == ^id, + join: activity in assoc(n, :activity), + preload: [activity: activity] + ) + + notification = Repo.one(query) + + case notification do + %{user_id: ^user_id} -> + {:ok, notification} + + _ -> + {:error, "Cannot get notification"} + end + end + + def clear(user) do + from(n in SubscriptionNotification, where: n.user_id == ^user.id) + |> Repo.delete_all() + end + + def destroy_multiple(%{id: user_id} = _user, ids) do + from(n in SubscriptionNotification, + where: n.id in ^ids, + where: n.user_id == ^user_id + ) + |> Repo.delete_all() + end + + def dismiss(%{id: user_id} = _user, id) do + notification = Repo.get(SubscriptionNotification, id) + + case notification do + %{user_id: ^user_id} -> + Repo.delete(notification) + + _ -> + {:error, "Cannot dismiss notification"} + end + end + + def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do + object = Object.normalize(activity) + + unless object && object.data["type"] == "Answer" do + users = get_notified_from_activity(activity) + notifications = Enum.map(users, fn user -> create_notification(activity, user) end) + {:ok, notifications} + else + {:ok, []} + end + end + + def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity) + when type in ["Like", "Announce", "Follow"] do + users = get_notified_from_activity(activity) + notifications = Enum.map(users, fn user -> create_notification(activity, user) end) + {:ok, notifications} + end + + def create_notifications(_), do: {:ok, []} + + # TODO move to sql, too. + def create_notification(%Activity{} = activity, %User{} = user) do + unless skip?(activity, user) do + notification = %SubscriptionNotification{user_id: user.id, activity: activity} + {:ok, notification} = Repo.insert(notification) + Streamer.stream("user", notification) + Streamer.stream("user:subscription_notification", notification) + Push.send(notification) + notification + end + end + + def get_notified_from_activity(activity, local_only \\ true) + + def get_notified_from_activity( + %Activity{data: %{"to" => _, "type" => type} = _data} = activity, + local_only + ) + when type in ["Create", "Like", "Announce", "Follow"] do + recipients = + [] + |> Utils.maybe_notify_subscribers(activity) + |> Enum.uniq() + + User.get_users_from_set(recipients, local_only) + end + + def get_notified_from_activity(_, _local_only), do: [] + + @spec skip?(Activity.t(), User.t()) :: boolean() + def skip?(activity, user) do + [ + :self, + :followers, + :follows, + :non_followers, + :non_follows, + :recently_followed + ] + |> Enum.any?(&skip?(&1, activity, user)) + end + + @spec skip?(atom(), Activity.t(), User.t()) :: boolean() + def skip?(:self, activity, user) do + activity.data["actor"] == user.ap_id + end + + def skip?( + :followers, + activity, + %{info: %{notification_settings: %{"followers" => false}}} = user + ) do + actor = activity.data["actor"] + follower = User.get_cached_by_ap_id(actor) + User.following?(follower, user) + end + + def skip?( + :non_followers, + activity, + %{info: %{notification_settings: %{"non_followers" => false}}} = user + ) do + actor = activity.data["actor"] + follower = User.get_cached_by_ap_id(actor) + !User.following?(follower, user) + end + + def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do + actor = activity.data["actor"] + followed = User.get_cached_by_ap_id(actor) + User.following?(user, followed) + end + + def skip?( + :non_follows, + activity, + %{info: %{notification_settings: %{"non_follows" => false}}} = user + ) do + actor = activity.data["actor"] + followed = User.get_cached_by_ap_id(actor) + !User.following?(user, followed) + end + + def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do + actor = activity.data["actor"] + + SubscriptionNotification.for_user(user) + |> Enum.any?(fn + %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true + _ -> false + end) + end + + def skip?(_, _, _), do: false +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index d23ec66ac..bc9a7a2d6 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Object.Fetcher alias Pleroma.Pagination alias Pleroma.Repo + alias Pleroma.SubscriptionNotification alias Pleroma.Upload alias Pleroma.User alias Pleroma.Web.ActivityPub.MRF @@ -148,6 +149,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do PleromaJobQueue.enqueue(:background, Pleroma.Web.RichMedia.Helpers, [:fetch, activity]) Notification.create_notifications(activity) + SubscriptionNotification.create_notifications(activity) participations = activity diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index c54462bb3..3730c962c 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -23,6 +23,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Repo alias Pleroma.ScheduledActivity alias Pleroma.Stats + alias Pleroma.SubscriptionNotification alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub @@ -39,6 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.MastodonAPI.ReportView alias Pleroma.Web.MastodonAPI.ScheduledActivityView alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.MastodonAPI.SubscriptionNotificationView alias Pleroma.Web.MediaProxy alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization @@ -725,6 +727,28 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> render("index.json", %{notifications: notifications, for: user}) end + def subscription_notifications(%{assigns: %{user: user}} = conn, params) do + notifications = MastodonAPI.get_subscription_notifications(user, params) + + conn + |> add_link_headers(:subscription_notifications, notifications) + |> put_view(SubscriptionNotificationView) + |> render("index.json", %{notifications: notifications, for: user}) + end + + def get_subscription_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + with {:ok, notification} <- SubscriptionNotification.get(user, id) do + conn + |> put_view(SubscriptionNotificationView) + |> render("show.json", %{subscription_notification: notification, for: user}) + else + {:error, reason} -> + conn + |> put_status(:forbidden) + |> json(%{"error" => reason}) + end + end + def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do with {:ok, notification} <- Notification.get(user, id) do conn @@ -743,6 +767,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, %{}) end + def clear_subscription_notifications(%{assigns: %{user: user}} = conn, _params) do + SubscriptionNotification.clear(user) + json(conn, %{}) + end + def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do with {:ok, _notif} <- Notification.dismiss(user, id) do json(conn, %{}) @@ -754,11 +783,30 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def dismiss_subscription_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do + json(conn, %{}) + else + {:error, reason} -> + conn + |> put_status(:forbidden) + |> json(%{"error" => reason}) + end + end + def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do Notification.destroy_multiple(user, ids) json(conn, %{}) end + def destroy_multiple_subscription_notifications( + %{assigns: %{user: user}} = conn, + %{"ids" => ids} = _params + ) do + SubscriptionNotification.destroy_multiple(user, ids) + json(conn, %{}) + end + def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do id = List.wrap(id) q = from(u in User, where: u.id in ^id) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index ac01d1ff3..6751e24d8 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do alias Pleroma.Notification alias Pleroma.Pagination alias Pleroma.ScheduledActivity + alias Pleroma.SubscriptionNotification alias Pleroma.User alias Pleroma.Web.CommonAPI @@ -62,6 +63,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do |> Pagination.fetch_paginated(params) end + def get_subscription_notifications(user, params \\ %{}) do + options = cast_params(params) + + user + |> SubscriptionNotification.for_user_query(options) + |> restrict(:exclude_types, options) + |> Pagination.fetch_paginated(params) + end + def get_scheduled_activities(user, params \\ %{}) do user |> ScheduledActivity.for_user_query() diff --git a/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex b/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex new file mode 100644 index 000000000..c6f0b5064 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.SubscriptionNotificationView do + use Pleroma.Web, :view + + alias Pleroma.Activity + # alias Pleroma.SubscriptionNotification + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI.SubscriptionNotificationView + alias Pleroma.Web.MastodonAPI.StatusView + + def render("index.json", %{notifications: notifications, for: user}) do + safe_render_many(notifications, SubscriptionNotificationView, "show.json", %{for: user}) + end + + def render("show.json", %{ + subscription_notification: %{activity: activity} = notification, + for: user + }) do + actor = User.get_cached_by_ap_id(activity.data["actor"]) + parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) + mastodon_type = Activity.mastodon_notification_type(activity) + + response = %{ + id: to_string(notification.id), + type: mastodon_type, + created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at), + account: AccountView.render("account.json", %{user: actor, for: user}) + } + + case mastodon_type do + "mention" -> + response + |> Map.merge(%{ + status: StatusView.render("status.json", %{activity: activity, for: user}) + }) + + "favourite" -> + response + |> Map.merge(%{ + status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + }) + + "reblog" -> + response + |> Map.merge(%{ + status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + }) + + "follow" -> + response + + _ -> + nil + end + end +end diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index f4df3b024..71792d913 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do alias Pleroma.Conversation.Participation alias Pleroma.Notification + alias Pleroma.SubscriptionNotification alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.NotificationView @@ -95,4 +96,29 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do |> render("index.json", %{notifications: notifications, for: user}) end end + + def delete_subscription_notification(%{assigns: %{user: user}} = conn, %{ + "id" => notification_id + }) do + with {:ok, notification} <- SubscriptionNotification.dismiss(user, notification_id) do + conn + |> put_view(NotificationView) + |> render("show.json", %{notification: notification, for: user}) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + end + end + + def read_subscription_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do + with notifications <- SubscriptionNotification.clear_up_to(user, max_id) do + notifications = Enum.take(notifications, 80) + + conn + |> put_view(NotificationView) + |> render("index.json", %{notifications: notifications, for: user}) + end + end end diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 35d3ff07c..7ea5607fa 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.Push.Impl do alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.SubscriptionNotification alias Pleroma.User alias Pleroma.Web.Metadata.Utils alias Pleroma.Web.Push.Subscription @@ -19,7 +20,7 @@ defmodule Pleroma.Web.Push.Impl do @types ["Create", "Follow", "Announce", "Like"] @doc "Performs sending notifications for user subscriptions" - @spec perform(Notification.t()) :: list(any) | :error + @spec perform(Notification.t() | SubscriptionNotification.t()) :: list(any) | :error def perform( %{ activity: %{data: %{"type" => activity_type}, id: activity_id} = activity, diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index b0464037e..dbd0deecd 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -300,11 +300,39 @@ defmodule Pleroma.Web.Router do get("/bookmarks", MastodonAPIController, :bookmarks) post("/notifications/clear", MastodonAPIController, :clear_notifications) + + post( + "/notifications/subscription/clear", + MastodonAPIController, + :clear_subscription_notifications + ) + post("/notifications/dismiss", MastodonAPIController, :dismiss_notification) + + post( + "/notifications/subscription/dismiss", + MastodonAPIController, + :dismiss_subscription_notification + ) + get("/notifications", MastodonAPIController, :notifications) + get("/notifications/subscription", MastodonAPIController, :subscription_notifications) get("/notifications/:id", MastodonAPIController, :get_notification) + + get( + "/notifications/subscription/:id", + MastodonAPIController, + :get_subscription_notification + ) + delete("/notifications/destroy_multiple", MastodonAPIController, :destroy_multiple) + delete( + "/notifications/subscription/destroy_multiple", + MastodonAPIController, + :destroy_multiple_subscription_notifications + ) + get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses) get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 587c43f40..42d95e33a 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.Streamer do alias Pleroma.Conversation.Participation alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.SubscriptionNotification alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility @@ -208,10 +209,17 @@ defmodule Pleroma.Web.Streamer do |> Jason.encode!() end - @spec represent_notification(User.t(), Notification.t()) :: binary() - defp represent_notification(%User{} = user, %Notification{} = notify) do + @spec represent_notification(User.t(), Notification.t() | %SubscriptionNotification{}) :: + binary() + defp represent_notification(%User{} = user, notify) do + event = + case notify do + %Notification{} -> "notification" + %SubscriptionNotification{} -> "subscription_norification" + end + %{ - event: "notification", + event: event, payload: NotificationView.render( "show.json", -- cgit v1.2.3 From 69faec031d62f4e87a1791ae0c71ca4b0f02f12b Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 13 Sep 2019 19:02:42 +0300 Subject: markdown generation to the new file --- lib/pleroma/docs/markdown.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 24930cc9f..8386dc2fb 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -3,9 +3,9 @@ defmodule Pleroma.Docs.Markdown do @spec process(keyword()) :: {:ok, String.t()} def process(descriptions) do - config_path = "docs/config.md" + config_path = "docs/generated_config.md" {:ok, file} = File.open(config_path, [:utf8, :write]) - IO.write(file, "# Configuration\n") + IO.write(file, "# Generated configuration\n") IO.write(file, "Date of generation: #{Date.utc_today()}\n\n") IO.write( -- cgit v1.2.3 From ac4a748fad34c02647bf72e802cd9d74205681fe Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 13 Sep 2019 19:28:35 +0300 Subject: Disallow NULLs in deliveries --- lib/pleroma/delivery.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/delivery.ex b/lib/pleroma/delivery.ex index 38c148c34..29a1e5a77 100644 --- a/lib/pleroma/delivery.ex +++ b/lib/pleroma/delivery.ex @@ -23,6 +23,7 @@ defmodule Pleroma.Delivery do def changeset(delivery, params \\ %{}) do delivery |> cast(params, [:user_id, :object_id]) + |> validate_required([:user_id, :object_id]) |> foreign_key_constraint(:object_id) |> foreign_key_constraint(:user_id) |> unique_constraint(:user_id, name: :deliveries_user_id_object_id_index) -- cgit v1.2.3 From 5c5ebd38619bb853a58374918fd8983569ba7c0b Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 14 Sep 2019 01:50:15 +0300 Subject: Mastodon API: Respect post privacy in favourited/reblogged endpoints --- lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 0940e07a6..060137b80 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -842,6 +842,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), + {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do q = from(u in User, where: u.ap_id in ^likes) @@ -853,12 +854,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> put_view(AccountView) |> render("accounts.json", %{for: user, users: users, as: :user}) else + {:visible, false} -> {:error, :not_found} _ -> json(conn, []) end end def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), + {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do q = from(u in User, where: u.ap_id in ^announces) @@ -870,6 +873,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> put_view(AccountView) |> render("accounts.json", %{for: user, users: users, as: :user}) else + {:visible, false} -> {:error, :not_found} _ -> json(conn, []) end end -- cgit v1.2.3 From b4cf74c1067b866574a63fbd25ccb12cc1fed619 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Sun, 15 Sep 2019 14:53:58 +0300 Subject: added prepare html for RichMedia.Parser --- lib/pleroma/web/rich_media/parser.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index f5f9e358c..c06b0a0f2 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -81,6 +81,7 @@ defmodule Pleroma.Web.RichMedia.Parser do {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options) html + |> parse_html |> maybe_parse() |> Map.put(:url, url) |> clean_parsed_data() @@ -91,6 +92,8 @@ defmodule Pleroma.Web.RichMedia.Parser do end end + defp parse_html(html), do: Floki.parse(html) + defp maybe_parse(html) do Enum.reduce_while(parsers(), %{}, fn parser, acc -> case parser.parse(html, acc) do @@ -100,7 +103,8 @@ defmodule Pleroma.Web.RichMedia.Parser do end) end - defp check_parsed_data(%{title: title} = data) when is_binary(title) and byte_size(title) > 0 do + defp check_parsed_data(%{title: title} = data) + when is_binary(title) and byte_size(title) > 0 do {:ok, data} end -- cgit v1.2.3 From aab264db82054df470075c65ca25c42bbcc5d7a8 Mon Sep 17 00:00:00 2001 From: Steven Fuchs Date: Mon, 16 Sep 2019 07:44:03 +0000 Subject: Streamer refactoring --- lib/pleroma/activity/ir/topics.ex | 63 +++++ lib/pleroma/application.ex | 2 +- lib/pleroma/notification.ex | 6 +- lib/pleroma/web/activity_pub/activity_pub.ex | 50 +--- lib/pleroma/web/mastodon_api/websocket_handler.ex | 7 +- lib/pleroma/web/streamer.ex | 318 ---------------------- lib/pleroma/web/streamer/ping.ex | 33 +++ lib/pleroma/web/streamer/state.ex | 68 +++++ lib/pleroma/web/streamer/streamer.ex | 55 ++++ lib/pleroma/web/streamer/streamer_socket.ex | 31 +++ lib/pleroma/web/streamer/supervisor.ex | 33 +++ lib/pleroma/web/streamer/worker.ex | 220 +++++++++++++++ lib/pleroma/web/views/streamer_view.ex | 66 +++++ 13 files changed, 590 insertions(+), 362 deletions(-) create mode 100644 lib/pleroma/activity/ir/topics.ex delete mode 100644 lib/pleroma/web/streamer.ex create mode 100644 lib/pleroma/web/streamer/ping.ex create mode 100644 lib/pleroma/web/streamer/state.ex create mode 100644 lib/pleroma/web/streamer/streamer.ex create mode 100644 lib/pleroma/web/streamer/streamer_socket.ex create mode 100644 lib/pleroma/web/streamer/supervisor.ex create mode 100644 lib/pleroma/web/streamer/worker.ex create mode 100644 lib/pleroma/web/views/streamer_view.ex (limited to 'lib') diff --git a/lib/pleroma/activity/ir/topics.ex b/lib/pleroma/activity/ir/topics.ex new file mode 100644 index 000000000..010897abc --- /dev/null +++ b/lib/pleroma/activity/ir/topics.ex @@ -0,0 +1,63 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Activity.Ir.Topics do + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Visibility + + def get_activity_topics(activity) do + activity + |> Object.normalize() + |> generate_topics(activity) + |> List.flatten() + end + + defp generate_topics(%{data: %{"type" => "Answer"}}, _) do + [] + end + + defp generate_topics(object, activity) do + ["user", "list"] ++ visibility_tags(object, activity) + end + + defp visibility_tags(object, activity) do + case Visibility.get_visibility(activity) do + "public" -> + if activity.local do + ["public", "public:local"] + else + ["public"] + end + |> item_creation_tags(object, activity) + + "direct" -> + ["direct"] + + _ -> + [] + end + end + + defp item_creation_tags(tags, %{data: %{"type" => "Create"}} = object, activity) do + tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity) + end + + defp item_creation_tags(tags, _, _) do + tags + end + + defp hashtags_to_topics(%{data: %{"tag" => tags}}) do + tags + |> Enum.filter(&is_bitstring(&1)) + |> Enum.map(fn tag -> "hashtag:" <> tag end) + end + + defp hashtags_to_topics(_), do: [] + + defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: [] + + defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"] + + defp attachment_topics(_object, _act), do: ["public:media"] +end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 49094704b..3b37ce630 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -141,7 +141,7 @@ defmodule Pleroma.Application do defp streamer_child(:test), do: [] defp streamer_child(_) do - [Pleroma.Web.Streamer] + [Pleroma.Web.Streamer.supervisor()] end defp oauth_cleanup_child(true), diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b7c880c51..8012389ac 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -210,8 +210,10 @@ defmodule Pleroma.Notification do unless skip?(activity, user) do notification = %Notification{user_id: user.id, activity: activity} {:ok, notification} = Repo.insert(notification) - Streamer.stream("user", notification) - Streamer.stream("user:notification", notification) + + ["user", "user:notification"] + |> Streamer.stream(notification) + Push.send(notification) notification end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 41f6a0f1f..bc5ae7fbf 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Activity + alias Pleroma.Activity.Ir.Topics alias Pleroma.Config alias Pleroma.Conversation alias Pleroma.Notification @@ -16,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.User alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.Streamer alias Pleroma.Web.WebFinger alias Pleroma.Workers.BackgroundWorker @@ -187,9 +189,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do participations |> Repo.preload(:user) - Enum.each(participations, fn participation -> - Pleroma.Web.Streamer.stream("participation", participation) - end) + Streamer.stream("participation", participations) end def stream_out_participations(%Object{data: %{"context" => context}}, user) do @@ -208,41 +208,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def stream_out_participations(_, _), do: :noop - def stream_out(activity) do - if activity.data["type"] in ["Create", "Announce", "Delete"] do - object = Object.normalize(activity) - # Do not stream out poll replies - unless object.data["type"] == "Answer" do - Pleroma.Web.Streamer.stream("user", activity) - Pleroma.Web.Streamer.stream("list", activity) - - if get_visibility(activity) == "public" do - Pleroma.Web.Streamer.stream("public", activity) - - if activity.local do - Pleroma.Web.Streamer.stream("public:local", activity) - end - - if activity.data["type"] in ["Create"] do - object.data - |> Map.get("tag", []) - |> Enum.filter(fn tag -> is_bitstring(tag) end) - |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) - - if object.data["attachment"] != [] do - Pleroma.Web.Streamer.stream("public:media", activity) - - if activity.local do - Pleroma.Web.Streamer.stream("public:local:media", activity) - end - end - end - else - if get_visibility(activity) == "direct", - do: Pleroma.Web.Streamer.stream("direct", activity) - end - end - end + def stream_out(%Activity{data: %{"type" => data_type}} = activity) + when data_type in ["Create", "Announce", "Delete"] do + activity + |> Topics.get_activity_topics() + |> Streamer.stream(activity) + end + + def stream_out(_activity) do + :noop end def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index dbd3542ea..3c26eb406 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.Streamer @behaviour :cowboy_websocket @@ -24,7 +25,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do ] @anonymous_streams ["public", "public:local", "hashtag"] - # Handled by periodic keepalive in Pleroma.Web.Streamer. + # Handled by periodic keepalive in Pleroma.Web.Streamer.Ping. @timeout :infinity def init(%{qs: qs} = req, state) do @@ -65,7 +66,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do }, topic #{state.topic}" ) - Pleroma.Web.Streamer.add_socket(state.topic, streamer_socket(state)) + Streamer.add_socket(state.topic, streamer_socket(state)) {:ok, state} end @@ -80,7 +81,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do }, topic #{state.topic || "?"}: #{inspect(reason)}" ) - Pleroma.Web.Streamer.remove_socket(state.topic, streamer_socket(state)) + Streamer.remove_socket(state.topic, streamer_socket(state)) :ok end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex deleted file mode 100644 index 587c43f40..000000000 --- a/lib/pleroma/web/streamer.ex +++ /dev/null @@ -1,318 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Streamer do - use GenServer - require Logger - alias Pleroma.Activity - alias Pleroma.Config - alias Pleroma.Conversation.Participation - alias Pleroma.Notification - alias Pleroma.Object - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MastodonAPI.NotificationView - - @keepalive_interval :timer.seconds(30) - - def start_link(_) do - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) - end - - def add_socket(topic, socket) do - GenServer.cast(__MODULE__, %{action: :add, socket: socket, topic: topic}) - end - - def remove_socket(topic, socket) do - GenServer.cast(__MODULE__, %{action: :remove, socket: socket, topic: topic}) - end - - def stream(topic, item) do - GenServer.cast(__MODULE__, %{action: :stream, topic: topic, item: item}) - end - - def init(args) do - Process.send_after(self(), %{action: :ping}, @keepalive_interval) - - {:ok, args} - end - - def handle_info(%{action: :ping}, topics) do - topics - |> Map.values() - |> List.flatten() - |> Enum.each(fn socket -> - Logger.debug("Sending keepalive ping") - send(socket.transport_pid, {:text, ""}) - end) - - Process.send_after(self(), %{action: :ping}, @keepalive_interval) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: "direct", item: item}, topics) do - recipient_topics = - User.get_recipients_from_activity(item) - |> Enum.map(fn %{id: id} -> "direct:#{id}" end) - - Enum.each(recipient_topics || [], fn user_topic -> - Logger.debug("Trying to push direct message to #{user_topic}\n\n") - push_to_socket(topics, user_topic, item) - end) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: "participation", item: participation}, topics) do - user_topic = "direct:#{participation.user_id}" - Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") - - push_to_socket(topics, user_topic, participation) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do - # filter the recipient list if the activity is not public, see #270. - recipient_lists = - case Visibility.is_public?(item) do - true -> - Pleroma.List.get_lists_from_activity(item) - - _ -> - Pleroma.List.get_lists_from_activity(item) - |> Enum.filter(fn list -> - owner = User.get_cached_by_id(list.user_id) - - Visibility.visible_for_user?(item, owner) - end) - end - - recipient_topics = - recipient_lists - |> Enum.map(fn %{id: id} -> "list:#{id}" end) - - Enum.each(recipient_topics || [], fn list_topic -> - Logger.debug("Trying to push message to #{list_topic}\n\n") - push_to_socket(topics, list_topic, item) - end) - - {:noreply, topics} - end - - def handle_cast( - %{action: :stream, topic: topic, item: %Notification{} = item}, - topics - ) - when topic in ["user", "user:notification"] do - topics - |> Map.get("#{topic}:#{item.user_id}", []) - |> Enum.each(fn socket -> - with %User{} = user <- User.get_cached_by_ap_id(socket.assigns[:user].ap_id), - true <- should_send?(user, item) do - send( - socket.transport_pid, - {:text, represent_notification(socket.assigns[:user], item)} - ) - end - end) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: "user", item: item}, topics) do - Logger.debug("Trying to push to users") - - recipient_topics = - User.get_recipients_from_activity(item) - |> Enum.map(fn %{id: id} -> "user:#{id}" end) - - Enum.each(recipient_topics, fn topic -> - push_to_socket(topics, topic, item) - end) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: topic, item: item}, topics) do - Logger.debug("Trying to push to #{topic}") - Logger.debug("Pushing item to #{topic}") - push_to_socket(topics, topic, item) - {:noreply, topics} - end - - def handle_cast(%{action: :add, topic: topic, socket: socket}, sockets) do - topic = internal_topic(topic, socket) - sockets_for_topic = sockets[topic] || [] - sockets_for_topic = Enum.uniq([socket | sockets_for_topic]) - sockets = Map.put(sockets, topic, sockets_for_topic) - Logger.debug("Got new conn for #{topic}") - {:noreply, sockets} - end - - def handle_cast(%{action: :remove, topic: topic, socket: socket}, sockets) do - topic = internal_topic(topic, socket) - sockets_for_topic = sockets[topic] || [] - sockets_for_topic = List.delete(sockets_for_topic, socket) - sockets = Map.put(sockets, topic, sockets_for_topic) - Logger.debug("Removed conn for #{topic}") - {:noreply, sockets} - end - - def handle_cast(m, state) do - Logger.info("Unknown: #{inspect(m)}, #{inspect(state)}") - {:noreply, state} - end - - defp represent_update(%Activity{} = activity, %User{} = user) do - %{ - event: "update", - payload: - Pleroma.Web.MastodonAPI.StatusView.render( - "status.json", - activity: activity, - for: user - ) - |> Jason.encode!() - } - |> Jason.encode!() - end - - defp represent_update(%Activity{} = activity) do - %{ - event: "update", - payload: - Pleroma.Web.MastodonAPI.StatusView.render( - "status.json", - activity: activity - ) - |> Jason.encode!() - } - |> Jason.encode!() - end - - def represent_conversation(%Participation{} = participation) do - %{ - event: "conversation", - payload: - Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ - participation: participation, - for: participation.user - }) - |> Jason.encode!() - } - |> Jason.encode!() - end - - @spec represent_notification(User.t(), Notification.t()) :: binary() - defp represent_notification(%User{} = user, %Notification{} = notify) do - %{ - event: "notification", - payload: - NotificationView.render( - "show.json", - %{notification: notify, for: user} - ) - |> Jason.encode!() - } - |> Jason.encode!() - end - - defp should_send?(%User{} = user, %Activity{} = item) do - blocks = user.info.blocks || [] - mutes = user.info.mutes || [] - reblog_mutes = user.info.muted_reblogs || [] - domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks) - - with parent when not is_nil(parent) <- Object.normalize(item), - true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), - true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)), - %{host: item_host} <- URI.parse(item.actor), - %{host: parent_host} <- URI.parse(parent.data["actor"]), - false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), - false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), - true <- thread_containment(item, user), - false <- CommonAPI.thread_muted?(user, item) do - true - else - _ -> false - end - end - - defp should_send?(%User{} = user, %Notification{activity: activity}) do - should_send?(user, activity) - end - - def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do - Enum.each(topics[topic] || [], fn socket -> - # Get the current user so we have up-to-date blocks etc. - if socket.assigns[:user] do - user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) - - if should_send?(user, item) do - send(socket.transport_pid, {:text, represent_update(item, user)}) - end - else - send(socket.transport_pid, {:text, represent_update(item)}) - end - end) - end - - def push_to_socket(topics, topic, %Participation{} = participation) do - Enum.each(topics[topic] || [], fn socket -> - send(socket.transport_pid, {:text, represent_conversation(participation)}) - end) - end - - def push_to_socket(topics, topic, %Activity{ - data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} - }) do - Enum.each(topics[topic] || [], fn socket -> - send( - socket.transport_pid, - {:text, %{event: "delete", payload: to_string(deleted_activity_id)} |> Jason.encode!()} - ) - end) - end - - def push_to_socket(_topics, _topic, %Activity{data: %{"type" => "Delete"}}), do: :noop - - def push_to_socket(topics, topic, item) do - Enum.each(topics[topic] || [], fn socket -> - # Get the current user so we have up-to-date blocks etc. - if socket.assigns[:user] do - user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) - blocks = user.info.blocks || [] - mutes = user.info.mutes || [] - - with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)), - true <- thread_containment(item, user) do - send(socket.transport_pid, {:text, represent_update(item, user)}) - end - else - send(socket.transport_pid, {:text, represent_update(item)}) - end - end) - end - - defp internal_topic(topic, socket) when topic in ~w[user user:notification direct] do - "#{topic}:#{socket.assigns[:user].id}" - end - - defp internal_topic(topic, _), do: topic - - @spec thread_containment(Activity.t(), User.t()) :: boolean() - defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true - - defp thread_containment(activity, user) do - if Config.get([:instance, :skip_thread_containment]) do - true - else - ActivityPub.contain_activity(activity, user) - end - end -end diff --git a/lib/pleroma/web/streamer/ping.ex b/lib/pleroma/web/streamer/ping.ex new file mode 100644 index 000000000..f77cbb95c --- /dev/null +++ b/lib/pleroma/web/streamer/ping.ex @@ -0,0 +1,33 @@ +defmodule Pleroma.Web.Streamer.Ping do + use GenServer + require Logger + + alias Pleroma.Web.Streamer.State + alias Pleroma.Web.Streamer.StreamerSocket + + @keepalive_interval :timer.seconds(30) + + def start_link(opts) do + ping_interval = Keyword.get(opts, :ping_interval, @keepalive_interval) + GenServer.start_link(__MODULE__, %{ping_interval: ping_interval}, name: __MODULE__) + end + + def init(%{ping_interval: ping_interval} = args) do + Process.send_after(self(), :ping, ping_interval) + {:ok, args} + end + + def handle_info(:ping, %{ping_interval: ping_interval} = state) do + State.get_sockets() + |> Map.values() + |> List.flatten() + |> Enum.each(fn %StreamerSocket{transport_pid: transport_pid} -> + Logger.debug("Sending keepalive ping") + send(transport_pid, {:text, ""}) + end) + + Process.send_after(self(), :ping, ping_interval) + + {:noreply, state} + end +end diff --git a/lib/pleroma/web/streamer/state.ex b/lib/pleroma/web/streamer/state.ex new file mode 100644 index 000000000..7b5199068 --- /dev/null +++ b/lib/pleroma/web/streamer/state.ex @@ -0,0 +1,68 @@ +defmodule Pleroma.Web.Streamer.State do + use GenServer + require Logger + + alias Pleroma.Web.Streamer.StreamerSocket + + def start_link(_) do + GenServer.start_link(__MODULE__, %{sockets: %{}}, name: __MODULE__) + end + + def add_socket(topic, socket) do + GenServer.call(__MODULE__, {:add, socket, topic}) + end + + def remove_socket(topic, socket) do + GenServer.call(__MODULE__, {:remove, socket, topic}) + end + + def get_sockets do + %{sockets: stream_sockets} = GenServer.call(__MODULE__, :get_state) + stream_sockets + end + + def init(init_arg) do + {:ok, init_arg} + end + + def handle_call(:get_state, _from, state) do + {:reply, state, state} + end + + def handle_call({:add, socket, topic}, _from, %{sockets: sockets} = state) do + internal_topic = internal_topic(topic, socket) + stream_socket = StreamerSocket.from_socket(socket) + + sockets_for_topic = + sockets + |> Map.get(internal_topic, []) + |> List.insert_at(0, stream_socket) + |> Enum.uniq() + + state = put_in(state, [:sockets, internal_topic], sockets_for_topic) + Logger.debug("Got new conn for #{topic}") + {:reply, state, state} + end + + def handle_call({:remove, socket, topic}, _from, %{sockets: sockets} = state) do + internal_topic = internal_topic(topic, socket) + stream_socket = StreamerSocket.from_socket(socket) + + sockets_for_topic = + sockets + |> Map.get(internal_topic, []) + |> List.delete(stream_socket) + + state = Kernel.put_in(state, [:sockets, internal_topic], sockets_for_topic) + {:reply, state, state} + end + + defp internal_topic(topic, socket) + when topic in ~w[user user:notification direct] do + "#{topic}:#{socket.assigns[:user].id}" + end + + defp internal_topic(topic, _) do + topic + end +end diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex new file mode 100644 index 000000000..8cf719277 --- /dev/null +++ b/lib/pleroma/web/streamer/streamer.ex @@ -0,0 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Streamer do + alias Pleroma.Web.Streamer.State + alias Pleroma.Web.Streamer.Worker + + @timeout 60_000 + @mix_env Mix.env() + + def add_socket(topic, socket) do + State.add_socket(topic, socket) + end + + def remove_socket(topic, socket) do + State.remove_socket(topic, socket) + end + + def get_sockets do + State.get_sockets() + end + + def stream(topics, items) do + if should_send?() do + Task.async(fn -> + :poolboy.transaction( + :streamer_worker, + &Worker.stream(&1, topics, items), + @timeout + ) + end) + end + end + + def supervisor, do: Pleroma.Web.Streamer.Supervisor + + defp should_send? do + handle_should_send(@mix_env) + end + + defp handle_should_send(:test) do + case Process.whereis(:streamer_worker) do + nil -> + false + + pid -> + Process.alive?(pid) + end + end + + defp handle_should_send(_) do + true + end +end diff --git a/lib/pleroma/web/streamer/streamer_socket.ex b/lib/pleroma/web/streamer/streamer_socket.ex new file mode 100644 index 000000000..f006c0306 --- /dev/null +++ b/lib/pleroma/web/streamer/streamer_socket.ex @@ -0,0 +1,31 @@ +defmodule Pleroma.Web.Streamer.StreamerSocket do + defstruct transport_pid: nil, user: nil + + alias Pleroma.User + alias Pleroma.Web.Streamer.StreamerSocket + + def from_socket(%{ + transport_pid: transport_pid, + assigns: %{user: nil} + }) do + %StreamerSocket{ + transport_pid: transport_pid + } + end + + def from_socket(%{ + transport_pid: transport_pid, + assigns: %{user: %User{} = user} + }) do + %StreamerSocket{ + transport_pid: transport_pid, + user: user + } + end + + def from_socket(%{transport_pid: transport_pid}) do + %StreamerSocket{ + transport_pid: transport_pid + } + end +end diff --git a/lib/pleroma/web/streamer/supervisor.ex b/lib/pleroma/web/streamer/supervisor.ex new file mode 100644 index 000000000..6afe19323 --- /dev/null +++ b/lib/pleroma/web/streamer/supervisor.ex @@ -0,0 +1,33 @@ +defmodule Pleroma.Web.Streamer.Supervisor do + use Supervisor + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + def init(args) do + children = [ + {Pleroma.Web.Streamer.State, args}, + {Pleroma.Web.Streamer.Ping, args}, + :poolboy.child_spec(:streamer_worker, poolboy_config()) + ] + + opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor] + Supervisor.init(children, opts) + end + + defp poolboy_config do + opts = + Pleroma.Config.get(:streamer, + workers: 3, + overflow_workers: 2 + ) + + [ + {:name, {:local, :streamer_worker}}, + {:worker_module, Pleroma.Web.Streamer.Worker}, + {:size, opts[:workers]}, + {:max_overflow, opts[:overflow_workers]} + ] + end +end diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex new file mode 100644 index 000000000..5804508eb --- /dev/null +++ b/lib/pleroma/web/streamer/worker.ex @@ -0,0 +1,220 @@ +defmodule Pleroma.Web.Streamer.Worker do + use GenServer + + require Logger + + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.Conversation.Participation + alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Streamer.State + alias Pleroma.Web.Streamer.StreamerSocket + alias Pleroma.Web.StreamerView + + def start_link(_) do + GenServer.start_link(__MODULE__, %{}, []) + end + + def init(init_arg) do + {:ok, init_arg} + end + + def stream(pid, topics, items) do + GenServer.call(pid, {:stream, topics, items}) + end + + def handle_call({:stream, topics, item}, _from, state) when is_list(topics) do + Enum.each(topics, fn t -> + do_stream(%{topic: t, item: item}) + end) + + {:reply, state, state} + end + + def handle_call({:stream, topic, items}, _from, state) when is_list(items) do + Enum.each(items, fn i -> + do_stream(%{topic: topic, item: i}) + end) + + {:reply, state, state} + end + + def handle_call({:stream, topic, item}, _from, state) do + do_stream(%{topic: topic, item: item}) + + {:reply, state, state} + end + + defp do_stream(%{topic: "direct", item: item}) do + recipient_topics = + User.get_recipients_from_activity(item) + |> Enum.map(fn %{id: id} -> "direct:#{id}" end) + + Enum.each(recipient_topics, fn user_topic -> + Logger.debug("Trying to push direct message to #{user_topic}\n\n") + push_to_socket(State.get_sockets(), user_topic, item) + end) + end + + defp do_stream(%{topic: "participation", item: participation}) do + user_topic = "direct:#{participation.user_id}" + Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") + + push_to_socket(State.get_sockets(), user_topic, participation) + end + + defp do_stream(%{topic: "list", item: item}) do + # filter the recipient list if the activity is not public, see #270. + recipient_lists = + case Visibility.is_public?(item) do + true -> + Pleroma.List.get_lists_from_activity(item) + + _ -> + Pleroma.List.get_lists_from_activity(item) + |> Enum.filter(fn list -> + owner = User.get_cached_by_id(list.user_id) + + Visibility.visible_for_user?(item, owner) + end) + end + + recipient_topics = + recipient_lists + |> Enum.map(fn %{id: id} -> "list:#{id}" end) + + Enum.each(recipient_topics, fn list_topic -> + Logger.debug("Trying to push message to #{list_topic}\n\n") + push_to_socket(State.get_sockets(), list_topic, item) + end) + end + + defp do_stream(%{topic: topic, item: %Notification{} = item}) + when topic in ["user", "user:notification"] do + State.get_sockets() + |> Map.get("#{topic}:#{item.user_id}", []) + |> Enum.each(fn %StreamerSocket{transport_pid: transport_pid, user: socket_user} -> + with %User{} = user <- User.get_cached_by_ap_id(socket_user.ap_id), + true <- should_send?(user, item) do + send(transport_pid, {:text, StreamerView.render("notification.json", socket_user, item)}) + end + end) + end + + defp do_stream(%{topic: "user", item: item}) do + Logger.debug("Trying to push to users") + + recipient_topics = + User.get_recipients_from_activity(item) + |> Enum.map(fn %{id: id} -> "user:#{id}" end) + + Enum.each(recipient_topics, fn topic -> + push_to_socket(State.get_sockets(), topic, item) + end) + end + + defp do_stream(%{topic: topic, item: item}) do + Logger.debug("Trying to push to #{topic}") + Logger.debug("Pushing item to #{topic}") + push_to_socket(State.get_sockets(), topic, item) + end + + defp should_send?(%User{} = user, %Activity{} = item) do + blocks = user.info.blocks || [] + mutes = user.info.mutes || [] + reblog_mutes = user.info.muted_reblogs || [] + domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks) + + with parent when not is_nil(parent) <- Object.normalize(item), + true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), + true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)), + %{host: item_host} <- URI.parse(item.actor), + %{host: parent_host} <- URI.parse(parent.data["actor"]), + false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), + false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), + true <- thread_containment(item, user), + false <- CommonAPI.thread_muted?(user, item) do + true + else + _ -> false + end + end + + defp should_send?(%User{} = user, %Notification{activity: activity}) do + should_send?(user, activity) + end + + def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do + Enum.each(topics[topic] || [], fn %StreamerSocket{ + transport_pid: transport_pid, + user: socket_user + } -> + # Get the current user so we have up-to-date blocks etc. + if socket_user do + user = User.get_cached_by_ap_id(socket_user.ap_id) + + if should_send?(user, item) do + send(transport_pid, {:text, StreamerView.render("update.json", item, user)}) + end + else + send(transport_pid, {:text, StreamerView.render("update.json", item)}) + end + end) + end + + def push_to_socket(topics, topic, %Participation{} = participation) do + Enum.each(topics[topic] || [], fn %StreamerSocket{transport_pid: transport_pid} -> + send(transport_pid, {:text, StreamerView.render("conversation.json", participation)}) + end) + end + + def push_to_socket(topics, topic, %Activity{ + data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} + }) do + Enum.each(topics[topic] || [], fn %StreamerSocket{transport_pid: transport_pid} -> + send( + transport_pid, + {:text, %{event: "delete", payload: to_string(deleted_activity_id)} |> Jason.encode!()} + ) + end) + end + + def push_to_socket(_topics, _topic, %Activity{data: %{"type" => "Delete"}}), do: :noop + + def push_to_socket(topics, topic, item) do + Enum.each(topics[topic] || [], fn %StreamerSocket{ + transport_pid: transport_pid, + user: socket_user + } -> + # Get the current user so we have up-to-date blocks etc. + if socket_user do + user = User.get_cached_by_ap_id(socket_user.ap_id) + blocks = user.info.blocks || [] + mutes = user.info.mutes || [] + + with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)), + true <- thread_containment(item, user) do + send(transport_pid, {:text, StreamerView.render("update.json", item, user)}) + end + else + send(transport_pid, {:text, StreamerView.render("update.json", item)}) + end + end) + end + + @spec thread_containment(Activity.t(), User.t()) :: boolean() + defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true + + defp thread_containment(activity, user) do + if Config.get([:instance, :skip_thread_containment]) do + true + else + ActivityPub.contain_activity(activity, user) + end + end +end diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex new file mode 100644 index 000000000..b13030fa0 --- /dev/null +++ b/lib/pleroma/web/views/streamer_view.ex @@ -0,0 +1,66 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.StreamerView do + use Pleroma.Web, :view + + alias Pleroma.Activity + alias Pleroma.Conversation.Participation + alias Pleroma.Notification + alias Pleroma.User + alias Pleroma.Web.MastodonAPI.NotificationView + + def render("update.json", %Activity{} = activity, %User{} = user) do + %{ + event: "update", + payload: + Pleroma.Web.MastodonAPI.StatusView.render( + "status.json", + activity: activity, + for: user + ) + |> Jason.encode!() + } + |> Jason.encode!() + end + + def render("notification.json", %User{} = user, %Notification{} = notify) do + %{ + event: "notification", + payload: + NotificationView.render( + "show.json", + %{notification: notify, for: user} + ) + |> Jason.encode!() + } + |> Jason.encode!() + end + + def render("update.json", %Activity{} = activity) do + %{ + event: "update", + payload: + Pleroma.Web.MastodonAPI.StatusView.render( + "status.json", + activity: activity + ) + |> Jason.encode!() + } + |> Jason.encode!() + end + + def render("conversation.json", %Participation{} = participation) do + %{ + event: "conversation", + payload: + Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ + participation: participation, + for: participation.user + }) + |> Jason.encode!() + } + |> Jason.encode!() + end +end -- cgit v1.2.3 From c623b4324deaf236334a0f77a81435b5bffadf3c Mon Sep 17 00:00:00 2001 From: kaniini Date: Mon, 16 Sep 2019 09:09:21 +0000 Subject: Revert "Merge branch 'streamer-refactoring' into 'develop'" This reverts merge request !1653 --- lib/pleroma/activity/ir/topics.ex | 63 ----- lib/pleroma/application.ex | 2 +- lib/pleroma/notification.ex | 6 +- lib/pleroma/web/activity_pub/activity_pub.ex | 50 +++- lib/pleroma/web/mastodon_api/websocket_handler.ex | 7 +- lib/pleroma/web/streamer.ex | 318 ++++++++++++++++++++++ lib/pleroma/web/streamer/ping.ex | 33 --- lib/pleroma/web/streamer/state.ex | 68 ----- lib/pleroma/web/streamer/streamer.ex | 55 ---- lib/pleroma/web/streamer/streamer_socket.ex | 31 --- lib/pleroma/web/streamer/supervisor.ex | 33 --- lib/pleroma/web/streamer/worker.ex | 220 --------------- lib/pleroma/web/views/streamer_view.ex | 66 ----- 13 files changed, 362 insertions(+), 590 deletions(-) delete mode 100644 lib/pleroma/activity/ir/topics.ex create mode 100644 lib/pleroma/web/streamer.ex delete mode 100644 lib/pleroma/web/streamer/ping.ex delete mode 100644 lib/pleroma/web/streamer/state.ex delete mode 100644 lib/pleroma/web/streamer/streamer.ex delete mode 100644 lib/pleroma/web/streamer/streamer_socket.ex delete mode 100644 lib/pleroma/web/streamer/supervisor.ex delete mode 100644 lib/pleroma/web/streamer/worker.ex delete mode 100644 lib/pleroma/web/views/streamer_view.ex (limited to 'lib') diff --git a/lib/pleroma/activity/ir/topics.ex b/lib/pleroma/activity/ir/topics.ex deleted file mode 100644 index 010897abc..000000000 --- a/lib/pleroma/activity/ir/topics.ex +++ /dev/null @@ -1,63 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Activity.Ir.Topics do - alias Pleroma.Object - alias Pleroma.Web.ActivityPub.Visibility - - def get_activity_topics(activity) do - activity - |> Object.normalize() - |> generate_topics(activity) - |> List.flatten() - end - - defp generate_topics(%{data: %{"type" => "Answer"}}, _) do - [] - end - - defp generate_topics(object, activity) do - ["user", "list"] ++ visibility_tags(object, activity) - end - - defp visibility_tags(object, activity) do - case Visibility.get_visibility(activity) do - "public" -> - if activity.local do - ["public", "public:local"] - else - ["public"] - end - |> item_creation_tags(object, activity) - - "direct" -> - ["direct"] - - _ -> - [] - end - end - - defp item_creation_tags(tags, %{data: %{"type" => "Create"}} = object, activity) do - tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity) - end - - defp item_creation_tags(tags, _, _) do - tags - end - - defp hashtags_to_topics(%{data: %{"tag" => tags}}) do - tags - |> Enum.filter(&is_bitstring(&1)) - |> Enum.map(fn tag -> "hashtag:" <> tag end) - end - - defp hashtags_to_topics(_), do: [] - - defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: [] - - defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"] - - defp attachment_topics(_object, _act), do: ["public:media"] -end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 3b37ce630..49094704b 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -141,7 +141,7 @@ defmodule Pleroma.Application do defp streamer_child(:test), do: [] defp streamer_child(_) do - [Pleroma.Web.Streamer.supervisor()] + [Pleroma.Web.Streamer] end defp oauth_cleanup_child(true), diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 8012389ac..b7c880c51 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -210,10 +210,8 @@ defmodule Pleroma.Notification do unless skip?(activity, user) do notification = %Notification{user_id: user.id, activity: activity} {:ok, notification} = Repo.insert(notification) - - ["user", "user:notification"] - |> Streamer.stream(notification) - + Streamer.stream("user", notification) + Streamer.stream("user:notification", notification) Push.send(notification) notification end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index bc5ae7fbf..41f6a0f1f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -4,7 +4,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Activity - alias Pleroma.Activity.Ir.Topics alias Pleroma.Config alias Pleroma.Conversation alias Pleroma.Notification @@ -17,7 +16,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.User alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.Transmogrifier - alias Pleroma.Web.Streamer alias Pleroma.Web.WebFinger alias Pleroma.Workers.BackgroundWorker @@ -189,7 +187,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do participations |> Repo.preload(:user) - Streamer.stream("participation", participations) + Enum.each(participations, fn participation -> + Pleroma.Web.Streamer.stream("participation", participation) + end) end def stream_out_participations(%Object{data: %{"context" => context}}, user) do @@ -208,15 +208,41 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def stream_out_participations(_, _), do: :noop - def stream_out(%Activity{data: %{"type" => data_type}} = activity) - when data_type in ["Create", "Announce", "Delete"] do - activity - |> Topics.get_activity_topics() - |> Streamer.stream(activity) - end - - def stream_out(_activity) do - :noop + def stream_out(activity) do + if activity.data["type"] in ["Create", "Announce", "Delete"] do + object = Object.normalize(activity) + # Do not stream out poll replies + unless object.data["type"] == "Answer" do + Pleroma.Web.Streamer.stream("user", activity) + Pleroma.Web.Streamer.stream("list", activity) + + if get_visibility(activity) == "public" do + Pleroma.Web.Streamer.stream("public", activity) + + if activity.local do + Pleroma.Web.Streamer.stream("public:local", activity) + end + + if activity.data["type"] in ["Create"] do + object.data + |> Map.get("tag", []) + |> Enum.filter(fn tag -> is_bitstring(tag) end) + |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) + + if object.data["attachment"] != [] do + Pleroma.Web.Streamer.stream("public:media", activity) + + if activity.local do + Pleroma.Web.Streamer.stream("public:local:media", activity) + end + end + end + else + if get_visibility(activity) == "direct", + do: Pleroma.Web.Streamer.stream("direct", activity) + end + end + end end def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 3c26eb406..dbd3542ea 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.Streamer @behaviour :cowboy_websocket @@ -25,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do ] @anonymous_streams ["public", "public:local", "hashtag"] - # Handled by periodic keepalive in Pleroma.Web.Streamer.Ping. + # Handled by periodic keepalive in Pleroma.Web.Streamer. @timeout :infinity def init(%{qs: qs} = req, state) do @@ -66,7 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do }, topic #{state.topic}" ) - Streamer.add_socket(state.topic, streamer_socket(state)) + Pleroma.Web.Streamer.add_socket(state.topic, streamer_socket(state)) {:ok, state} end @@ -81,7 +80,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do }, topic #{state.topic || "?"}: #{inspect(reason)}" ) - Streamer.remove_socket(state.topic, streamer_socket(state)) + Pleroma.Web.Streamer.remove_socket(state.topic, streamer_socket(state)) :ok end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex new file mode 100644 index 000000000..587c43f40 --- /dev/null +++ b/lib/pleroma/web/streamer.ex @@ -0,0 +1,318 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Streamer do + use GenServer + require Logger + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.Conversation.Participation + alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.NotificationView + + @keepalive_interval :timer.seconds(30) + + def start_link(_) do + GenServer.start_link(__MODULE__, %{}, name: __MODULE__) + end + + def add_socket(topic, socket) do + GenServer.cast(__MODULE__, %{action: :add, socket: socket, topic: topic}) + end + + def remove_socket(topic, socket) do + GenServer.cast(__MODULE__, %{action: :remove, socket: socket, topic: topic}) + end + + def stream(topic, item) do + GenServer.cast(__MODULE__, %{action: :stream, topic: topic, item: item}) + end + + def init(args) do + Process.send_after(self(), %{action: :ping}, @keepalive_interval) + + {:ok, args} + end + + def handle_info(%{action: :ping}, topics) do + topics + |> Map.values() + |> List.flatten() + |> Enum.each(fn socket -> + Logger.debug("Sending keepalive ping") + send(socket.transport_pid, {:text, ""}) + end) + + Process.send_after(self(), %{action: :ping}, @keepalive_interval) + + {:noreply, topics} + end + + def handle_cast(%{action: :stream, topic: "direct", item: item}, topics) do + recipient_topics = + User.get_recipients_from_activity(item) + |> Enum.map(fn %{id: id} -> "direct:#{id}" end) + + Enum.each(recipient_topics || [], fn user_topic -> + Logger.debug("Trying to push direct message to #{user_topic}\n\n") + push_to_socket(topics, user_topic, item) + end) + + {:noreply, topics} + end + + def handle_cast(%{action: :stream, topic: "participation", item: participation}, topics) do + user_topic = "direct:#{participation.user_id}" + Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") + + push_to_socket(topics, user_topic, participation) + + {:noreply, topics} + end + + def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do + # filter the recipient list if the activity is not public, see #270. + recipient_lists = + case Visibility.is_public?(item) do + true -> + Pleroma.List.get_lists_from_activity(item) + + _ -> + Pleroma.List.get_lists_from_activity(item) + |> Enum.filter(fn list -> + owner = User.get_cached_by_id(list.user_id) + + Visibility.visible_for_user?(item, owner) + end) + end + + recipient_topics = + recipient_lists + |> Enum.map(fn %{id: id} -> "list:#{id}" end) + + Enum.each(recipient_topics || [], fn list_topic -> + Logger.debug("Trying to push message to #{list_topic}\n\n") + push_to_socket(topics, list_topic, item) + end) + + {:noreply, topics} + end + + def handle_cast( + %{action: :stream, topic: topic, item: %Notification{} = item}, + topics + ) + when topic in ["user", "user:notification"] do + topics + |> Map.get("#{topic}:#{item.user_id}", []) + |> Enum.each(fn socket -> + with %User{} = user <- User.get_cached_by_ap_id(socket.assigns[:user].ap_id), + true <- should_send?(user, item) do + send( + socket.transport_pid, + {:text, represent_notification(socket.assigns[:user], item)} + ) + end + end) + + {:noreply, topics} + end + + def handle_cast(%{action: :stream, topic: "user", item: item}, topics) do + Logger.debug("Trying to push to users") + + recipient_topics = + User.get_recipients_from_activity(item) + |> Enum.map(fn %{id: id} -> "user:#{id}" end) + + Enum.each(recipient_topics, fn topic -> + push_to_socket(topics, topic, item) + end) + + {:noreply, topics} + end + + def handle_cast(%{action: :stream, topic: topic, item: item}, topics) do + Logger.debug("Trying to push to #{topic}") + Logger.debug("Pushing item to #{topic}") + push_to_socket(topics, topic, item) + {:noreply, topics} + end + + def handle_cast(%{action: :add, topic: topic, socket: socket}, sockets) do + topic = internal_topic(topic, socket) + sockets_for_topic = sockets[topic] || [] + sockets_for_topic = Enum.uniq([socket | sockets_for_topic]) + sockets = Map.put(sockets, topic, sockets_for_topic) + Logger.debug("Got new conn for #{topic}") + {:noreply, sockets} + end + + def handle_cast(%{action: :remove, topic: topic, socket: socket}, sockets) do + topic = internal_topic(topic, socket) + sockets_for_topic = sockets[topic] || [] + sockets_for_topic = List.delete(sockets_for_topic, socket) + sockets = Map.put(sockets, topic, sockets_for_topic) + Logger.debug("Removed conn for #{topic}") + {:noreply, sockets} + end + + def handle_cast(m, state) do + Logger.info("Unknown: #{inspect(m)}, #{inspect(state)}") + {:noreply, state} + end + + defp represent_update(%Activity{} = activity, %User{} = user) do + %{ + event: "update", + payload: + Pleroma.Web.MastodonAPI.StatusView.render( + "status.json", + activity: activity, + for: user + ) + |> Jason.encode!() + } + |> Jason.encode!() + end + + defp represent_update(%Activity{} = activity) do + %{ + event: "update", + payload: + Pleroma.Web.MastodonAPI.StatusView.render( + "status.json", + activity: activity + ) + |> Jason.encode!() + } + |> Jason.encode!() + end + + def represent_conversation(%Participation{} = participation) do + %{ + event: "conversation", + payload: + Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ + participation: participation, + for: participation.user + }) + |> Jason.encode!() + } + |> Jason.encode!() + end + + @spec represent_notification(User.t(), Notification.t()) :: binary() + defp represent_notification(%User{} = user, %Notification{} = notify) do + %{ + event: "notification", + payload: + NotificationView.render( + "show.json", + %{notification: notify, for: user} + ) + |> Jason.encode!() + } + |> Jason.encode!() + end + + defp should_send?(%User{} = user, %Activity{} = item) do + blocks = user.info.blocks || [] + mutes = user.info.mutes || [] + reblog_mutes = user.info.muted_reblogs || [] + domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks) + + with parent when not is_nil(parent) <- Object.normalize(item), + true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), + true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)), + %{host: item_host} <- URI.parse(item.actor), + %{host: parent_host} <- URI.parse(parent.data["actor"]), + false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), + false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), + true <- thread_containment(item, user), + false <- CommonAPI.thread_muted?(user, item) do + true + else + _ -> false + end + end + + defp should_send?(%User{} = user, %Notification{activity: activity}) do + should_send?(user, activity) + end + + def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do + Enum.each(topics[topic] || [], fn socket -> + # Get the current user so we have up-to-date blocks etc. + if socket.assigns[:user] do + user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) + + if should_send?(user, item) do + send(socket.transport_pid, {:text, represent_update(item, user)}) + end + else + send(socket.transport_pid, {:text, represent_update(item)}) + end + end) + end + + def push_to_socket(topics, topic, %Participation{} = participation) do + Enum.each(topics[topic] || [], fn socket -> + send(socket.transport_pid, {:text, represent_conversation(participation)}) + end) + end + + def push_to_socket(topics, topic, %Activity{ + data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} + }) do + Enum.each(topics[topic] || [], fn socket -> + send( + socket.transport_pid, + {:text, %{event: "delete", payload: to_string(deleted_activity_id)} |> Jason.encode!()} + ) + end) + end + + def push_to_socket(_topics, _topic, %Activity{data: %{"type" => "Delete"}}), do: :noop + + def push_to_socket(topics, topic, item) do + Enum.each(topics[topic] || [], fn socket -> + # Get the current user so we have up-to-date blocks etc. + if socket.assigns[:user] do + user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) + blocks = user.info.blocks || [] + mutes = user.info.mutes || [] + + with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)), + true <- thread_containment(item, user) do + send(socket.transport_pid, {:text, represent_update(item, user)}) + end + else + send(socket.transport_pid, {:text, represent_update(item)}) + end + end) + end + + defp internal_topic(topic, socket) when topic in ~w[user user:notification direct] do + "#{topic}:#{socket.assigns[:user].id}" + end + + defp internal_topic(topic, _), do: topic + + @spec thread_containment(Activity.t(), User.t()) :: boolean() + defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true + + defp thread_containment(activity, user) do + if Config.get([:instance, :skip_thread_containment]) do + true + else + ActivityPub.contain_activity(activity, user) + end + end +end diff --git a/lib/pleroma/web/streamer/ping.ex b/lib/pleroma/web/streamer/ping.ex deleted file mode 100644 index f77cbb95c..000000000 --- a/lib/pleroma/web/streamer/ping.ex +++ /dev/null @@ -1,33 +0,0 @@ -defmodule Pleroma.Web.Streamer.Ping do - use GenServer - require Logger - - alias Pleroma.Web.Streamer.State - alias Pleroma.Web.Streamer.StreamerSocket - - @keepalive_interval :timer.seconds(30) - - def start_link(opts) do - ping_interval = Keyword.get(opts, :ping_interval, @keepalive_interval) - GenServer.start_link(__MODULE__, %{ping_interval: ping_interval}, name: __MODULE__) - end - - def init(%{ping_interval: ping_interval} = args) do - Process.send_after(self(), :ping, ping_interval) - {:ok, args} - end - - def handle_info(:ping, %{ping_interval: ping_interval} = state) do - State.get_sockets() - |> Map.values() - |> List.flatten() - |> Enum.each(fn %StreamerSocket{transport_pid: transport_pid} -> - Logger.debug("Sending keepalive ping") - send(transport_pid, {:text, ""}) - end) - - Process.send_after(self(), :ping, ping_interval) - - {:noreply, state} - end -end diff --git a/lib/pleroma/web/streamer/state.ex b/lib/pleroma/web/streamer/state.ex deleted file mode 100644 index 7b5199068..000000000 --- a/lib/pleroma/web/streamer/state.ex +++ /dev/null @@ -1,68 +0,0 @@ -defmodule Pleroma.Web.Streamer.State do - use GenServer - require Logger - - alias Pleroma.Web.Streamer.StreamerSocket - - def start_link(_) do - GenServer.start_link(__MODULE__, %{sockets: %{}}, name: __MODULE__) - end - - def add_socket(topic, socket) do - GenServer.call(__MODULE__, {:add, socket, topic}) - end - - def remove_socket(topic, socket) do - GenServer.call(__MODULE__, {:remove, socket, topic}) - end - - def get_sockets do - %{sockets: stream_sockets} = GenServer.call(__MODULE__, :get_state) - stream_sockets - end - - def init(init_arg) do - {:ok, init_arg} - end - - def handle_call(:get_state, _from, state) do - {:reply, state, state} - end - - def handle_call({:add, socket, topic}, _from, %{sockets: sockets} = state) do - internal_topic = internal_topic(topic, socket) - stream_socket = StreamerSocket.from_socket(socket) - - sockets_for_topic = - sockets - |> Map.get(internal_topic, []) - |> List.insert_at(0, stream_socket) - |> Enum.uniq() - - state = put_in(state, [:sockets, internal_topic], sockets_for_topic) - Logger.debug("Got new conn for #{topic}") - {:reply, state, state} - end - - def handle_call({:remove, socket, topic}, _from, %{sockets: sockets} = state) do - internal_topic = internal_topic(topic, socket) - stream_socket = StreamerSocket.from_socket(socket) - - sockets_for_topic = - sockets - |> Map.get(internal_topic, []) - |> List.delete(stream_socket) - - state = Kernel.put_in(state, [:sockets, internal_topic], sockets_for_topic) - {:reply, state, state} - end - - defp internal_topic(topic, socket) - when topic in ~w[user user:notification direct] do - "#{topic}:#{socket.assigns[:user].id}" - end - - defp internal_topic(topic, _) do - topic - end -end diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex deleted file mode 100644 index 8cf719277..000000000 --- a/lib/pleroma/web/streamer/streamer.ex +++ /dev/null @@ -1,55 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Streamer do - alias Pleroma.Web.Streamer.State - alias Pleroma.Web.Streamer.Worker - - @timeout 60_000 - @mix_env Mix.env() - - def add_socket(topic, socket) do - State.add_socket(topic, socket) - end - - def remove_socket(topic, socket) do - State.remove_socket(topic, socket) - end - - def get_sockets do - State.get_sockets() - end - - def stream(topics, items) do - if should_send?() do - Task.async(fn -> - :poolboy.transaction( - :streamer_worker, - &Worker.stream(&1, topics, items), - @timeout - ) - end) - end - end - - def supervisor, do: Pleroma.Web.Streamer.Supervisor - - defp should_send? do - handle_should_send(@mix_env) - end - - defp handle_should_send(:test) do - case Process.whereis(:streamer_worker) do - nil -> - false - - pid -> - Process.alive?(pid) - end - end - - defp handle_should_send(_) do - true - end -end diff --git a/lib/pleroma/web/streamer/streamer_socket.ex b/lib/pleroma/web/streamer/streamer_socket.ex deleted file mode 100644 index f006c0306..000000000 --- a/lib/pleroma/web/streamer/streamer_socket.ex +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Pleroma.Web.Streamer.StreamerSocket do - defstruct transport_pid: nil, user: nil - - alias Pleroma.User - alias Pleroma.Web.Streamer.StreamerSocket - - def from_socket(%{ - transport_pid: transport_pid, - assigns: %{user: nil} - }) do - %StreamerSocket{ - transport_pid: transport_pid - } - end - - def from_socket(%{ - transport_pid: transport_pid, - assigns: %{user: %User{} = user} - }) do - %StreamerSocket{ - transport_pid: transport_pid, - user: user - } - end - - def from_socket(%{transport_pid: transport_pid}) do - %StreamerSocket{ - transport_pid: transport_pid - } - end -end diff --git a/lib/pleroma/web/streamer/supervisor.ex b/lib/pleroma/web/streamer/supervisor.ex deleted file mode 100644 index 6afe19323..000000000 --- a/lib/pleroma/web/streamer/supervisor.ex +++ /dev/null @@ -1,33 +0,0 @@ -defmodule Pleroma.Web.Streamer.Supervisor do - use Supervisor - - def start_link(opts) do - Supervisor.start_link(__MODULE__, opts, name: __MODULE__) - end - - def init(args) do - children = [ - {Pleroma.Web.Streamer.State, args}, - {Pleroma.Web.Streamer.Ping, args}, - :poolboy.child_spec(:streamer_worker, poolboy_config()) - ] - - opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor] - Supervisor.init(children, opts) - end - - defp poolboy_config do - opts = - Pleroma.Config.get(:streamer, - workers: 3, - overflow_workers: 2 - ) - - [ - {:name, {:local, :streamer_worker}}, - {:worker_module, Pleroma.Web.Streamer.Worker}, - {:size, opts[:workers]}, - {:max_overflow, opts[:overflow_workers]} - ] - end -end diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex deleted file mode 100644 index 5804508eb..000000000 --- a/lib/pleroma/web/streamer/worker.ex +++ /dev/null @@ -1,220 +0,0 @@ -defmodule Pleroma.Web.Streamer.Worker do - use GenServer - - require Logger - - alias Pleroma.Activity - alias Pleroma.Config - alias Pleroma.Conversation.Participation - alias Pleroma.Notification - alias Pleroma.Object - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.Streamer.State - alias Pleroma.Web.Streamer.StreamerSocket - alias Pleroma.Web.StreamerView - - def start_link(_) do - GenServer.start_link(__MODULE__, %{}, []) - end - - def init(init_arg) do - {:ok, init_arg} - end - - def stream(pid, topics, items) do - GenServer.call(pid, {:stream, topics, items}) - end - - def handle_call({:stream, topics, item}, _from, state) when is_list(topics) do - Enum.each(topics, fn t -> - do_stream(%{topic: t, item: item}) - end) - - {:reply, state, state} - end - - def handle_call({:stream, topic, items}, _from, state) when is_list(items) do - Enum.each(items, fn i -> - do_stream(%{topic: topic, item: i}) - end) - - {:reply, state, state} - end - - def handle_call({:stream, topic, item}, _from, state) do - do_stream(%{topic: topic, item: item}) - - {:reply, state, state} - end - - defp do_stream(%{topic: "direct", item: item}) do - recipient_topics = - User.get_recipients_from_activity(item) - |> Enum.map(fn %{id: id} -> "direct:#{id}" end) - - Enum.each(recipient_topics, fn user_topic -> - Logger.debug("Trying to push direct message to #{user_topic}\n\n") - push_to_socket(State.get_sockets(), user_topic, item) - end) - end - - defp do_stream(%{topic: "participation", item: participation}) do - user_topic = "direct:#{participation.user_id}" - Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") - - push_to_socket(State.get_sockets(), user_topic, participation) - end - - defp do_stream(%{topic: "list", item: item}) do - # filter the recipient list if the activity is not public, see #270. - recipient_lists = - case Visibility.is_public?(item) do - true -> - Pleroma.List.get_lists_from_activity(item) - - _ -> - Pleroma.List.get_lists_from_activity(item) - |> Enum.filter(fn list -> - owner = User.get_cached_by_id(list.user_id) - - Visibility.visible_for_user?(item, owner) - end) - end - - recipient_topics = - recipient_lists - |> Enum.map(fn %{id: id} -> "list:#{id}" end) - - Enum.each(recipient_topics, fn list_topic -> - Logger.debug("Trying to push message to #{list_topic}\n\n") - push_to_socket(State.get_sockets(), list_topic, item) - end) - end - - defp do_stream(%{topic: topic, item: %Notification{} = item}) - when topic in ["user", "user:notification"] do - State.get_sockets() - |> Map.get("#{topic}:#{item.user_id}", []) - |> Enum.each(fn %StreamerSocket{transport_pid: transport_pid, user: socket_user} -> - with %User{} = user <- User.get_cached_by_ap_id(socket_user.ap_id), - true <- should_send?(user, item) do - send(transport_pid, {:text, StreamerView.render("notification.json", socket_user, item)}) - end - end) - end - - defp do_stream(%{topic: "user", item: item}) do - Logger.debug("Trying to push to users") - - recipient_topics = - User.get_recipients_from_activity(item) - |> Enum.map(fn %{id: id} -> "user:#{id}" end) - - Enum.each(recipient_topics, fn topic -> - push_to_socket(State.get_sockets(), topic, item) - end) - end - - defp do_stream(%{topic: topic, item: item}) do - Logger.debug("Trying to push to #{topic}") - Logger.debug("Pushing item to #{topic}") - push_to_socket(State.get_sockets(), topic, item) - end - - defp should_send?(%User{} = user, %Activity{} = item) do - blocks = user.info.blocks || [] - mutes = user.info.mutes || [] - reblog_mutes = user.info.muted_reblogs || [] - domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks) - - with parent when not is_nil(parent) <- Object.normalize(item), - true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), - true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)), - %{host: item_host} <- URI.parse(item.actor), - %{host: parent_host} <- URI.parse(parent.data["actor"]), - false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), - false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), - true <- thread_containment(item, user), - false <- CommonAPI.thread_muted?(user, item) do - true - else - _ -> false - end - end - - defp should_send?(%User{} = user, %Notification{activity: activity}) do - should_send?(user, activity) - end - - def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do - Enum.each(topics[topic] || [], fn %StreamerSocket{ - transport_pid: transport_pid, - user: socket_user - } -> - # Get the current user so we have up-to-date blocks etc. - if socket_user do - user = User.get_cached_by_ap_id(socket_user.ap_id) - - if should_send?(user, item) do - send(transport_pid, {:text, StreamerView.render("update.json", item, user)}) - end - else - send(transport_pid, {:text, StreamerView.render("update.json", item)}) - end - end) - end - - def push_to_socket(topics, topic, %Participation{} = participation) do - Enum.each(topics[topic] || [], fn %StreamerSocket{transport_pid: transport_pid} -> - send(transport_pid, {:text, StreamerView.render("conversation.json", participation)}) - end) - end - - def push_to_socket(topics, topic, %Activity{ - data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} - }) do - Enum.each(topics[topic] || [], fn %StreamerSocket{transport_pid: transport_pid} -> - send( - transport_pid, - {:text, %{event: "delete", payload: to_string(deleted_activity_id)} |> Jason.encode!()} - ) - end) - end - - def push_to_socket(_topics, _topic, %Activity{data: %{"type" => "Delete"}}), do: :noop - - def push_to_socket(topics, topic, item) do - Enum.each(topics[topic] || [], fn %StreamerSocket{ - transport_pid: transport_pid, - user: socket_user - } -> - # Get the current user so we have up-to-date blocks etc. - if socket_user do - user = User.get_cached_by_ap_id(socket_user.ap_id) - blocks = user.info.blocks || [] - mutes = user.info.mutes || [] - - with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)), - true <- thread_containment(item, user) do - send(transport_pid, {:text, StreamerView.render("update.json", item, user)}) - end - else - send(transport_pid, {:text, StreamerView.render("update.json", item)}) - end - end) - end - - @spec thread_containment(Activity.t(), User.t()) :: boolean() - defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true - - defp thread_containment(activity, user) do - if Config.get([:instance, :skip_thread_containment]) do - true - else - ActivityPub.contain_activity(activity, user) - end - end -end diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex deleted file mode 100644 index b13030fa0..000000000 --- a/lib/pleroma/web/views/streamer_view.ex +++ /dev/null @@ -1,66 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.StreamerView do - use Pleroma.Web, :view - - alias Pleroma.Activity - alias Pleroma.Conversation.Participation - alias Pleroma.Notification - alias Pleroma.User - alias Pleroma.Web.MastodonAPI.NotificationView - - def render("update.json", %Activity{} = activity, %User{} = user) do - %{ - event: "update", - payload: - Pleroma.Web.MastodonAPI.StatusView.render( - "status.json", - activity: activity, - for: user - ) - |> Jason.encode!() - } - |> Jason.encode!() - end - - def render("notification.json", %User{} = user, %Notification{} = notify) do - %{ - event: "notification", - payload: - NotificationView.render( - "show.json", - %{notification: notify, for: user} - ) - |> Jason.encode!() - } - |> Jason.encode!() - end - - def render("update.json", %Activity{} = activity) do - %{ - event: "update", - payload: - Pleroma.Web.MastodonAPI.StatusView.render( - "status.json", - activity: activity - ) - |> Jason.encode!() - } - |> Jason.encode!() - end - - def render("conversation.json", %Participation{} = participation) do - %{ - event: "conversation", - payload: - Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ - participation: participation, - for: participation.user - }) - |> Jason.encode!() - } - |> Jason.encode!() - end -end -- cgit v1.2.3 From 96816ceaa25c21cec7677e75dcddd7ffb42d83c3 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 16 Sep 2019 17:03:37 +0700 Subject: Revert "Merge branch 'revert-4fabf83a' into 'develop'" This reverts commit fe7fd331263007e0fb2877ef7370a09a9704da36, reversing changes made to 4fabf83ad01352442906d79187aeab4c777f4df8. --- lib/pleroma/activity/ir/topics.ex | 63 +++++ lib/pleroma/application.ex | 2 +- lib/pleroma/notification.ex | 6 +- lib/pleroma/web/activity_pub/activity_pub.ex | 50 +--- lib/pleroma/web/mastodon_api/websocket_handler.ex | 7 +- lib/pleroma/web/streamer.ex | 318 ---------------------- lib/pleroma/web/streamer/ping.ex | 33 +++ lib/pleroma/web/streamer/state.ex | 68 +++++ lib/pleroma/web/streamer/streamer.ex | 55 ++++ lib/pleroma/web/streamer/streamer_socket.ex | 31 +++ lib/pleroma/web/streamer/supervisor.ex | 33 +++ lib/pleroma/web/streamer/worker.ex | 220 +++++++++++++++ lib/pleroma/web/views/streamer_view.ex | 66 +++++ 13 files changed, 590 insertions(+), 362 deletions(-) create mode 100644 lib/pleroma/activity/ir/topics.ex delete mode 100644 lib/pleroma/web/streamer.ex create mode 100644 lib/pleroma/web/streamer/ping.ex create mode 100644 lib/pleroma/web/streamer/state.ex create mode 100644 lib/pleroma/web/streamer/streamer.ex create mode 100644 lib/pleroma/web/streamer/streamer_socket.ex create mode 100644 lib/pleroma/web/streamer/supervisor.ex create mode 100644 lib/pleroma/web/streamer/worker.ex create mode 100644 lib/pleroma/web/views/streamer_view.ex (limited to 'lib') diff --git a/lib/pleroma/activity/ir/topics.ex b/lib/pleroma/activity/ir/topics.ex new file mode 100644 index 000000000..010897abc --- /dev/null +++ b/lib/pleroma/activity/ir/topics.ex @@ -0,0 +1,63 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Activity.Ir.Topics do + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Visibility + + def get_activity_topics(activity) do + activity + |> Object.normalize() + |> generate_topics(activity) + |> List.flatten() + end + + defp generate_topics(%{data: %{"type" => "Answer"}}, _) do + [] + end + + defp generate_topics(object, activity) do + ["user", "list"] ++ visibility_tags(object, activity) + end + + defp visibility_tags(object, activity) do + case Visibility.get_visibility(activity) do + "public" -> + if activity.local do + ["public", "public:local"] + else + ["public"] + end + |> item_creation_tags(object, activity) + + "direct" -> + ["direct"] + + _ -> + [] + end + end + + defp item_creation_tags(tags, %{data: %{"type" => "Create"}} = object, activity) do + tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity) + end + + defp item_creation_tags(tags, _, _) do + tags + end + + defp hashtags_to_topics(%{data: %{"tag" => tags}}) do + tags + |> Enum.filter(&is_bitstring(&1)) + |> Enum.map(fn tag -> "hashtag:" <> tag end) + end + + defp hashtags_to_topics(_), do: [] + + defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: [] + + defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"] + + defp attachment_topics(_object, _act), do: ["public:media"] +end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 49094704b..3b37ce630 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -141,7 +141,7 @@ defmodule Pleroma.Application do defp streamer_child(:test), do: [] defp streamer_child(_) do - [Pleroma.Web.Streamer] + [Pleroma.Web.Streamer.supervisor()] end defp oauth_cleanup_child(true), diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b7c880c51..8012389ac 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -210,8 +210,10 @@ defmodule Pleroma.Notification do unless skip?(activity, user) do notification = %Notification{user_id: user.id, activity: activity} {:ok, notification} = Repo.insert(notification) - Streamer.stream("user", notification) - Streamer.stream("user:notification", notification) + + ["user", "user:notification"] + |> Streamer.stream(notification) + Push.send(notification) notification end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 41f6a0f1f..bc5ae7fbf 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Activity + alias Pleroma.Activity.Ir.Topics alias Pleroma.Config alias Pleroma.Conversation alias Pleroma.Notification @@ -16,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.User alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.Streamer alias Pleroma.Web.WebFinger alias Pleroma.Workers.BackgroundWorker @@ -187,9 +189,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do participations |> Repo.preload(:user) - Enum.each(participations, fn participation -> - Pleroma.Web.Streamer.stream("participation", participation) - end) + Streamer.stream("participation", participations) end def stream_out_participations(%Object{data: %{"context" => context}}, user) do @@ -208,41 +208,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def stream_out_participations(_, _), do: :noop - def stream_out(activity) do - if activity.data["type"] in ["Create", "Announce", "Delete"] do - object = Object.normalize(activity) - # Do not stream out poll replies - unless object.data["type"] == "Answer" do - Pleroma.Web.Streamer.stream("user", activity) - Pleroma.Web.Streamer.stream("list", activity) - - if get_visibility(activity) == "public" do - Pleroma.Web.Streamer.stream("public", activity) - - if activity.local do - Pleroma.Web.Streamer.stream("public:local", activity) - end - - if activity.data["type"] in ["Create"] do - object.data - |> Map.get("tag", []) - |> Enum.filter(fn tag -> is_bitstring(tag) end) - |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) - - if object.data["attachment"] != [] do - Pleroma.Web.Streamer.stream("public:media", activity) - - if activity.local do - Pleroma.Web.Streamer.stream("public:local:media", activity) - end - end - end - else - if get_visibility(activity) == "direct", - do: Pleroma.Web.Streamer.stream("direct", activity) - end - end - end + def stream_out(%Activity{data: %{"type" => data_type}} = activity) + when data_type in ["Create", "Announce", "Delete"] do + activity + |> Topics.get_activity_topics() + |> Streamer.stream(activity) + end + + def stream_out(_activity) do + :noop end def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index dbd3542ea..3c26eb406 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.Streamer @behaviour :cowboy_websocket @@ -24,7 +25,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do ] @anonymous_streams ["public", "public:local", "hashtag"] - # Handled by periodic keepalive in Pleroma.Web.Streamer. + # Handled by periodic keepalive in Pleroma.Web.Streamer.Ping. @timeout :infinity def init(%{qs: qs} = req, state) do @@ -65,7 +66,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do }, topic #{state.topic}" ) - Pleroma.Web.Streamer.add_socket(state.topic, streamer_socket(state)) + Streamer.add_socket(state.topic, streamer_socket(state)) {:ok, state} end @@ -80,7 +81,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do }, topic #{state.topic || "?"}: #{inspect(reason)}" ) - Pleroma.Web.Streamer.remove_socket(state.topic, streamer_socket(state)) + Streamer.remove_socket(state.topic, streamer_socket(state)) :ok end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex deleted file mode 100644 index 587c43f40..000000000 --- a/lib/pleroma/web/streamer.ex +++ /dev/null @@ -1,318 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Streamer do - use GenServer - require Logger - alias Pleroma.Activity - alias Pleroma.Config - alias Pleroma.Conversation.Participation - alias Pleroma.Notification - alias Pleroma.Object - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MastodonAPI.NotificationView - - @keepalive_interval :timer.seconds(30) - - def start_link(_) do - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) - end - - def add_socket(topic, socket) do - GenServer.cast(__MODULE__, %{action: :add, socket: socket, topic: topic}) - end - - def remove_socket(topic, socket) do - GenServer.cast(__MODULE__, %{action: :remove, socket: socket, topic: topic}) - end - - def stream(topic, item) do - GenServer.cast(__MODULE__, %{action: :stream, topic: topic, item: item}) - end - - def init(args) do - Process.send_after(self(), %{action: :ping}, @keepalive_interval) - - {:ok, args} - end - - def handle_info(%{action: :ping}, topics) do - topics - |> Map.values() - |> List.flatten() - |> Enum.each(fn socket -> - Logger.debug("Sending keepalive ping") - send(socket.transport_pid, {:text, ""}) - end) - - Process.send_after(self(), %{action: :ping}, @keepalive_interval) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: "direct", item: item}, topics) do - recipient_topics = - User.get_recipients_from_activity(item) - |> Enum.map(fn %{id: id} -> "direct:#{id}" end) - - Enum.each(recipient_topics || [], fn user_topic -> - Logger.debug("Trying to push direct message to #{user_topic}\n\n") - push_to_socket(topics, user_topic, item) - end) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: "participation", item: participation}, topics) do - user_topic = "direct:#{participation.user_id}" - Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") - - push_to_socket(topics, user_topic, participation) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do - # filter the recipient list if the activity is not public, see #270. - recipient_lists = - case Visibility.is_public?(item) do - true -> - Pleroma.List.get_lists_from_activity(item) - - _ -> - Pleroma.List.get_lists_from_activity(item) - |> Enum.filter(fn list -> - owner = User.get_cached_by_id(list.user_id) - - Visibility.visible_for_user?(item, owner) - end) - end - - recipient_topics = - recipient_lists - |> Enum.map(fn %{id: id} -> "list:#{id}" end) - - Enum.each(recipient_topics || [], fn list_topic -> - Logger.debug("Trying to push message to #{list_topic}\n\n") - push_to_socket(topics, list_topic, item) - end) - - {:noreply, topics} - end - - def handle_cast( - %{action: :stream, topic: topic, item: %Notification{} = item}, - topics - ) - when topic in ["user", "user:notification"] do - topics - |> Map.get("#{topic}:#{item.user_id}", []) - |> Enum.each(fn socket -> - with %User{} = user <- User.get_cached_by_ap_id(socket.assigns[:user].ap_id), - true <- should_send?(user, item) do - send( - socket.transport_pid, - {:text, represent_notification(socket.assigns[:user], item)} - ) - end - end) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: "user", item: item}, topics) do - Logger.debug("Trying to push to users") - - recipient_topics = - User.get_recipients_from_activity(item) - |> Enum.map(fn %{id: id} -> "user:#{id}" end) - - Enum.each(recipient_topics, fn topic -> - push_to_socket(topics, topic, item) - end) - - {:noreply, topics} - end - - def handle_cast(%{action: :stream, topic: topic, item: item}, topics) do - Logger.debug("Trying to push to #{topic}") - Logger.debug("Pushing item to #{topic}") - push_to_socket(topics, topic, item) - {:noreply, topics} - end - - def handle_cast(%{action: :add, topic: topic, socket: socket}, sockets) do - topic = internal_topic(topic, socket) - sockets_for_topic = sockets[topic] || [] - sockets_for_topic = Enum.uniq([socket | sockets_for_topic]) - sockets = Map.put(sockets, topic, sockets_for_topic) - Logger.debug("Got new conn for #{topic}") - {:noreply, sockets} - end - - def handle_cast(%{action: :remove, topic: topic, socket: socket}, sockets) do - topic = internal_topic(topic, socket) - sockets_for_topic = sockets[topic] || [] - sockets_for_topic = List.delete(sockets_for_topic, socket) - sockets = Map.put(sockets, topic, sockets_for_topic) - Logger.debug("Removed conn for #{topic}") - {:noreply, sockets} - end - - def handle_cast(m, state) do - Logger.info("Unknown: #{inspect(m)}, #{inspect(state)}") - {:noreply, state} - end - - defp represent_update(%Activity{} = activity, %User{} = user) do - %{ - event: "update", - payload: - Pleroma.Web.MastodonAPI.StatusView.render( - "status.json", - activity: activity, - for: user - ) - |> Jason.encode!() - } - |> Jason.encode!() - end - - defp represent_update(%Activity{} = activity) do - %{ - event: "update", - payload: - Pleroma.Web.MastodonAPI.StatusView.render( - "status.json", - activity: activity - ) - |> Jason.encode!() - } - |> Jason.encode!() - end - - def represent_conversation(%Participation{} = participation) do - %{ - event: "conversation", - payload: - Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ - participation: participation, - for: participation.user - }) - |> Jason.encode!() - } - |> Jason.encode!() - end - - @spec represent_notification(User.t(), Notification.t()) :: binary() - defp represent_notification(%User{} = user, %Notification{} = notify) do - %{ - event: "notification", - payload: - NotificationView.render( - "show.json", - %{notification: notify, for: user} - ) - |> Jason.encode!() - } - |> Jason.encode!() - end - - defp should_send?(%User{} = user, %Activity{} = item) do - blocks = user.info.blocks || [] - mutes = user.info.mutes || [] - reblog_mutes = user.info.muted_reblogs || [] - domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks) - - with parent when not is_nil(parent) <- Object.normalize(item), - true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), - true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)), - %{host: item_host} <- URI.parse(item.actor), - %{host: parent_host} <- URI.parse(parent.data["actor"]), - false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), - false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), - true <- thread_containment(item, user), - false <- CommonAPI.thread_muted?(user, item) do - true - else - _ -> false - end - end - - defp should_send?(%User{} = user, %Notification{activity: activity}) do - should_send?(user, activity) - end - - def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do - Enum.each(topics[topic] || [], fn socket -> - # Get the current user so we have up-to-date blocks etc. - if socket.assigns[:user] do - user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) - - if should_send?(user, item) do - send(socket.transport_pid, {:text, represent_update(item, user)}) - end - else - send(socket.transport_pid, {:text, represent_update(item)}) - end - end) - end - - def push_to_socket(topics, topic, %Participation{} = participation) do - Enum.each(topics[topic] || [], fn socket -> - send(socket.transport_pid, {:text, represent_conversation(participation)}) - end) - end - - def push_to_socket(topics, topic, %Activity{ - data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} - }) do - Enum.each(topics[topic] || [], fn socket -> - send( - socket.transport_pid, - {:text, %{event: "delete", payload: to_string(deleted_activity_id)} |> Jason.encode!()} - ) - end) - end - - def push_to_socket(_topics, _topic, %Activity{data: %{"type" => "Delete"}}), do: :noop - - def push_to_socket(topics, topic, item) do - Enum.each(topics[topic] || [], fn socket -> - # Get the current user so we have up-to-date blocks etc. - if socket.assigns[:user] do - user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) - blocks = user.info.blocks || [] - mutes = user.info.mutes || [] - - with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)), - true <- thread_containment(item, user) do - send(socket.transport_pid, {:text, represent_update(item, user)}) - end - else - send(socket.transport_pid, {:text, represent_update(item)}) - end - end) - end - - defp internal_topic(topic, socket) when topic in ~w[user user:notification direct] do - "#{topic}:#{socket.assigns[:user].id}" - end - - defp internal_topic(topic, _), do: topic - - @spec thread_containment(Activity.t(), User.t()) :: boolean() - defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true - - defp thread_containment(activity, user) do - if Config.get([:instance, :skip_thread_containment]) do - true - else - ActivityPub.contain_activity(activity, user) - end - end -end diff --git a/lib/pleroma/web/streamer/ping.ex b/lib/pleroma/web/streamer/ping.ex new file mode 100644 index 000000000..f77cbb95c --- /dev/null +++ b/lib/pleroma/web/streamer/ping.ex @@ -0,0 +1,33 @@ +defmodule Pleroma.Web.Streamer.Ping do + use GenServer + require Logger + + alias Pleroma.Web.Streamer.State + alias Pleroma.Web.Streamer.StreamerSocket + + @keepalive_interval :timer.seconds(30) + + def start_link(opts) do + ping_interval = Keyword.get(opts, :ping_interval, @keepalive_interval) + GenServer.start_link(__MODULE__, %{ping_interval: ping_interval}, name: __MODULE__) + end + + def init(%{ping_interval: ping_interval} = args) do + Process.send_after(self(), :ping, ping_interval) + {:ok, args} + end + + def handle_info(:ping, %{ping_interval: ping_interval} = state) do + State.get_sockets() + |> Map.values() + |> List.flatten() + |> Enum.each(fn %StreamerSocket{transport_pid: transport_pid} -> + Logger.debug("Sending keepalive ping") + send(transport_pid, {:text, ""}) + end) + + Process.send_after(self(), :ping, ping_interval) + + {:noreply, state} + end +end diff --git a/lib/pleroma/web/streamer/state.ex b/lib/pleroma/web/streamer/state.ex new file mode 100644 index 000000000..7b5199068 --- /dev/null +++ b/lib/pleroma/web/streamer/state.ex @@ -0,0 +1,68 @@ +defmodule Pleroma.Web.Streamer.State do + use GenServer + require Logger + + alias Pleroma.Web.Streamer.StreamerSocket + + def start_link(_) do + GenServer.start_link(__MODULE__, %{sockets: %{}}, name: __MODULE__) + end + + def add_socket(topic, socket) do + GenServer.call(__MODULE__, {:add, socket, topic}) + end + + def remove_socket(topic, socket) do + GenServer.call(__MODULE__, {:remove, socket, topic}) + end + + def get_sockets do + %{sockets: stream_sockets} = GenServer.call(__MODULE__, :get_state) + stream_sockets + end + + def init(init_arg) do + {:ok, init_arg} + end + + def handle_call(:get_state, _from, state) do + {:reply, state, state} + end + + def handle_call({:add, socket, topic}, _from, %{sockets: sockets} = state) do + internal_topic = internal_topic(topic, socket) + stream_socket = StreamerSocket.from_socket(socket) + + sockets_for_topic = + sockets + |> Map.get(internal_topic, []) + |> List.insert_at(0, stream_socket) + |> Enum.uniq() + + state = put_in(state, [:sockets, internal_topic], sockets_for_topic) + Logger.debug("Got new conn for #{topic}") + {:reply, state, state} + end + + def handle_call({:remove, socket, topic}, _from, %{sockets: sockets} = state) do + internal_topic = internal_topic(topic, socket) + stream_socket = StreamerSocket.from_socket(socket) + + sockets_for_topic = + sockets + |> Map.get(internal_topic, []) + |> List.delete(stream_socket) + + state = Kernel.put_in(state, [:sockets, internal_topic], sockets_for_topic) + {:reply, state, state} + end + + defp internal_topic(topic, socket) + when topic in ~w[user user:notification direct] do + "#{topic}:#{socket.assigns[:user].id}" + end + + defp internal_topic(topic, _) do + topic + end +end diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex new file mode 100644 index 000000000..8cf719277 --- /dev/null +++ b/lib/pleroma/web/streamer/streamer.ex @@ -0,0 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Streamer do + alias Pleroma.Web.Streamer.State + alias Pleroma.Web.Streamer.Worker + + @timeout 60_000 + @mix_env Mix.env() + + def add_socket(topic, socket) do + State.add_socket(topic, socket) + end + + def remove_socket(topic, socket) do + State.remove_socket(topic, socket) + end + + def get_sockets do + State.get_sockets() + end + + def stream(topics, items) do + if should_send?() do + Task.async(fn -> + :poolboy.transaction( + :streamer_worker, + &Worker.stream(&1, topics, items), + @timeout + ) + end) + end + end + + def supervisor, do: Pleroma.Web.Streamer.Supervisor + + defp should_send? do + handle_should_send(@mix_env) + end + + defp handle_should_send(:test) do + case Process.whereis(:streamer_worker) do + nil -> + false + + pid -> + Process.alive?(pid) + end + end + + defp handle_should_send(_) do + true + end +end diff --git a/lib/pleroma/web/streamer/streamer_socket.ex b/lib/pleroma/web/streamer/streamer_socket.ex new file mode 100644 index 000000000..f006c0306 --- /dev/null +++ b/lib/pleroma/web/streamer/streamer_socket.ex @@ -0,0 +1,31 @@ +defmodule Pleroma.Web.Streamer.StreamerSocket do + defstruct transport_pid: nil, user: nil + + alias Pleroma.User + alias Pleroma.Web.Streamer.StreamerSocket + + def from_socket(%{ + transport_pid: transport_pid, + assigns: %{user: nil} + }) do + %StreamerSocket{ + transport_pid: transport_pid + } + end + + def from_socket(%{ + transport_pid: transport_pid, + assigns: %{user: %User{} = user} + }) do + %StreamerSocket{ + transport_pid: transport_pid, + user: user + } + end + + def from_socket(%{transport_pid: transport_pid}) do + %StreamerSocket{ + transport_pid: transport_pid + } + end +end diff --git a/lib/pleroma/web/streamer/supervisor.ex b/lib/pleroma/web/streamer/supervisor.ex new file mode 100644 index 000000000..6afe19323 --- /dev/null +++ b/lib/pleroma/web/streamer/supervisor.ex @@ -0,0 +1,33 @@ +defmodule Pleroma.Web.Streamer.Supervisor do + use Supervisor + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + def init(args) do + children = [ + {Pleroma.Web.Streamer.State, args}, + {Pleroma.Web.Streamer.Ping, args}, + :poolboy.child_spec(:streamer_worker, poolboy_config()) + ] + + opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor] + Supervisor.init(children, opts) + end + + defp poolboy_config do + opts = + Pleroma.Config.get(:streamer, + workers: 3, + overflow_workers: 2 + ) + + [ + {:name, {:local, :streamer_worker}}, + {:worker_module, Pleroma.Web.Streamer.Worker}, + {:size, opts[:workers]}, + {:max_overflow, opts[:overflow_workers]} + ] + end +end diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex new file mode 100644 index 000000000..5804508eb --- /dev/null +++ b/lib/pleroma/web/streamer/worker.ex @@ -0,0 +1,220 @@ +defmodule Pleroma.Web.Streamer.Worker do + use GenServer + + require Logger + + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.Conversation.Participation + alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Streamer.State + alias Pleroma.Web.Streamer.StreamerSocket + alias Pleroma.Web.StreamerView + + def start_link(_) do + GenServer.start_link(__MODULE__, %{}, []) + end + + def init(init_arg) do + {:ok, init_arg} + end + + def stream(pid, topics, items) do + GenServer.call(pid, {:stream, topics, items}) + end + + def handle_call({:stream, topics, item}, _from, state) when is_list(topics) do + Enum.each(topics, fn t -> + do_stream(%{topic: t, item: item}) + end) + + {:reply, state, state} + end + + def handle_call({:stream, topic, items}, _from, state) when is_list(items) do + Enum.each(items, fn i -> + do_stream(%{topic: topic, item: i}) + end) + + {:reply, state, state} + end + + def handle_call({:stream, topic, item}, _from, state) do + do_stream(%{topic: topic, item: item}) + + {:reply, state, state} + end + + defp do_stream(%{topic: "direct", item: item}) do + recipient_topics = + User.get_recipients_from_activity(item) + |> Enum.map(fn %{id: id} -> "direct:#{id}" end) + + Enum.each(recipient_topics, fn user_topic -> + Logger.debug("Trying to push direct message to #{user_topic}\n\n") + push_to_socket(State.get_sockets(), user_topic, item) + end) + end + + defp do_stream(%{topic: "participation", item: participation}) do + user_topic = "direct:#{participation.user_id}" + Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") + + push_to_socket(State.get_sockets(), user_topic, participation) + end + + defp do_stream(%{topic: "list", item: item}) do + # filter the recipient list if the activity is not public, see #270. + recipient_lists = + case Visibility.is_public?(item) do + true -> + Pleroma.List.get_lists_from_activity(item) + + _ -> + Pleroma.List.get_lists_from_activity(item) + |> Enum.filter(fn list -> + owner = User.get_cached_by_id(list.user_id) + + Visibility.visible_for_user?(item, owner) + end) + end + + recipient_topics = + recipient_lists + |> Enum.map(fn %{id: id} -> "list:#{id}" end) + + Enum.each(recipient_topics, fn list_topic -> + Logger.debug("Trying to push message to #{list_topic}\n\n") + push_to_socket(State.get_sockets(), list_topic, item) + end) + end + + defp do_stream(%{topic: topic, item: %Notification{} = item}) + when topic in ["user", "user:notification"] do + State.get_sockets() + |> Map.get("#{topic}:#{item.user_id}", []) + |> Enum.each(fn %StreamerSocket{transport_pid: transport_pid, user: socket_user} -> + with %User{} = user <- User.get_cached_by_ap_id(socket_user.ap_id), + true <- should_send?(user, item) do + send(transport_pid, {:text, StreamerView.render("notification.json", socket_user, item)}) + end + end) + end + + defp do_stream(%{topic: "user", item: item}) do + Logger.debug("Trying to push to users") + + recipient_topics = + User.get_recipients_from_activity(item) + |> Enum.map(fn %{id: id} -> "user:#{id}" end) + + Enum.each(recipient_topics, fn topic -> + push_to_socket(State.get_sockets(), topic, item) + end) + end + + defp do_stream(%{topic: topic, item: item}) do + Logger.debug("Trying to push to #{topic}") + Logger.debug("Pushing item to #{topic}") + push_to_socket(State.get_sockets(), topic, item) + end + + defp should_send?(%User{} = user, %Activity{} = item) do + blocks = user.info.blocks || [] + mutes = user.info.mutes || [] + reblog_mutes = user.info.muted_reblogs || [] + domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks) + + with parent when not is_nil(parent) <- Object.normalize(item), + true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), + true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)), + %{host: item_host} <- URI.parse(item.actor), + %{host: parent_host} <- URI.parse(parent.data["actor"]), + false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), + false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), + true <- thread_containment(item, user), + false <- CommonAPI.thread_muted?(user, item) do + true + else + _ -> false + end + end + + defp should_send?(%User{} = user, %Notification{activity: activity}) do + should_send?(user, activity) + end + + def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do + Enum.each(topics[topic] || [], fn %StreamerSocket{ + transport_pid: transport_pid, + user: socket_user + } -> + # Get the current user so we have up-to-date blocks etc. + if socket_user do + user = User.get_cached_by_ap_id(socket_user.ap_id) + + if should_send?(user, item) do + send(transport_pid, {:text, StreamerView.render("update.json", item, user)}) + end + else + send(transport_pid, {:text, StreamerView.render("update.json", item)}) + end + end) + end + + def push_to_socket(topics, topic, %Participation{} = participation) do + Enum.each(topics[topic] || [], fn %StreamerSocket{transport_pid: transport_pid} -> + send(transport_pid, {:text, StreamerView.render("conversation.json", participation)}) + end) + end + + def push_to_socket(topics, topic, %Activity{ + data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} + }) do + Enum.each(topics[topic] || [], fn %StreamerSocket{transport_pid: transport_pid} -> + send( + transport_pid, + {:text, %{event: "delete", payload: to_string(deleted_activity_id)} |> Jason.encode!()} + ) + end) + end + + def push_to_socket(_topics, _topic, %Activity{data: %{"type" => "Delete"}}), do: :noop + + def push_to_socket(topics, topic, item) do + Enum.each(topics[topic] || [], fn %StreamerSocket{ + transport_pid: transport_pid, + user: socket_user + } -> + # Get the current user so we have up-to-date blocks etc. + if socket_user do + user = User.get_cached_by_ap_id(socket_user.ap_id) + blocks = user.info.blocks || [] + mutes = user.info.mutes || [] + + with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)), + true <- thread_containment(item, user) do + send(transport_pid, {:text, StreamerView.render("update.json", item, user)}) + end + else + send(transport_pid, {:text, StreamerView.render("update.json", item)}) + end + end) + end + + @spec thread_containment(Activity.t(), User.t()) :: boolean() + defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true + + defp thread_containment(activity, user) do + if Config.get([:instance, :skip_thread_containment]) do + true + else + ActivityPub.contain_activity(activity, user) + end + end +end diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex new file mode 100644 index 000000000..b13030fa0 --- /dev/null +++ b/lib/pleroma/web/views/streamer_view.ex @@ -0,0 +1,66 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.StreamerView do + use Pleroma.Web, :view + + alias Pleroma.Activity + alias Pleroma.Conversation.Participation + alias Pleroma.Notification + alias Pleroma.User + alias Pleroma.Web.MastodonAPI.NotificationView + + def render("update.json", %Activity{} = activity, %User{} = user) do + %{ + event: "update", + payload: + Pleroma.Web.MastodonAPI.StatusView.render( + "status.json", + activity: activity, + for: user + ) + |> Jason.encode!() + } + |> Jason.encode!() + end + + def render("notification.json", %User{} = user, %Notification{} = notify) do + %{ + event: "notification", + payload: + NotificationView.render( + "show.json", + %{notification: notify, for: user} + ) + |> Jason.encode!() + } + |> Jason.encode!() + end + + def render("update.json", %Activity{} = activity) do + %{ + event: "update", + payload: + Pleroma.Web.MastodonAPI.StatusView.render( + "status.json", + activity: activity + ) + |> Jason.encode!() + } + |> Jason.encode!() + end + + def render("conversation.json", %Participation{} = participation) do + %{ + event: "conversation", + payload: + Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ + participation: participation, + for: participation.user + }) + |> Jason.encode!() + } + |> Jason.encode!() + end +end -- cgit v1.2.3 From 085d014f0859b3b3e5023c423ae0361ec6ed6c67 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 16 Sep 2019 19:26:00 +0700 Subject: Fix `Transmogrifier.upgrade_user_from_ap_id/1` --- lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index acb3087d0..8461b666e 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -1050,7 +1050,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id), {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id), already_ap <- User.ap_enabled?(user), - {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do + {:ok, user} <- user |> User.upgrade_changeset(data, true) |> User.update_and_set_cache() do unless already_ap do TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id}) end -- cgit v1.2.3 From 56b60798c2282055089424f5dc6770a10876626b Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Mon, 16 Sep 2019 20:50:14 +0300 Subject: Code style fixes --- lib/pleroma/subscription_notification.ex | 72 ++++++++++------------ .../views/subscription_notification_view.ex | 1 - 2 files changed, 33 insertions(+), 40 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/subscription_notification.ex b/lib/pleroma/subscription_notification.ex index 7ae25a7b1..9ce0c6598 100644 --- a/lib/pleroma/subscription_notification.ex +++ b/lib/pleroma/subscription_notification.ex @@ -56,7 +56,8 @@ defmodule Pleroma.SubscriptionNotification do if opts[:with_muted] do query else - where(query, [n, a], a.actor not in ^user.info.muted_notifications) + query + |> where([n, a], a.actor not in ^user.info.muted_notifications) |> where([n, a], a.actor not in ^user.info.blocks) |> where( [n, a], @@ -88,9 +89,9 @@ defmodule Pleroma.SubscriptionNotification do """ @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()] def for_user_since(user, date) do - from(n in for_user_query(user), - where: n.updated_at > ^date - ) + user + |> for_user_query() + |> where([n], n.updated_at > ^date) |> Repo.all() end @@ -112,10 +113,8 @@ defmodule Pleroma.SubscriptionNotification do preload: [activity: activity] ) - notification = Repo.one(query) - - case notification do - %{user_id: ^user_id} -> + case Repo.one(query) do + %{user_id: ^user_id} = notification -> {:ok, notification} _ -> @@ -137,10 +136,8 @@ defmodule Pleroma.SubscriptionNotification do end def dismiss(%{id: user_id} = _user, id) do - notification = Repo.get(SubscriptionNotification, id) - - case notification do - %{user_id: ^user_id} -> + case Repo.get(SubscriptionNotification, id) do + %{user_id: ^user_id} = notification -> Repo.delete(notification) _ -> @@ -149,21 +146,24 @@ defmodule Pleroma.SubscriptionNotification do end def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do - object = Object.normalize(activity) + case Object.normalize(activity) do + %{data: %{"type" => "Answer"}} -> + {:ok, []} - unless object && object.data["type"] == "Answer" do - users = get_notified_from_activity(activity) - notifications = Enum.map(users, fn user -> create_notification(activity, user) end) - {:ok, notifications} - else - {:ok, []} + _ -> + users = get_notified_from_activity(activity) + notifications = Enum.map(users, fn user -> create_notification(activity, user) end) + {:ok, notifications} end end def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity) when type in ["Like", "Announce", "Follow"] do - users = get_notified_from_activity(activity) - notifications = Enum.map(users, fn user -> create_notification(activity, user) end) + notifications = + activity + |> get_notified_from_activity() + |> Enum.map(&create_notification(activity, &1)) + {:ok, notifications} end @@ -188,12 +188,10 @@ defmodule Pleroma.SubscriptionNotification do local_only ) when type in ["Create", "Like", "Announce", "Follow"] do - recipients = - [] - |> Utils.maybe_notify_subscribers(activity) - |> Enum.uniq() - - User.get_users_from_set(recipients, local_only) + [] + |> Utils.maybe_notify_subscribers(activity) + |> Enum.uniq() + |> User.get_users_from_set(local_only) end def get_notified_from_activity(_, _local_only), do: [] @@ -218,12 +216,12 @@ defmodule Pleroma.SubscriptionNotification do def skip?( :followers, - activity, + %{data: %{"actor" => actor}}, %{info: %{notification_settings: %{"followers" => false}}} = user ) do - actor = activity.data["actor"] - follower = User.get_cached_by_ap_id(actor) - User.following?(follower, user) + actor + |> User.get_cached_by_ap_id() + |> User.following?(user) end def skip?( @@ -252,14 +250,10 @@ defmodule Pleroma.SubscriptionNotification do !User.following?(user, followed) end - def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do - actor = activity.data["actor"] - - SubscriptionNotification.for_user(user) - |> Enum.any?(fn - %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true - _ -> false - end) + def skip?(:recently_followed, %{data: %{"type" => "Follow", "actor" => actor}}, user) do + user + |> SubscriptionNotification.for_user() + |> Enum.any?(&match?(%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}}, &1)) end def skip?(_, _, _), do: false diff --git a/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex b/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex index c6f0b5064..83d2b647f 100644 --- a/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionNotificationView do use Pleroma.Web, :view alias Pleroma.Activity - # alias Pleroma.SubscriptionNotification alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView -- cgit v1.2.3 From 6042e21b25885f9c3214d3296d9d2fdf35ad58ea Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Mon, 16 Sep 2019 21:59:49 +0300 Subject: Move subscription notifications to a separate controller --- .../controllers/mastodon_api_controller.ex | 48 ----------------- lib/pleroma/web/mastodon_api/mastodon_api.ex | 10 ---- .../views/subscription_notification_view.ex | 60 ---------------------- lib/pleroma/web/pleroma_api/pleroma_api.ex | 40 +++++++++++++++ .../subscription_notification_controller.ex | 59 +++++++++++++++++++++ .../views/subscription_notification_view.ex | 60 ++++++++++++++++++++++ lib/pleroma/web/router.ex | 33 +++--------- 7 files changed, 167 insertions(+), 143 deletions(-) delete mode 100644 lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex create mode 100644 lib/pleroma/web/pleroma_api/pleroma_api.ex create mode 100644 lib/pleroma/web/pleroma_api/subscription_notification_controller.ex create mode 100644 lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index eefdb8c06..060137b80 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -23,7 +23,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Repo alias Pleroma.ScheduledActivity alias Pleroma.Stats - alias Pleroma.SubscriptionNotification alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub @@ -40,7 +39,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.MastodonAPI.ReportView alias Pleroma.Web.MastodonAPI.ScheduledActivityView alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.MastodonAPI.SubscriptionNotificationView alias Pleroma.Web.MediaProxy alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization @@ -727,28 +725,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> render("index.json", %{notifications: notifications, for: user}) end - def subscription_notifications(%{assigns: %{user: user}} = conn, params) do - notifications = MastodonAPI.get_subscription_notifications(user, params) - - conn - |> add_link_headers(:subscription_notifications, notifications) - |> put_view(SubscriptionNotificationView) - |> render("index.json", %{notifications: notifications, for: user}) - end - - def get_subscription_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do - with {:ok, notification} <- SubscriptionNotification.get(user, id) do - conn - |> put_view(SubscriptionNotificationView) - |> render("show.json", %{subscription_notification: notification, for: user}) - else - {:error, reason} -> - conn - |> put_status(:forbidden) - |> json(%{"error" => reason}) - end - end - def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do with {:ok, notification} <- Notification.get(user, id) do conn @@ -767,11 +743,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, %{}) end - def clear_subscription_notifications(%{assigns: %{user: user}} = conn, _params) do - SubscriptionNotification.clear(user) - json(conn, %{}) - end - def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do with {:ok, _notif} <- Notification.dismiss(user, id) do json(conn, %{}) @@ -783,30 +754,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def dismiss_subscription_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do - with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do - json(conn, %{}) - else - {:error, reason} -> - conn - |> put_status(:forbidden) - |> json(%{"error" => reason}) - end - end - def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do Notification.destroy_multiple(user, ids) json(conn, %{}) end - def destroy_multiple_subscription_notifications( - %{assigns: %{user: user}} = conn, - %{"ids" => ids} = _params - ) do - SubscriptionNotification.destroy_multiple(user, ids) - json(conn, %{}) - end - def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do id = List.wrap(id) q = from(u in User, where: u.id in ^id) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 6751e24d8..ac01d1ff3 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do alias Pleroma.Notification alias Pleroma.Pagination alias Pleroma.ScheduledActivity - alias Pleroma.SubscriptionNotification alias Pleroma.User alias Pleroma.Web.CommonAPI @@ -63,15 +62,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do |> Pagination.fetch_paginated(params) end - def get_subscription_notifications(user, params \\ %{}) do - options = cast_params(params) - - user - |> SubscriptionNotification.for_user_query(options) - |> restrict(:exclude_types, options) - |> Pagination.fetch_paginated(params) - end - def get_scheduled_activities(user, params \\ %{}) do user |> ScheduledActivity.for_user_query() diff --git a/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex b/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex deleted file mode 100644 index 83d2b647f..000000000 --- a/lib/pleroma/web/mastodon_api/views/subscription_notification_view.ex +++ /dev/null @@ -1,60 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.MastodonAPI.SubscriptionNotificationView do - use Pleroma.Web, :view - - alias Pleroma.Activity - alias Pleroma.User - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MastodonAPI.AccountView - alias Pleroma.Web.MastodonAPI.SubscriptionNotificationView - alias Pleroma.Web.MastodonAPI.StatusView - - def render("index.json", %{notifications: notifications, for: user}) do - safe_render_many(notifications, SubscriptionNotificationView, "show.json", %{for: user}) - end - - def render("show.json", %{ - subscription_notification: %{activity: activity} = notification, - for: user - }) do - actor = User.get_cached_by_ap_id(activity.data["actor"]) - parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) - mastodon_type = Activity.mastodon_notification_type(activity) - - response = %{ - id: to_string(notification.id), - type: mastodon_type, - created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at), - account: AccountView.render("account.json", %{user: actor, for: user}) - } - - case mastodon_type do - "mention" -> - response - |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: activity, for: user}) - }) - - "favourite" -> - response - |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) - }) - - "reblog" -> - response - |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) - }) - - "follow" -> - response - - _ -> - nil - end - end -end diff --git a/lib/pleroma/web/pleroma_api/pleroma_api.ex b/lib/pleroma/web/pleroma_api/pleroma_api.ex new file mode 100644 index 000000000..480964845 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/pleroma_api.ex @@ -0,0 +1,40 @@ +defmodule Pleroma.Web.PleromaAPI.PleromaAPI do + import Ecto.Query + import Ecto.Changeset + + alias Pleroma.Activity + alias Pleroma.Pagination + alias Pleroma.SubscriptionNotification + + def get_subscription_notifications(user, params \\ %{}) do + options = cast_params(params) + + user + |> SubscriptionNotification.for_user_query(options) + |> restrict(:exclude_types, options) + |> Pagination.fetch_paginated(params) + end + + defp cast_params(params) do + param_types = %{ + exclude_types: {:array, :string}, + reblogs: :boolean, + with_muted: :boolean + } + + changeset = cast({%{}, param_types}, params, Map.keys(param_types)) + changeset.changes + end + + defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do + ap_types = + mastodon_types + |> Enum.map(&Activity.from_mastodon_notification_type/1) + |> Enum.filter(& &1) + + query + |> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data)) + end + + defp restrict(query, _, _), do: query +end diff --git a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex new file mode 100644 index 000000000..bfc2631dd --- /dev/null +++ b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.SubscriptionNotification + alias Pleroma.Web.PleromaAPI.PleromaAPI + alias Pleroma.Web.PleromaAPI.SubscriptionNotificationView + + def list(%{assigns: %{user: user}} = conn, params) do + notifications = PleromaAPI.get_subscription_notifications(user, params) + + conn + |> add_link_headers(notifications) + |> put_view(SubscriptionNotificationView) + |> render("index.json", %{notifications: notifications, for: user}) + end + + def get(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + with {:ok, notification} <- SubscriptionNotification.get(user, id) do + conn + |> put_view(SubscriptionNotificationView) + |> render("show.json", %{subscription_notification: notification, for: user}) + else + {:error, reason} -> + conn + |> put_status(:forbidden) + |> json(%{"error" => reason}) + end + end + + def clear(%{assigns: %{user: user}} = conn, _params) do + SubscriptionNotification.clear(user) + json(conn, %{}) + end + + def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do + json(conn, %{}) + else + {:error, reason} -> + conn + |> put_status(:forbidden) + |> json(%{"error" => reason}) + end + end + + def destroy_multiple( + %{assigns: %{user: user}} = conn, + %{"ids" => ids} = _params + ) do + SubscriptionNotification.destroy_multiple(user, ids) + json(conn, %{}) + end +end diff --git a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex new file mode 100644 index 000000000..d7f7f4c5a --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do + use Pleroma.Web, :view + + alias Pleroma.Activity + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.PleromaAPI.SubscriptionNotificationView + + def render("index.json", %{notifications: notifications, for: user}) do + safe_render_many(notifications, SubscriptionNotificationView, "show.json", %{for: user}) + end + + def render("show.json", %{ + subscription_notification: %{activity: activity} = notification, + for: user + }) do + actor = User.get_cached_by_ap_id(activity.data["actor"]) + parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) + mastodon_type = Activity.mastodon_notification_type(activity) + + response = %{ + id: to_string(notification.id), + type: mastodon_type, + created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at), + account: AccountView.render("account.json", %{user: actor, for: user}) + } + + case mastodon_type do + "mention" -> + response + |> Map.merge(%{ + status: StatusView.render("status.json", %{activity: activity, for: user}) + }) + + "favourite" -> + response + |> Map.merge(%{ + status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + }) + + "reblog" -> + response + |> Map.merge(%{ + status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + }) + + "follow" -> + response + + _ -> + nil + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 409fc9eca..05891b6c0 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -268,6 +268,14 @@ defmodule Pleroma.Web.Router do pipe_through(:oauth_read) get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses) get("/conversations/:id", PleromaAPIController, :conversation) + + scope "/subscription_notifications" do + post("/clear", SubscriptionNotificationController, :clear) + post("/dismiss", SubscriptionNotificationController, :dismiss) + delete("/destroy_multiple", SubscriptionNotificationController, :destroy_multiple) + get("/", SubscriptionNotificationController, :list) + get("/id", SubscriptionNotificationController, :get) + end end scope [] do @@ -302,38 +310,13 @@ defmodule Pleroma.Web.Router do post("/notifications/clear", MastodonAPIController, :clear_notifications) - post( - "/notifications/subscription/clear", - MastodonAPIController, - :clear_subscription_notifications - ) - post("/notifications/dismiss", MastodonAPIController, :dismiss_notification) - post( - "/notifications/subscription/dismiss", - MastodonAPIController, - :dismiss_subscription_notification - ) - get("/notifications", MastodonAPIController, :notifications) - get("/notifications/subscription", MastodonAPIController, :subscription_notifications) get("/notifications/:id", MastodonAPIController, :get_notification) - get( - "/notifications/subscription/:id", - MastodonAPIController, - :get_subscription_notification - ) - delete("/notifications/destroy_multiple", MastodonAPIController, :destroy_multiple) - delete( - "/notifications/subscription/destroy_multiple", - MastodonAPIController, - :destroy_multiple_subscription_notifications - ) - get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses) get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status) -- cgit v1.2.3 From a21584556f2c3edb90db3c58ba2a4829a7e220c1 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 17 Sep 2019 13:04:43 +0300 Subject: Update oban to 0.8.1 This version uses a different locking mechanism, which gets rid of `WARNING: you don't own a lock of type ShareLock` log spam --- lib/pleroma/flake_id.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/flake_id.ex b/lib/pleroma/flake_id.ex index 47d61ca5f..042cf8659 100644 --- a/lib/pleroma/flake_id.ex +++ b/lib/pleroma/flake_id.ex @@ -14,7 +14,7 @@ defmodule Pleroma.FlakeId do @type t :: binary - @behaviour Ecto.Type + use Ecto.Type use GenServer require Logger alias __MODULE__ -- cgit v1.2.3 From 450bf7a63c39c2301d5985448a867e77f1dfe3b3 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Fri, 13 Sep 2019 17:37:30 +0300 Subject: Mastodon API: Add a setting to hide follow/follower count from the user view (`hide_follows_count` and `hide_followers_count`) --- lib/pleroma/user/info.ex | 14 ++++++++-- lib/pleroma/web/activity_pub/views/user_view.ex | 32 +++++++++++++--------- .../controllers/mastodon_api_controller.ex | 2 ++ lib/pleroma/web/mastodon_api/views/account_view.ex | 14 ++++++++-- 4 files changed, 45 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 151e025de..b150a57cd 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -41,6 +41,8 @@ defmodule Pleroma.User.Info do field(:topic, :string, default: nil) field(:hub, :string, default: nil) field(:salmon, :string, default: nil) + field(:hide_followers_count, :boolean, default: false) + field(:hide_follows_count, :boolean, default: false) field(:hide_followers, :boolean, default: false) field(:hide_follows, :boolean, default: false) field(:hide_favorites, :boolean, default: true) @@ -262,6 +264,8 @@ defmodule Pleroma.User.Info do :salmon, :hide_followers, :hide_follows, + :hide_followers_count, + :hide_follows_count, :follower_count, :fields, :following_count @@ -281,7 +285,9 @@ defmodule Pleroma.User.Info do :following_count, :hide_follows, :fields, - :hide_followers + :hide_followers, + :hide_followers_count, + :hide_follows_count ]) |> validate_fields(remote?) end @@ -295,6 +301,8 @@ defmodule Pleroma.User.Info do :banner, :hide_follows, :hide_followers, + :hide_followers_count, + :hide_follows_count, :hide_favorites, :background, :show_role, @@ -458,7 +466,9 @@ defmodule Pleroma.User.Info do :hide_followers, :hide_follows, :follower_count, - :following_count + :following_count, + :hide_followers_count, + :hide_follows_count ]) end end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 7be734b26..164b973d0 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -118,30 +118,34 @@ defmodule Pleroma.Web.ActivityPub.UserView do end def render("following.json", %{user: user, page: page} = opts) do - showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows + showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_follows + showing_count = showing_items || !user.info.hide_follows_count + query = User.get_friends_query(user) query = from(user in query, select: [:ap_id]) following = Repo.all(query) total = - if showing do + if showing_count do length(following) else 0 end - collection(following, "#{user.ap_id}/following", page, showing, total) + collection(following, "#{user.ap_id}/following", page, showing_items, total) |> Map.merge(Utils.make_json_ld_header()) end def render("following.json", %{user: user} = opts) do - showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows + showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_follows + showing_count = showing_items || !user.info.hide_follows_count + query = User.get_friends_query(user) query = from(user in query, select: [:ap_id]) following = Repo.all(query) total = - if showing do + if showing_count do length(following) else 0 @@ -152,7 +156,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do "type" => "OrderedCollection", "totalItems" => total, "first" => - if showing do + if showing_items do collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows) else "#{user.ap_id}/following?page=1" @@ -162,32 +166,34 @@ defmodule Pleroma.Web.ActivityPub.UserView do end def render("followers.json", %{user: user, page: page} = opts) do - showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers + showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_followers + showing_count = showing_items || !user.info.hide_followers_count query = User.get_followers_query(user) query = from(user in query, select: [:ap_id]) followers = Repo.all(query) total = - if showing do + if showing_count do length(followers) else 0 end - collection(followers, "#{user.ap_id}/followers", page, showing, total) + collection(followers, "#{user.ap_id}/followers", page, showing_items, total) |> Map.merge(Utils.make_json_ld_header()) end def render("followers.json", %{user: user} = opts) do - showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers + showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_followers + showing_count = showing_items || !user.info.hide_followers_count query = User.get_followers_query(user) query = from(user in query, select: [:ap_id]) followers = Repo.all(query) total = - if showing do + if showing_count do length(followers) else 0 @@ -198,8 +204,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do "type" => "OrderedCollection", "totalItems" => total, "first" => - if showing do - collection(followers, "#{user.ap_id}/followers", 1, showing, total) + if showing_items do + collection(followers, "#{user.ap_id}/followers", 1, showing_items, total) else "#{user.ap_id}/followers?page=1" end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 060137b80..1beb4bcf2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -147,6 +147,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do [ :no_rich_text, :locked, + :hide_followers_count, + :hide_follows_count, :hide_followers, :hide_follows, :hide_favorites, diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 169116d0d..195dd124b 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -74,10 +74,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do user_info = User.get_cached_user_info(user) following_count = - ((!user.info.hide_follows or opts[:for] == user) && user_info.following_count) || 0 + if !user.info.hide_follows_count or !user.info.hide_follows or opts[:for] == user do + user_info.following_count + else + 0 + end followers_count = - ((!user.info.hide_followers or opts[:for] == user) && user_info.follower_count) || 0 + if !user.info.hide_followers_count or !user.info.hide_followers or opts[:for] == user do + user_info.follower_count + else + 0 + end bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"] @@ -138,6 +146,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do pleroma: %{ confirmation_pending: user_info.confirmation_pending, tags: user.tags, + hide_followers_count: user.info.hide_followers_count, + hide_follows_count: user.info.hide_follows_count, hide_followers: user.info.hide_followers, hide_follows: user.info.hide_follows, hide_favorites: user.info.hide_favorites, -- cgit v1.2.3 From 2688b876abf5ebd48d18e460eee7db992f984f5a Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Tue, 17 Sep 2019 13:42:28 +0000 Subject: Apply suggestion to lib/pleroma/web/pleroma_api/subscription_notification_controller.ex --- lib/pleroma/web/pleroma_api/subscription_notification_controller.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex index bfc2631dd..d5da92946 100644 --- a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex +++ b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do alias Pleroma.SubscriptionNotification alias Pleroma.Web.PleromaAPI.PleromaAPI - alias Pleroma.Web.PleromaAPI.SubscriptionNotificationView def list(%{assigns: %{user: user}} = conn, params) do notifications = PleromaAPI.get_subscription_notifications(user, params) -- cgit v1.2.3 From c0f776faecfa91ed755760975da12b546ca89317 Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Tue, 17 Sep 2019 13:42:36 +0000 Subject: Apply suggestion to lib/pleroma/web/pleroma_api/subscription_notification_controller.ex --- lib/pleroma/web/pleroma_api/subscription_notification_controller.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex index d5da92946..fff307b4e 100644 --- a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex +++ b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex @@ -15,7 +15,6 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do conn |> add_link_headers(notifications) - |> put_view(SubscriptionNotificationView) |> render("index.json", %{notifications: notifications, for: user}) end -- cgit v1.2.3 From f9be517c7f3e63cfaaca871a4458cbf7c8a6a3f4 Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Tue, 17 Sep 2019 13:42:40 +0000 Subject: Apply suggestion to lib/pleroma/web/pleroma_api/subscription_notification_controller.ex --- lib/pleroma/web/pleroma_api/subscription_notification_controller.ex | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex index fff307b4e..969ce0179 100644 --- a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex +++ b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex @@ -20,9 +20,7 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do def get(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do with {:ok, notification} <- SubscriptionNotification.get(user, id) do - conn - |> put_view(SubscriptionNotificationView) - |> render("show.json", %{subscription_notification: notification, for: user}) + render(conn, "show.json", %{subscription_notification: notification, for: user}) else {:error, reason} -> conn -- cgit v1.2.3 From a81f80233d63d98a0de7b57def76275182d5477e Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Tue, 17 Sep 2019 13:43:10 +0000 Subject: Apply suggestion to lib/pleroma/web/router.ex --- lib/pleroma/web/router.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 05891b6c0..1fff94b38 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -274,7 +274,7 @@ defmodule Pleroma.Web.Router do post("/dismiss", SubscriptionNotificationController, :dismiss) delete("/destroy_multiple", SubscriptionNotificationController, :destroy_multiple) get("/", SubscriptionNotificationController, :list) - get("/id", SubscriptionNotificationController, :get) + get("/:id", SubscriptionNotificationController, :get) end end -- cgit v1.2.3 From a76168e743c3dd193db6ebca029f287da9edd290 Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Tue, 17 Sep 2019 16:44:41 +0300 Subject: Cleanup PleromaAPIController --- .../web/pleroma_api/pleroma_api_controller.ex | 26 ---------------------- 1 file changed, 26 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index 246b351dc..d17ccf84d 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do alias Pleroma.Conversation.Participation alias Pleroma.Notification - alias Pleroma.SubscriptionNotification alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.NotificationView @@ -87,29 +86,4 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do |> render("index.json", %{notifications: notifications, for: user}) end end - - def delete_subscription_notification(%{assigns: %{user: user}} = conn, %{ - "id" => notification_id - }) do - with {:ok, notification} <- SubscriptionNotification.dismiss(user, notification_id) do - conn - |> put_view(NotificationView) - |> render("show.json", %{notification: notification, for: user}) - else - {:error, message} -> - conn - |> put_status(:bad_request) - |> json(%{"error" => message}) - end - end - - def read_subscription_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do - with notifications <- SubscriptionNotification.clear_up_to(user, max_id) do - notifications = Enum.take(notifications, 80) - - conn - |> put_view(NotificationView) - |> render("index.json", %{notifications: notifications, for: user}) - end - end end -- cgit v1.2.3 From 7d1773bc6b01caad8666ef07a9b2f2ac326fd0cd Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Tue, 17 Sep 2019 16:48:24 +0300 Subject: Rename SubscriptionNotificationController list and get actions to index and show --- lib/pleroma/web/pleroma_api/subscription_notification_controller.ex | 4 ++-- lib/pleroma/web/router.ex | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex index 969ce0179..fa8307668 100644 --- a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex +++ b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do alias Pleroma.SubscriptionNotification alias Pleroma.Web.PleromaAPI.PleromaAPI - def list(%{assigns: %{user: user}} = conn, params) do + def index(%{assigns: %{user: user}} = conn, params) do notifications = PleromaAPI.get_subscription_notifications(user, params) conn @@ -18,7 +18,7 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do |> render("index.json", %{notifications: notifications, for: user}) end - def get(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + def show(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do with {:ok, notification} <- SubscriptionNotification.get(user, id) do render(conn, "show.json", %{subscription_notification: notification, for: user}) else diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 1fff94b38..502c67e74 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -273,8 +273,8 @@ defmodule Pleroma.Web.Router do post("/clear", SubscriptionNotificationController, :clear) post("/dismiss", SubscriptionNotificationController, :dismiss) delete("/destroy_multiple", SubscriptionNotificationController, :destroy_multiple) - get("/", SubscriptionNotificationController, :list) - get("/:id", SubscriptionNotificationController, :get) + get("/", SubscriptionNotificationController, :index) + get("/:id", SubscriptionNotificationController, :show) end end -- cgit v1.2.3 From e9f69a3eb7f17ae8c2890972851de1139983ce3d Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Tue, 17 Sep 2019 16:52:23 +0300 Subject: Move pleroma_api controllers into controllers sub-folders --- .../controllers/pleroma_api_controller.ex | 89 ++++++++++++++++++++++ .../subscription_notification_controller.ex | 55 +++++++++++++ .../web/pleroma_api/pleroma_api_controller.ex | 89 ---------------------- .../subscription_notification_controller.ex | 55 ------------- 4 files changed, 144 insertions(+), 144 deletions(-) create mode 100644 lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex create mode 100644 lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex delete mode 100644 lib/pleroma/web/pleroma_api/pleroma_api_controller.ex delete mode 100644 lib/pleroma/web/pleroma_api/subscription_notification_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex new file mode 100644 index 000000000..d17ccf84d --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -0,0 +1,89 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.Conversation.Participation + alias Pleroma.Notification + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.MastodonAPI.ConversationView + alias Pleroma.Web.MastodonAPI.NotificationView + alias Pleroma.Web.MastodonAPI.StatusView + + def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do + with %Participation{} = participation <- Participation.get(participation_id), + true <- user.id == participation.user_id do + conn + |> put_view(ConversationView) + |> render("participation.json", %{participation: participation, for: user}) + end + end + + def conversation_statuses( + %{assigns: %{user: user}} = conn, + %{"id" => participation_id} = params + ) do + participation = Participation.get(participation_id, preload: [:conversation]) + + if user.id == participation.user_id do + params = + params + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + + activities = + participation.conversation.ap_id + |> ActivityPub.fetch_activities_for_context(params) + |> Enum.reverse() + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) + end + end + + def update_conversation( + %{assigns: %{user: user}} = conn, + %{"id" => participation_id, "recipients" => recipients} + ) do + participation = + participation_id + |> Participation.get() + + with true <- user.id == participation.user_id, + {:ok, participation} <- Participation.set_recipients(participation, recipients) do + conn + |> put_view(ConversationView) + |> render("participation.json", %{participation: participation, for: user}) + end + end + + def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do + with {:ok, notification} <- Notification.read_one(user, notification_id) do + conn + |> put_view(NotificationView) + |> render("show.json", %{notification: notification, for: user}) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + end + end + + def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do + with notifications <- Notification.set_read_up_to(user, max_id) do + notifications = Enum.take(notifications, 80) + + conn + |> put_view(NotificationView) + |> render("index.json", %{notifications: notifications, for: user}) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex new file mode 100644 index 000000000..fa8307668 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex @@ -0,0 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.SubscriptionNotification + alias Pleroma.Web.PleromaAPI.PleromaAPI + + def index(%{assigns: %{user: user}} = conn, params) do + notifications = PleromaAPI.get_subscription_notifications(user, params) + + conn + |> add_link_headers(notifications) + |> render("index.json", %{notifications: notifications, for: user}) + end + + def show(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + with {:ok, notification} <- SubscriptionNotification.get(user, id) do + render(conn, "show.json", %{subscription_notification: notification, for: user}) + else + {:error, reason} -> + conn + |> put_status(:forbidden) + |> json(%{"error" => reason}) + end + end + + def clear(%{assigns: %{user: user}} = conn, _params) do + SubscriptionNotification.clear(user) + json(conn, %{}) + end + + def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do + json(conn, %{}) + else + {:error, reason} -> + conn + |> put_status(:forbidden) + |> json(%{"error" => reason}) + end + end + + def destroy_multiple( + %{assigns: %{user: user}} = conn, + %{"ids" => ids} = _params + ) do + SubscriptionNotification.destroy_multiple(user, ids) + json(conn, %{}) + end +end diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex deleted file mode 100644 index d17ccf84d..000000000 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ /dev/null @@ -1,89 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do - use Pleroma.Web, :controller - - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] - - alias Pleroma.Conversation.Participation - alias Pleroma.Notification - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.MastodonAPI.ConversationView - alias Pleroma.Web.MastodonAPI.NotificationView - alias Pleroma.Web.MastodonAPI.StatusView - - def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do - with %Participation{} = participation <- Participation.get(participation_id), - true <- user.id == participation.user_id do - conn - |> put_view(ConversationView) - |> render("participation.json", %{participation: participation, for: user}) - end - end - - def conversation_statuses( - %{assigns: %{user: user}} = conn, - %{"id" => participation_id} = params - ) do - participation = Participation.get(participation_id, preload: [:conversation]) - - if user.id == participation.user_id do - params = - params - |> Map.put("blocking_user", user) - |> Map.put("muting_user", user) - |> Map.put("user", user) - - activities = - participation.conversation.ap_id - |> ActivityPub.fetch_activities_for_context(params) - |> Enum.reverse() - - conn - |> add_link_headers(activities) - |> put_view(StatusView) - |> render("index.json", %{activities: activities, for: user, as: :activity}) - end - end - - def update_conversation( - %{assigns: %{user: user}} = conn, - %{"id" => participation_id, "recipients" => recipients} - ) do - participation = - participation_id - |> Participation.get() - - with true <- user.id == participation.user_id, - {:ok, participation} <- Participation.set_recipients(participation, recipients) do - conn - |> put_view(ConversationView) - |> render("participation.json", %{participation: participation, for: user}) - end - end - - def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do - with {:ok, notification} <- Notification.read_one(user, notification_id) do - conn - |> put_view(NotificationView) - |> render("show.json", %{notification: notification, for: user}) - else - {:error, message} -> - conn - |> put_status(:bad_request) - |> json(%{"error" => message}) - end - end - - def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do - with notifications <- Notification.set_read_up_to(user, max_id) do - notifications = Enum.take(notifications, 80) - - conn - |> put_view(NotificationView) - |> render("index.json", %{notifications: notifications, for: user}) - end - end -end diff --git a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex deleted file mode 100644 index fa8307668..000000000 --- a/lib/pleroma/web/pleroma_api/subscription_notification_controller.ex +++ /dev/null @@ -1,55 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do - use Pleroma.Web, :controller - - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] - - alias Pleroma.SubscriptionNotification - alias Pleroma.Web.PleromaAPI.PleromaAPI - - def index(%{assigns: %{user: user}} = conn, params) do - notifications = PleromaAPI.get_subscription_notifications(user, params) - - conn - |> add_link_headers(notifications) - |> render("index.json", %{notifications: notifications, for: user}) - end - - def show(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do - with {:ok, notification} <- SubscriptionNotification.get(user, id) do - render(conn, "show.json", %{subscription_notification: notification, for: user}) - else - {:error, reason} -> - conn - |> put_status(:forbidden) - |> json(%{"error" => reason}) - end - end - - def clear(%{assigns: %{user: user}} = conn, _params) do - SubscriptionNotification.clear(user) - json(conn, %{}) - end - - def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do - with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do - json(conn, %{}) - else - {:error, reason} -> - conn - |> put_status(:forbidden) - |> json(%{"error" => reason}) - end - end - - def destroy_multiple( - %{assigns: %{user: user}} = conn, - %{"ids" => ids} = _params - ) do - SubscriptionNotification.destroy_multiple(user, ids) - json(conn, %{}) - end -end -- cgit v1.2.3 From 9fa2586abd915342095574f05358642412db0f04 Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Tue, 17 Sep 2019 17:44:10 +0300 Subject: Refactor SubscriptionNotificationView --- .../subscription_notification_controller.ex | 20 ++++++++++++++++++-- .../views/subscription_notification_view.ex | 9 +++++---- 2 files changed, 23 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex index fa8307668..37c2222de 100644 --- a/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex @@ -7,11 +7,16 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + alias Pleroma.Activity alias Pleroma.SubscriptionNotification + alias Pleroma.User alias Pleroma.Web.PleromaAPI.PleromaAPI def index(%{assigns: %{user: user}} = conn, params) do - notifications = PleromaAPI.get_subscription_notifications(user, params) + notifications = + user + |> PleromaAPI.get_subscription_notifications(params) + |> Enum.map(&build_notification_data/1) conn |> add_link_headers(notifications) @@ -20,7 +25,10 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do def show(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do with {:ok, notification} <- SubscriptionNotification.get(user, id) do - render(conn, "show.json", %{subscription_notification: notification, for: user}) + render(conn, "show.json", %{ + subscription_notification: build_notification_data(notification), + for: user + }) else {:error, reason} -> conn @@ -52,4 +60,12 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do SubscriptionNotification.destroy_multiple(user, ids) json(conn, %{}) end + + defp build_notification_data(%{activity: %{data: data}} = notification) do + %{ + notification: notification, + actor: User.get_cached_by_ap_id(data["actor"]), + parent_activity: Activity.get_create_by_object_ap_id(data["object"]) + } + end end diff --git a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex index d7f7f4c5a..0eccbcbb9 100644 --- a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex +++ b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do use Pleroma.Web, :view alias Pleroma.Activity - alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.StatusView @@ -17,11 +16,13 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do end def render("show.json", %{ - subscription_notification: %{activity: activity} = notification, + subscription_notification: %{ + notification: %{activity: activity} = notification, + actor: actor, + parent_activity: parent_activity + }, for: user }) do - actor = User.get_cached_by_ap_id(activity.data["actor"]) - parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) mastodon_type = Activity.mastodon_notification_type(activity) response = %{ -- cgit v1.2.3 From 80c5c3495bdd7939e576c8746a959f3f89f44042 Mon Sep 17 00:00:00 2001 From: Steven Fuchs Date: Tue, 17 Sep 2019 14:44:52 +0000 Subject: remove remaining errors from tests --- lib/pleroma/application.ex | 53 +++++++++++++++++++++++++++------------ lib/pleroma/web/streamer/state.ex | 18 ++++++++++--- 2 files changed, 51 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 3b37ce630..dabce771d 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -43,23 +43,9 @@ defmodule Pleroma.Application do hackney_pool_children() ++ [ Pleroma.Stats, - {Oban, Pleroma.Config.get(Oban)}, - %{ - id: :web_push_init, - start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, - restart: :temporary - }, - %{ - id: :federator_init, - start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]}, - restart: :temporary - }, - %{ - id: :internal_fetch_init, - start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]}, - restart: :temporary - } + {Oban, Pleroma.Config.get(Oban)} ] ++ + task_children(@env) ++ oauth_cleanup_child(oauth_cleanup_enabled?()) ++ streamer_child(@env) ++ chat_child(@env, chat_enabled?()) ++ @@ -163,4 +149,39 @@ defmodule Pleroma.Application do :hackney_pool.child_spec(pool, options) end end + + defp task_children(:test) do + [ + %{ + id: :web_push_init, + start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, + restart: :temporary + }, + %{ + id: :federator_init, + start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]}, + restart: :temporary + } + ] + end + + defp task_children(_) do + [ + %{ + id: :web_push_init, + start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, + restart: :temporary + }, + %{ + id: :federator_init, + start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]}, + restart: :temporary + }, + %{ + id: :internal_fetch_init, + start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]}, + restart: :temporary + } + ] + end end diff --git a/lib/pleroma/web/streamer/state.ex b/lib/pleroma/web/streamer/state.ex index 7b5199068..c48752d95 100644 --- a/lib/pleroma/web/streamer/state.ex +++ b/lib/pleroma/web/streamer/state.ex @@ -4,16 +4,18 @@ defmodule Pleroma.Web.Streamer.State do alias Pleroma.Web.Streamer.StreamerSocket + @env Mix.env() + def start_link(_) do GenServer.start_link(__MODULE__, %{sockets: %{}}, name: __MODULE__) end def add_socket(topic, socket) do - GenServer.call(__MODULE__, {:add, socket, topic}) + GenServer.call(__MODULE__, {:add, topic, socket}) end def remove_socket(topic, socket) do - GenServer.call(__MODULE__, {:remove, socket, topic}) + do_remove_socket(@env, topic, socket) end def get_sockets do @@ -29,7 +31,7 @@ defmodule Pleroma.Web.Streamer.State do {:reply, state, state} end - def handle_call({:add, socket, topic}, _from, %{sockets: sockets} = state) do + def handle_call({:add, topic, socket}, _from, %{sockets: sockets} = state) do internal_topic = internal_topic(topic, socket) stream_socket = StreamerSocket.from_socket(socket) @@ -44,7 +46,7 @@ defmodule Pleroma.Web.Streamer.State do {:reply, state, state} end - def handle_call({:remove, socket, topic}, _from, %{sockets: sockets} = state) do + def handle_call({:remove, topic, socket}, _from, %{sockets: sockets} = state) do internal_topic = internal_topic(topic, socket) stream_socket = StreamerSocket.from_socket(socket) @@ -57,6 +59,14 @@ defmodule Pleroma.Web.Streamer.State do {:reply, state, state} end + defp do_remove_socket(:test, _, _) do + :ok + end + + defp do_remove_socket(_env, topic, socket) do + GenServer.call(__MODULE__, {:remove, topic, socket}) + end + defp internal_topic(topic, socket) when topic in ~w[user user:notification direct] do "#{topic}:#{socket.assigns[:user].id}" -- cgit v1.2.3 From 6193157f1998b10ac6cb9f4d36dd863eced81b37 Mon Sep 17 00:00:00 2001 From: Steven Fuchs Date: Tue, 17 Sep 2019 18:12:27 +0000 Subject: Fix notification warnings --- lib/pleroma/workers/web_pusher_worker.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index bea2baffb..61b451e3e 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -10,7 +10,11 @@ defmodule Pleroma.Workers.WebPusherWorker do @impl Oban.Worker def perform(%{"op" => "web_push", "notification_id" => notification_id}, _job) do - notification = Repo.get(Notification, notification_id) + notification = + Notification + |> Repo.get(notification_id) + |> Repo.preload([:activity]) + Pleroma.Web.Push.Impl.perform(notification) end end -- cgit v1.2.3 From 7f211a48e0c443cbff90f028c5c92c496f66c62e Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 17 Sep 2019 21:43:27 +0200 Subject: docs/markdown.ex: child header as "- key (type): description" --- lib/pleroma/docs/markdown.ex | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 8386dc2fb..58a42b323 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -44,6 +44,13 @@ defmodule Pleroma.Docs.Markdown do {:ok, config_path} end + defp print_child_header(file, child) do + IO.write( + file, + "- `#{inspect(child[:key])}` (`#{inspect(child[:type])}`): #{child[:description]}\n" + ) + end + defp print_suggestion(file, suggestion) when is_list(suggestion) do IO.write(file, " `#{inspect(suggestion)}`\n") end @@ -70,9 +77,4 @@ defmodule Pleroma.Docs.Markdown do print_suggestion(file, List.first(suggestions)) end end - - defp print_child_header(file, child) do - IO.write(file, "- `#{inspect(child[:key])}` -`#{inspect(child[:type])}` \n") - IO.write(file, "#{child[:description]} \n") - end end -- cgit v1.2.3 From f9dd121ad3f7e1de465f81c7a5fe4e4173d88e28 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 17 Sep 2019 23:09:08 +0300 Subject: Admin API: Return link alongside with token on password reset --- lib/pleroma/web/admin_api/admin_api_controller.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 544b9d7d8..03a73053b 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -432,9 +432,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def get_password_reset(conn, %{"nickname" => nickname}) do (%User{local: true} = user) = User.get_cached_by_nickname(nickname) {:ok, token} = Pleroma.PasswordResetToken.create_token(user) + host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) + protocol = Pleroma.Config.get([Pleroma.Web.Endpoint, :protocol]) conn - |> json(token.token) + |> json(%{ + token: token.token, + link: "#{protocol}://#{host}/api/pleroma/password_reset/#{token}" + }) end def list_reports(conn, params) do -- cgit v1.2.3 From e0d8c8897e46d20039b4c0a383bca0192c5eb2ec Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 17 Sep 2019 22:00:02 +0200 Subject: docs/markdown.ex: do no print empty suggestions --- lib/pleroma/docs/markdown.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 58a42b323..d7ca97957 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -66,6 +66,8 @@ defmodule Pleroma.Docs.Markdown do defp print_suggestions(_file, nil), do: nil + defp print_suggestions(_file, ""), do: nil + defp print_suggestions(file, suggestions) do IO.write(file, "Suggestions:\n") -- cgit v1.2.3 From 106afaed58da3a25d1c4593e13192ad2145643e4 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 17 Sep 2019 22:04:21 +0200 Subject: markdown.ex: do not fail if there is no children --- lib/pleroma/docs/markdown.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index d7ca97957..20bd1c896 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -23,7 +23,7 @@ defmodule Pleroma.Docs.Markdown do IO.write(file, "#{group[:description]}\n") - for child <- group[:children] do + for child <- group[:children] || [] do print_child_header(file, child) print_suggestions(file, child[:suggestions]) -- cgit v1.2.3 From c0c56282007aff88a923bba4769af894cb6235af Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 17 Sep 2019 22:14:56 +0200 Subject: description.exs: remove empty strings on descriptions --- lib/pleroma/docs/markdown.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 20bd1c896..739e4fce3 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -44,13 +44,17 @@ defmodule Pleroma.Docs.Markdown do {:ok, config_path} end - defp print_child_header(file, child) do + defp print_child_header(file, %{key: key, type: type, description: description} = _child) do IO.write( file, - "- `#{inspect(child[:key])}` (`#{inspect(child[:type])}`): #{child[:description]}\n" + "- `#{inspect(key)}` (`#{inspect(type)}`): #{description}\n" ) end + defp print_child_header(file, %{key: key, type: type} = _child) do + IO.write(file, "- `#{inspect(key)}` (`#{inspect(type)}`)\n") + end + defp print_suggestion(file, suggestion) when is_list(suggestion) do IO.write(file, " `#{inspect(suggestion)}`\n") end -- cgit v1.2.3 From d6182a3c8fef6377c20bb827a8e86bdac5bfb125 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 17 Sep 2019 22:22:54 +0200 Subject: markdown.ex: Make suggestion(s) plural only if on >1 --- lib/pleroma/docs/markdown.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 739e4fce3..fc6389064 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -73,13 +73,15 @@ defmodule Pleroma.Docs.Markdown do defp print_suggestions(_file, ""), do: nil defp print_suggestions(file, suggestions) do - IO.write(file, "Suggestions:\n") - if length(suggestions) > 1 do + IO.write(file, "Suggestions:\n") + for suggestion <- suggestions do print_suggestion(file, suggestion, true) end else + IO.write(file, "Suggestion:\n") + print_suggestion(file, List.first(suggestions)) end end -- cgit v1.2.3 From d2097fd0f5d5d6750de09243cb5720b161305790 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 17 Sep 2019 22:33:32 +0200 Subject: markdown.ex: \n\n on >1 suggestions, 2-spaces on one --- lib/pleroma/docs/markdown.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index fc6389064..280fe0309 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -74,13 +74,13 @@ defmodule Pleroma.Docs.Markdown do defp print_suggestions(file, suggestions) do if length(suggestions) > 1 do - IO.write(file, "Suggestions:\n") + IO.write(file, "\n\nSuggestions:\n") for suggestion <- suggestions do print_suggestion(file, suggestion, true) end else - IO.write(file, "Suggestion:\n") + IO.write(file, " Suggestion: ") print_suggestion(file, List.first(suggestions)) end -- cgit v1.2.3 From 4785596a2cf638570b35afc91babbb0ac8309981 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 17 Sep 2019 22:55:29 +0200 Subject: markdown.ex: end suggestions list with a newline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise we end up with suggestion on the same level as the childs Markdown is a fuck… --- lib/pleroma/docs/markdown.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 280fe0309..27be1b095 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -79,6 +79,8 @@ defmodule Pleroma.Docs.Markdown do for suggestion <- suggestions do print_suggestion(file, suggestion, true) end + + IO.write(file, "\n") else IO.write(file, " Suggestion: ") -- cgit v1.2.3 From e501c822c98edb675b71b25d165fdf8df8447c27 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 17 Sep 2019 23:02:24 +0200 Subject: markdown.ex: put two-spaces before the description-newline --- lib/pleroma/docs/markdown.ex | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 27be1b095..68b106499 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -47,12 +47,12 @@ defmodule Pleroma.Docs.Markdown do defp print_child_header(file, %{key: key, type: type, description: description} = _child) do IO.write( file, - "- `#{inspect(key)}` (`#{inspect(type)}`): #{description}\n" + "- `#{inspect(key)}` (`#{inspect(type)}`): #{description} \n" ) end defp print_child_header(file, %{key: key, type: type} = _child) do - IO.write(file, "- `#{inspect(key)}` (`#{inspect(type)}`)\n") + IO.write(file, "- `#{inspect(key)}` (`#{inspect(type)}`) \n") end defp print_suggestion(file, suggestion) when is_list(suggestion) do @@ -74,13 +74,11 @@ defmodule Pleroma.Docs.Markdown do defp print_suggestions(file, suggestions) do if length(suggestions) > 1 do - IO.write(file, "\n\nSuggestions:\n") + IO.write(file, "Suggestions:\n") for suggestion <- suggestions do print_suggestion(file, suggestion, true) end - - IO.write(file, "\n") else IO.write(file, " Suggestion: ") -- cgit v1.2.3 From 4faf2b1555f004664005e0efddb9815ebca4c5c7 Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 6 Sep 2019 17:14:31 +0300 Subject: post for creating invite tokens in admin api --- lib/pleroma/web/admin_api/admin_api_controller.ex | 18 ++++++++++++++---- lib/pleroma/web/router.ex | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 2a1cc59e5..41ded7343 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -402,11 +402,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do @doc "Get a account registeration invite token (base64 string)" def get_invite_token(conn, params) do - options = params["invite"] || %{} - {:ok, invite} = UserInviteToken.create_invite(options) + opts = %{} - conn - |> json(invite.token) + opts = + if params["max_use"], + do: Map.put(opts, :max_use, params["max_use"]), + else: opts + + opts = + if params["expires_at"], + do: Map.put(opts, :expires_at, params["expires_at"]), + else: opts + + {:ok, invite} = UserInviteToken.create_invite(opts) + + json(conn, AccountView.render("invite.json", %{invite: invite})) end @doc "Get list of created invites" diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 401133bf3..5779d27d2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -180,7 +180,7 @@ defmodule Pleroma.Web.Router do post("/relay", AdminAPIController, :relay_follow) delete("/relay", AdminAPIController, :relay_unfollow) - get("/users/invite_token", AdminAPIController, :get_invite_token) + post("/users/invite_token", AdminAPIController, :get_invite_token) get("/users/invites", AdminAPIController, :invites) post("/users/revoke_invite", AdminAPIController, :revoke_invite) post("/users/email_invite", AdminAPIController, :email_invite) -- cgit v1.2.3 From 2263c8b6b9260bee7dedeaff3d2ce955df12f08b Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 6 Sep 2019 17:20:44 +0300 Subject: little fixes --- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 41ded7343..d25c21e33 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -400,7 +400,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end - @doc "Get a account registeration invite token (base64 string)" + @doc "Get an account registration invite token" def get_invite_token(conn, params) do opts = %{} -- cgit v1.2.3 From a18f1e7cd7addf8aee9c56643f4f0531e1c5b5a0 Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 13 Sep 2019 08:07:29 +0300 Subject: namings --- lib/pleroma/web/admin_api/admin_api_controller.ex | 4 ++-- lib/pleroma/web/router.ex | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index d25c21e33..8a8091daa 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -400,8 +400,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end - @doc "Get an account registration invite token" - def get_invite_token(conn, params) do + @doc "Create an account registration invite token" + def create_invite_token(conn, params) do opts = %{} opts = diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5779d27d2..b9b85fd67 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -180,7 +180,7 @@ defmodule Pleroma.Web.Router do post("/relay", AdminAPIController, :relay_follow) delete("/relay", AdminAPIController, :relay_unfollow) - post("/users/invite_token", AdminAPIController, :get_invite_token) + post("/users/invite_token", AdminAPIController, :create_invite_token) get("/users/invites", AdminAPIController, :invites) post("/users/revoke_invite", AdminAPIController, :revoke_invite) post("/users/email_invite", AdminAPIController, :email_invite) -- cgit v1.2.3 From 384b7dd40dd484146d267ba4e12f750184365bfc Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Wed, 18 Sep 2019 18:06:49 +0300 Subject: Fix response --- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 4421b30c8..54ab6e032 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -438,7 +438,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do conn |> json(%{ token: token.token, - link: "#{protocol}://#{host}/api/pleroma/password_reset/#{token}" + link: "#{protocol}://#{host}/api/pleroma/password_reset/#{token.token}" }) end -- cgit v1.2.3 From 7ef575d11e46247d1f64dd09d992e532cb8c5c37 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 18 Sep 2019 18:13:21 +0300 Subject: Initial poll refresh support Implement refreshing the object with an interval and call the function when getting the poll. --- lib/pleroma/object.ex | 18 ++++++++++++++++++ lib/pleroma/object/fetcher.ex | 17 ++++++++++++++--- .../controllers/mastodon_api_controller.ex | 2 +- 3 files changed, 33 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 5033798ae..640e068e5 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -38,6 +38,24 @@ defmodule Pleroma.Object do def get_by_id(nil), do: nil def get_by_id(id), do: Repo.get(Object, id) + def get_by_id_and_maybe_refetch(id, opts \\ []) do + %{updated_at: updated_at} = object = get_by_id(id) + + if opts[:interval] && + NaiveDateTime.diff(updated_at, NaiveDateTime.utc_now()) > opts[:interval] do + case Fetcher.refetch_object(object) do + {:ok, %Object{} = object} -> + object + + e -> + Logger.error("Couldn't refresh #{object.data["id"]}:\n#{inspect(e)}") + object + end + else + object + end + end + def get_by_ap_id(nil), do: nil def get_by_ap_id(ap_id) do diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index c1795ae0f..da1ebd8b3 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -7,17 +7,19 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.Signature + alias Pleroma.Repo alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.OStatus require Logger - defp reinject_object(data) do + defp reinject_object(struct, data) do Logger.debug("Reinjecting object #{data["id"]}") with data <- Transmogrifier.fix_object(data), - {:ok, object} <- Object.create(data) do + changeset <- Object.change(struct, %{data: data}), + {:ok, object} <- Repo.insert_or_update(changeset) do {:ok, object} else e -> @@ -26,6 +28,15 @@ defmodule Pleroma.Object.Fetcher do end end + def refetch_object(%Object{data: %{"id" => id}} = object) do + with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), + {:ok, object} <- reinject_object(object, data) do + {:ok, object} + else + e -> {:error, e} + end + end + # TODO: # This will create a Create activity, which we need internally at the moment. def fetch_object_from_id(id, options \\ []) do @@ -57,7 +68,7 @@ defmodule Pleroma.Object.Fetcher do {:reject, nil} {:object, data, nil} -> - reinject_object(data) + reinject_object(%Object{}, data) {:normalize, object = %Object{}} -> {:ok, object} diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 060137b80..970cfd8db 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -485,7 +485,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Object{} = object <- Object.get_by_id(id), + with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), true <- Visibility.visible_for_user?(activity, user) do conn -- cgit v1.2.3 From a9c700ff1594bbd3c280dd6ac3a8dffa6ea7060b Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 18 Sep 2019 18:52:33 +0300 Subject: Fix wrong argument order when calling NaiveDateTime.diff --- lib/pleroma/object.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 640e068e5..3fa407931 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -42,7 +42,7 @@ defmodule Pleroma.Object do %{updated_at: updated_at} = object = get_by_id(id) if opts[:interval] && - NaiveDateTime.diff(updated_at, NaiveDateTime.utc_now()) > opts[:interval] do + NaiveDateTime.diff(NaiveDateTime.utc_now(), updated_at) > opts[:interval] do case Fetcher.refetch_object(object) do {:ok, %Object{} = object} -> object -- cgit v1.2.3 From e3f902b3a1330f942ddaf6ff7b108bba8fc3120a Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 18 Sep 2019 19:07:25 +0300 Subject: Set updated_at even if the object stayed the same --- lib/pleroma/object/fetcher.ex | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index da1ebd8b3..786e31cce 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -14,11 +14,20 @@ defmodule Pleroma.Object.Fetcher do require Logger + defp touch_changeset(changeset) do + updated_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.truncate(:second) + + Ecto.Changeset.put_change(changeset, :updated_at, updated_at) + end + defp reinject_object(struct, data) do Logger.debug("Reinjecting object #{data["id"]}") with data <- Transmogrifier.fix_object(data), changeset <- Object.change(struct, %{data: data}), + changeset <- touch_changeset(changeset), {:ok, object} <- Repo.insert_or_update(changeset) do {:ok, object} else -- cgit v1.2.3 From d32894ae512c1f4cff4d967b89a0772e105d456b Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 18 Sep 2019 19:24:20 +0300 Subject: Move object internal fields to a constant --- lib/pleroma/constants.ex | 12 ++++++++++++ lib/pleroma/web/activity_pub/transmogrifier.ex | 10 +--------- 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index ef1418543..0bf20cdd0 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -6,4 +6,16 @@ defmodule Pleroma.Constants do use Const const(as_public, do: "https://www.w3.org/ns/activitystreams#Public") + + const(object_internal_fields, + do: [ + "likes", + "like_count", + "announcements", + "announcement_count", + "emoji", + "context_id", + "deleted_activity_id" + ] + ) end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 8461b666e..9d2ddc1cd 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -979,15 +979,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do defp strip_internal_fields(object) do object - |> Map.drop([ - "likes", - "like_count", - "announcements", - "announcement_count", - "emoji", - "context_id", - "deleted_activity_id" - ]) + |> Map.drop(Pleroma.Constants.object_internal_fields()) end defp strip_internal_tags(%{"tag" => tags} = object) do -- cgit v1.2.3 From eb87a86b5b3999f3e7ee119e839da3bd6d2ed4cf Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 18 Sep 2019 19:53:51 +0300 Subject: Preserve internal fields when reinjecting --- lib/pleroma/object/fetcher.ex | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 786e31cce..fecc97c5e 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.Web.OStatus require Logger + require Pleroma.Constants defp touch_changeset(changeset) do updated_at = @@ -22,10 +23,19 @@ defmodule Pleroma.Object.Fetcher do Ecto.Changeset.put_change(changeset, :updated_at, updated_at) end + defp maybe_reinject_internal_fields(data, %{data: %{} = old_data}) do + internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields()) + + Map.merge(data, internal_fields) + end + + defp maybe_reinject_internal_fields(data, _), do: data + defp reinject_object(struct, data) do Logger.debug("Reinjecting object #{data["id"]}") with data <- Transmogrifier.fix_object(data), + data <- maybe_reinject_internal_fields(data, struct), changeset <- Object.change(struct, %{data: data}), changeset <- touch_changeset(changeset), {:ok, object} <- Repo.insert_or_update(changeset) do -- cgit v1.2.3 From c096dd86e5e4e3bdb9aa35c2c4f499efc17ddd16 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 18 Sep 2019 19:59:23 +0300 Subject: Do not refetch local objects --- lib/pleroma/object/fetcher.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index fecc97c5e..91e6b6dca 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -48,10 +48,12 @@ defmodule Pleroma.Object.Fetcher do end def refetch_object(%Object{data: %{"id" => id}} = object) do - with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), + with {:local, false} <- {:local, String.starts_with?(id, Pleroma.Web.base_url() <> "/")}, + {:ok, data} <- fetch_and_contain_remote_object_from_id(id), {:ok, object} <- reinject_object(object, data) do {:ok, object} else + {:local, true} -> object e -> {:error, e} end end -- cgit v1.2.3 From 5028b7b5780fbfd0904b2e48c05a05eeab0e623d Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 18 Sep 2019 22:09:03 +0300 Subject: Fix credo issues --- lib/pleroma/object/fetcher.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 91e6b6dca..cea33b5af 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -6,8 +6,8 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.HTTP alias Pleroma.Object alias Pleroma.Object.Containment - alias Pleroma.Signature alias Pleroma.Repo + alias Pleroma.Signature alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.OStatus -- cgit v1.2.3 From 6a42641b8d806f40f697303995fb12af39a93bd8 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 10 Aug 2019 21:46:36 +0300 Subject: Add pack.toml loading --- lib/pleroma/emoji.ex | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 66e20f0e4..ede734a53 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -143,23 +143,38 @@ defmodule Pleroma.Emoji do defp load_pack(pack_dir, emoji_groups) do pack_name = Path.basename(pack_dir) - emoji_txt = Path.join(pack_dir, "emoji.txt") + pack_toml = Path.join(pack_dir, "pack.toml") - if File.exists?(emoji_txt) do - load_from_file(emoji_txt, emoji_groups) + if File.exists?(pack_toml) do + toml = Toml.decode_file!(pack_toml) + + toml["files"] + |> Enum.map(fn {name, rel_file} -> + filename = Path.join("/emoji/#{pack_name}", rel_file) + {name, filename, pack_name} + end) else - extensions = Pleroma.Config.get([:emoji, :pack_extensions]) + # Load from emoji.txt / all files + emoji_txt = Path.join(pack_dir, "emoji.txt") - Logger.info( - "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji" - ) + if File.exists?(emoji_txt) do + load_from_file(emoji_txt, emoji_groups) + else + extensions = Pleroma.Config.get([:emoji, :pack_extensions]) - make_shortcode_to_file_map(pack_dir, extensions) - |> Enum.map(fn {shortcode, rel_file} -> - filename = Path.join("/emoji/#{pack_name}", rel_file) + Logger.info( + "No emoji.txt found for pack \"#{pack_name}\", assuming all #{ + Enum.join(extensions, ", ") + } files are emoji" + ) - {shortcode, filename, [to_string(match_extra(emoji_groups, filename))]} - end) + make_shortcode_to_file_map(pack_dir, extensions) + |> Enum.map(fn {shortcode, rel_file} -> + filename = Path.join("/emoji/#{pack_name}", rel_file) + + {shortcode, filename, [to_string(match_extra(emoji_groups, filename))]} + end) + end end end -- cgit v1.2.3 From b791a0865641eb8210380e22e04a9fb680a79dcb Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 11 Aug 2019 00:39:21 +0300 Subject: Implement API actions on packs That incldues listing them and downloading them from other instances or from the remote url --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 171 ++++++++++++++++++++++ lib/pleroma/web/router.ex | 22 +++ 2 files changed, 193 insertions(+) create mode 100644 lib/pleroma/web/emoji_api/emoji_api_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex new file mode 100644 index 000000000..49d671518 --- /dev/null +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -0,0 +1,171 @@ +defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do + use Pleroma.Web, :controller + + def reload(conn, _params) do + Pleroma.Emoji.reload() + + conn |> json("ok") + end + + @emoji_dir_path Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + + def list_packs(conn, _params) do + pack_infos = + case File.ls(@emoji_dir_path) do + {:error, _} -> + %{} + + {:ok, results} -> + results + |> Enum.filter(fn file -> + dir_path = Path.join(@emoji_dir_path, file) + # Filter to only use the pack.toml packs + File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.toml")) + end) + |> Enum.map(fn pack_name -> + pack_path = Path.join(@emoji_dir_path, pack_name) + pack_file = Path.join(pack_path, "pack.toml") + + {pack_name, Toml.decode_file!(pack_file)} + end) + # Transform into a map of pack-name => pack-data + # Check if all the files are in place and can be sent + |> Enum.map(fn {name, pack} -> + pack_path = Path.join(@emoji_dir_path, name) + + archive_for_sha = make_archive(name, pack, pack_path) + archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() + + {name, + pack + |> put_in(["pack", "can-download"], can_download?(pack, pack_path)) + |> put_in(["pack", "download-sha256"], archive_sha)} + end) + |> Enum.into(%{}) + end + + conn |> json(pack_infos) + end + + defp can_download?(pack, pack_path) do + # If the pack is set as shared, check if it can be downloaded + # That means that when asked, the pack can be packed and sent to the remote + # Otherwise, they'd have to download it from external-src + pack["pack"]["share-files"] and + Enum.all?(pack["files"], fn {_, path} -> + File.exists?(Path.join(pack_path, path)) + end) + end + + defp make_archive(name, pack, pack_dir) do + files = + ['pack.toml'] ++ + (pack["files"] |> Enum.map(fn {_, path} -> to_charlist(path) end)) + + {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) + + zip_result + end + + def download_shared(conn, %{"name" => name}) do + pack_dir = Path.join(@emoji_dir_path, name) + pack_toml = Path.join(pack_dir, "pack.toml") + + if File.exists?(pack_toml) do + pack = Toml.decode_file!(pack_toml) + + if can_download?(pack, pack_dir) do + zip_result = make_archive(name, pack, pack_dir) + + conn + |> send_download({:binary, zip_result}, filename: "#{name}.zip") + else + {:error, + conn + |> put_status(:forbidden) + |> json("Pack #{name} cannot be downloaded from this instance, either pack sharing\ + was disabled for this pack or some files are missing")} + end + else + {:error, + conn + |> put_status(:not_found) + |> json("Pack #{name} does not exist")} + end + end + + def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do + list_uri = "#{address}/api/pleroma/emoji/packs/list" + + list = Tesla.get!(list_uri).body |> Jason.decode!() + full_pack = list[name] + pfiles = full_pack["files"] + pack = full_pack["pack"] + + pack_info_res = + cond do + pack["share-files"] && pack["can-download"] -> + {:ok, + %{ + sha: pack["download-sha256"], + uri: "#{address}/api/pleroma/emoji/packs/download_shared/#{name}" + }} + + pack["fallback-src"] -> + {:ok, + %{ + sha: pack["fallback-src-sha256"], + uri: pack["fallback-src"], + fallback: true + }} + + true -> + {:error, "The pack was not set as shared and the is no fallback url to download from"} + end + + case pack_info_res do + {:ok, %{sha: sha, uri: uri} = pinfo} -> + sha = Base.decode16!(sha) + emoji_archive = Tesla.get!(uri).body + + got_sha = :crypto.hash(:sha256, emoji_archive) + + if got_sha == sha do + local_name = data["as"] || name + pack_dir = Path.join(@emoji_dir_path, local_name) + File.mkdir_p!(pack_dir) + + files = + ['pack.toml'] ++ + (pfiles |> Enum.map(fn {_, path} -> to_charlist(path) end)) + + {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) + + # Fallback URL might not contain a pack.toml file, if that happens - fail (for now) + # FIXME: there seems to be a lack of any kind of encoders besides JSON. + erres = + if pinfo[:fallback] do + toml_path = Path.join(pack_dir, "pack.toml") + + unless File.exists?(toml_path) do + conn + |> put_status(:internal_server_error) + |> text("No pack.toml in falblack source") + end + end + + if not is_nil(erres), do: erres, else: conn |> text("ok") + else + conn + |> put_status(:internal_server_error) + |> text("SHA256 for the pack doesn't match the one sent by the server") + end + + {:error, e} -> + conn |> put_status(:internal_server_error) |> text(e) + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index b9b85fd67..514446fb3 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -207,6 +207,28 @@ defmodule Pleroma.Web.Router do get("/moderation_log", AdminAPIController, :list_log) end + scope "/api/pleroma/emoji", Pleroma.Web.EmojiAPI do + scope [] do + pipe_through([:admin_api, :oauth_write]) + + post("/reload", EmojiAPIController, :reload) + end + + scope "/packs" do + # Modifying packs + pipe_through([:admin_api, :oauth_write]) + + post("/download_from", EmojiAPIController, :download_from) + end + + scope "/packs" do + # Pack info / downloading + get("/list", EmojiAPIController, :list_packs) + get("/download_shared/:name", EmojiAPIController, :download_shared) + get("/sha_of_shared/:name", EmojiAPIController, :sha_of_shared) + end + end + scope "/", Pleroma.Web.TwitterAPI do pipe_through(:pleroma_html) -- cgit v1.2.3 From 54b8e683bce13cf67f2674ea9f56b30604b28358 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 11 Aug 2019 22:32:15 +0300 Subject: Swap TOML for YAML to get YAML generation for packs from fallbacks If fallback url doesn't have a pack.yml file, one from the source will be used --- lib/pleroma/emoji.ex | 8 ++--- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 38 ++++++++++------------- lib/pleroma/web/router.ex | 1 - 3 files changed, 21 insertions(+), 26 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index ede734a53..2a9f5f804 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -143,12 +143,12 @@ defmodule Pleroma.Emoji do defp load_pack(pack_dir, emoji_groups) do pack_name = Path.basename(pack_dir) - pack_toml = Path.join(pack_dir, "pack.toml") + pack_yaml = Path.join(pack_dir, "pack.yml") - if File.exists?(pack_toml) do - toml = Toml.decode_file!(pack_toml) + if File.exists?(pack_yaml) do + yaml = RelaxYaml.Decoder.read_from_file(pack_yaml) - toml["files"] + yaml["files"] |> Enum.map(fn {name, rel_file} -> filename = Path.join("/emoji/#{pack_name}", rel_file) {name, filename, pack_name} diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 49d671518..7ef9b543d 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -22,14 +22,14 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do results |> Enum.filter(fn file -> dir_path = Path.join(@emoji_dir_path, file) - # Filter to only use the pack.toml packs - File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.toml")) + # Filter to only use the pack.yml packs + File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.yml")) end) |> Enum.map(fn pack_name -> pack_path = Path.join(@emoji_dir_path, pack_name) - pack_file = Path.join(pack_path, "pack.toml") + pack_file = Path.join(pack_path, "pack.yml") - {pack_name, Toml.decode_file!(pack_file)} + {pack_name, RelaxYaml.Decoder.read_from_file(pack_file)} end) # Transform into a map of pack-name => pack-data # Check if all the files are in place and can be sent @@ -62,7 +62,7 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do defp make_archive(name, pack, pack_dir) do files = - ['pack.toml'] ++ + ['pack.yml'] ++ (pack["files"] |> Enum.map(fn {_, path} -> to_charlist(path) end)) {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) @@ -72,10 +72,10 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do def download_shared(conn, %{"name" => name}) do pack_dir = Path.join(@emoji_dir_path, name) - pack_toml = Path.join(pack_dir, "pack.toml") + pack_yaml = Path.join(pack_dir, "pack.yml") - if File.exists?(pack_toml) do - pack = Toml.decode_file!(pack_toml) + if File.exists?(pack_yaml) do + pack = RelaxYaml.Decoder.read_from_file(pack_yaml) if can_download?(pack, pack_dir) do zip_result = make_archive(name, pack, pack_dir) @@ -139,25 +139,21 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do File.mkdir_p!(pack_dir) files = - ['pack.toml'] ++ + ['pack.yml'] ++ (pfiles |> Enum.map(fn {_, path} -> to_charlist(path) end)) {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) - # Fallback URL might not contain a pack.toml file, if that happens - fail (for now) - # FIXME: there seems to be a lack of any kind of encoders besides JSON. - erres = - if pinfo[:fallback] do - toml_path = Path.join(pack_dir, "pack.toml") - - unless File.exists?(toml_path) do - conn - |> put_status(:internal_server_error) - |> text("No pack.toml in falblack source") - end + # Fallback URL might not contain a pack.yml file. Put on we have if there's none + if pinfo[:fallback] do + yaml_path = Path.join(pack_dir, "pack.yml") + + unless File.exists?(yaml_path) do + File.write!(yaml_path, RelaxYaml.Encoder.encode(full_pack, [])) end + end - if not is_nil(erres), do: erres, else: conn |> text("ok") + conn |> text("ok") else conn |> put_status(:internal_server_error) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 514446fb3..1c781d750 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -225,7 +225,6 @@ defmodule Pleroma.Web.Router do # Pack info / downloading get("/list", EmojiAPIController, :list_packs) get("/download_shared/:name", EmojiAPIController, :download_shared) - get("/sha_of_shared/:name", EmojiAPIController, :sha_of_shared) end end -- cgit v1.2.3 From 7fb7dd9e0e0135af467477a66692990bdaecdbe9 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 11 Aug 2019 23:24:23 +0300 Subject: Only find SHA256 for packs that are shared --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 7ef9b543d..915059783 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -36,13 +36,19 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do |> Enum.map(fn {name, pack} -> pack_path = Path.join(@emoji_dir_path, name) - archive_for_sha = make_archive(name, pack, pack_path) - archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() - - {name, - pack - |> put_in(["pack", "can-download"], can_download?(pack, pack_path)) - |> put_in(["pack", "download-sha256"], archive_sha)} + if can_download?(pack, pack_path) do + archive_for_sha = make_archive(name, pack, pack_path) + archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() + + {name, + pack + |> put_in(["pack", "can-download"], true) + |> put_in(["pack", "download-sha256"], archive_sha)} + else + {name, + pack + |> put_in(["pack", "can-download"], false)} + end end) |> Enum.into(%{}) end -- cgit v1.2.3 From ee620ecbf11398277551ef603355a56a53690461 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 12 Aug 2019 13:13:01 +0300 Subject: Add caching for emoji pack sharing --- lib/pleroma/application.ex | 6 +++- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 42 ++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index dabce771d..a339e2c48 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -102,10 +102,14 @@ defmodule Pleroma.Application do build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000), build_cachex("scrubber", limit: 2500), build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500), - build_cachex("web_resp", limit: 2500) + build_cachex("web_resp", limit: 2500), + build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10) ] end + defp emoji_packs_expiration, + do: expiration(default: :timer.seconds(5 * 60), interval: :timer.seconds(60)) + defp idempotency_expiration, do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60)) diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 915059783..8219eaaa1 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -1,6 +1,8 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do use Pleroma.Web, :controller + require Logger + def reload(conn, _params) do Pleroma.Emoji.reload() @@ -12,6 +14,8 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do "emoji" ) + @cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) + def list_packs(conn, _params) do pack_infos = case File.ls(@emoji_dir_path) do @@ -66,13 +70,49 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do end) end - defp make_archive(name, pack, pack_dir) do + defp create_archive_and_cache(name, pack, pack_dir, md5) do files = ['pack.yml'] ++ (pack["files"] |> Enum.map(fn {_, path} -> to_charlist(path) end)) {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) + cache_ms = :timer.seconds(@cache_seconds_per_file * Enum.count(files)) + + Cachex.put!( + :emoji_packs_cache, + name, + # if pack.yml MD5 changes, the cache is not valid anymore + %{pack_yml_md5: md5, pack_data: zip_result}, + # Add a minute to cache time for every file in the pack + ttl: cache_ms + ) + + Logger.debug("Create an archive for the '#{name}' shared emoji pack, \ +keeping it in cache for #{div(cache_ms, 1000)}s") + + zip_result + end + + defp make_archive(name, pack, pack_dir) do + # Having a different pack.yml md5 invalidates cache + pack_yml_md5 = :crypto.hash(:md5, File.read!(Path.join(pack_dir, "pack.yml"))) + + maybe_cached_pack = Cachex.get!(:emoji_packs_cache, name) + + zip_result = + if is_nil(maybe_cached_pack) do + create_archive_and_cache(name, pack, pack_dir, pack_yml_md5) + else + if maybe_cached_pack[:pack_yml_md5] == pack_yml_md5 do + Logger.debug("Using cache for the '#{name}' shared emoji pack") + + maybe_cached_pack[:pack_data] + else + create_archive_and_cache(name, pack, pack_dir, pack_yml_md5) + end + end + zip_result end -- cgit v1.2.3 From 7a0c755d0a69157868e245b35b48ed07a7dfd3c7 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 12 Aug 2019 16:43:28 +0300 Subject: Send ok for emoji reloading as text, not as json --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 8219eaaa1..72daccc8c 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do def reload(conn, _params) do Pleroma.Emoji.reload() - conn |> json("ok") + conn |> text("ok") end @emoji_dir_path Path.join( -- cgit v1.2.3 From 3a8669b48771ac4203b6abf2a372c6960d36345a Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 12 Aug 2019 17:35:25 +0300 Subject: Fix responses for emoji pack controlller --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 72daccc8c..f2b1e8a8d 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do ttl: cache_ms ) - Logger.debug("Create an archive for the '#{name}' shared emoji pack, \ + Logger.debug("Create an archive for the '#{name}' emoji pack, \ keeping it in cache for #{div(cache_ms, 1000)}s") zip_result @@ -132,14 +132,14 @@ keeping it in cache for #{div(cache_ms, 1000)}s") {:error, conn |> put_status(:forbidden) - |> json("Pack #{name} cannot be downloaded from this instance, either pack sharing\ + |> text("Pack #{name} cannot be downloaded from this instance, either pack sharing\ was disabled for this pack or some files are missing")} end else {:error, conn |> put_status(:not_found) - |> json("Pack #{name} does not exist")} + |> text("Pack #{name} does not exist")} end end @@ -169,7 +169,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") }} true -> - {:error, "The pack was not set as shared and the is no fallback url to download from"} + {:error, "The pack was not set as shared and there is no fallback src to download from"} end case pack_info_res do -- cgit v1.2.3 From 2d4b8f3d20c4dbf60e52e95e77f2e77766974402 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 12 Aug 2019 18:03:59 +0300 Subject: Add an endpoint for deleting emoji packs --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 12 ++++++++++++ lib/pleroma/web/router.ex | 1 + 2 files changed, 13 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index f2b1e8a8d..49d970277 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -210,4 +210,16 @@ keeping it in cache for #{div(cache_ms, 1000)}s") conn |> put_status(:internal_server_error) |> text(e) end end + + def delete(conn, %{"name" => name}) do + pack_dir = Path.join(@emoji_dir_path, name) + + case File.rm_rf(pack_dir) do + {:ok, _} -> + conn |> text("ok") + + {:error, _} -> + conn |> put_status(:internal_server_error) |> text("Couldn't delete the pack #{name}") + end + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 1c781d750..4df0ca3c3 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -218,6 +218,7 @@ defmodule Pleroma.Web.Router do # Modifying packs pipe_through([:admin_api, :oauth_write]) + delete("/delete/:name", EmojiAPIController, :delete) post("/download_from", EmojiAPIController, :download_from) end -- cgit v1.2.3 From b0ecd412f5c499773cdc462c50d6c8104a819550 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 12 Aug 2019 18:28:05 +0300 Subject: Clean out old emojis on reload --- lib/pleroma/emoji.ex | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 2a9f5f804..f56b26da2 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -122,6 +122,9 @@ defmodule Pleroma.Emoji do fn pack -> load_pack(Path.join(emoji_dir_path, pack), emoji_groups) end ) + # Clear out old emojis + :ets.delete_all_objects(@ets) + true = :ets.insert(@ets, emojis) end -- cgit v1.2.3 From 2a94eca096f67a908410ffdd82f5bace8a3df88c Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Thu, 15 Aug 2019 11:39:39 +0300 Subject: Change YAML to JSON --- lib/pleroma/emoji.ex | 8 ++--- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 40 +++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index f56b26da2..170a7d098 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -146,12 +146,12 @@ defmodule Pleroma.Emoji do defp load_pack(pack_dir, emoji_groups) do pack_name = Path.basename(pack_dir) - pack_yaml = Path.join(pack_dir, "pack.yml") + pack_file = Path.join(pack_dir, "pack.json") - if File.exists?(pack_yaml) do - yaml = RelaxYaml.Decoder.read_from_file(pack_yaml) + if File.exists?(pack_file) do + contents = Jason.decode!(File.read!(pack_file)) - yaml["files"] + contents["files"] |> Enum.map(fn {name, rel_file} -> filename = Path.join("/emoji/#{pack_name}", rel_file) {name, filename, pack_name} diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 49d970277..aedc70372 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -26,14 +26,14 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do results |> Enum.filter(fn file -> dir_path = Path.join(@emoji_dir_path, file) - # Filter to only use the pack.yml packs - File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.yml")) + # Filter to only use the pack.json packs + File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json")) end) |> Enum.map(fn pack_name -> pack_path = Path.join(@emoji_dir_path, pack_name) - pack_file = Path.join(pack_path, "pack.yml") + pack_file = Path.join(pack_path, "pack.json") - {pack_name, RelaxYaml.Decoder.read_from_file(pack_file)} + {pack_name, Jason.decode!(File.read!(pack_file))} end) # Transform into a map of pack-name => pack-data # Check if all the files are in place and can be sent @@ -72,7 +72,7 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do defp create_archive_and_cache(name, pack, pack_dir, md5) do files = - ['pack.yml'] ++ + ['pack.json'] ++ (pack["files"] |> Enum.map(fn {_, path} -> to_charlist(path) end)) {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) @@ -82,8 +82,8 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do Cachex.put!( :emoji_packs_cache, name, - # if pack.yml MD5 changes, the cache is not valid anymore - %{pack_yml_md5: md5, pack_data: zip_result}, + # if pack.json MD5 changes, the cache is not valid anymore + %{pack_json_md5: md5, pack_data: zip_result}, # Add a minute to cache time for every file in the pack ttl: cache_ms ) @@ -95,21 +95,21 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end defp make_archive(name, pack, pack_dir) do - # Having a different pack.yml md5 invalidates cache - pack_yml_md5 = :crypto.hash(:md5, File.read!(Path.join(pack_dir, "pack.yml"))) + # Having a different pack.json md5 invalidates cache + pack_file_md5 = :crypto.hash(:md5, File.read!(Path.join(pack_dir, "pack.json"))) maybe_cached_pack = Cachex.get!(:emoji_packs_cache, name) zip_result = if is_nil(maybe_cached_pack) do - create_archive_and_cache(name, pack, pack_dir, pack_yml_md5) + create_archive_and_cache(name, pack, pack_dir, pack_file_md5) else - if maybe_cached_pack[:pack_yml_md5] == pack_yml_md5 do + if maybe_cached_pack[:pack_file_md5] == pack_file_md5 do Logger.debug("Using cache for the '#{name}' shared emoji pack") maybe_cached_pack[:pack_data] else - create_archive_and_cache(name, pack, pack_dir, pack_yml_md5) + create_archive_and_cache(name, pack, pack_dir, pack_file_md5) end end @@ -118,10 +118,10 @@ keeping it in cache for #{div(cache_ms, 1000)}s") def download_shared(conn, %{"name" => name}) do pack_dir = Path.join(@emoji_dir_path, name) - pack_yaml = Path.join(pack_dir, "pack.yml") + pack_file = Path.join(pack_dir, "pack.json") - if File.exists?(pack_yaml) do - pack = RelaxYaml.Decoder.read_from_file(pack_yaml) + if File.exists?(pack_file) do + pack = Jason.decode!(File.read!(pack_file)) if can_download?(pack, pack_dir) do zip_result = make_archive(name, pack, pack_dir) @@ -185,17 +185,17 @@ keeping it in cache for #{div(cache_ms, 1000)}s") File.mkdir_p!(pack_dir) files = - ['pack.yml'] ++ + ['pack.json'] ++ (pfiles |> Enum.map(fn {_, path} -> to_charlist(path) end)) {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) - # Fallback URL might not contain a pack.yml file. Put on we have if there's none + # Fallback URL might not contain a pack.json file. Put on we have if there's none if pinfo[:fallback] do - yaml_path = Path.join(pack_dir, "pack.yml") + pack_file_path = Path.join(pack_dir, "pack.json") - unless File.exists?(yaml_path) do - File.write!(yaml_path, RelaxYaml.Encoder.encode(full_pack, [])) + unless File.exists?(pack_file_path) do + File.write!(pack_file_path, Jason.encode!(full_pack)) end end -- cgit v1.2.3 From b78973d27f0c9225104914c79cf93bf3589fe7cc Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Thu, 15 Aug 2019 11:46:03 +0300 Subject: fallback can't have pack.json, reflect that in code having pacj.json and sha256 in a fallback pack would cause a circular dependency of itself --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index aedc70372..3b9eab8b8 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -184,19 +184,19 @@ keeping it in cache for #{div(cache_ms, 1000)}s") pack_dir = Path.join(@emoji_dir_path, local_name) File.mkdir_p!(pack_dir) + # Fallback cannot contain a pack.json file files = - ['pack.json'] ++ + unless(pinfo[:fallback], do: ['pack.json'], else: []) ++ (pfiles |> Enum.map(fn {_, path} -> to_charlist(path) end)) {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) - # Fallback URL might not contain a pack.json file. Put on we have if there's none + # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 + # in it to depend on itself if pinfo[:fallback] do pack_file_path = Path.join(pack_dir, "pack.json") - unless File.exists?(pack_file_path) do - File.write!(pack_file_path, Jason.encode!(full_pack)) - end + File.write!(pack_file_path, Jason.encode!(full_pack)) end conn |> text("ok") -- cgit v1.2.3 From bcc0bfd0c54784fe6a7ccd88fc083bd09dca41af Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Thu, 15 Aug 2019 19:55:58 +0300 Subject: Add an endpoint for emoji pack metadata updating --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 49 ++++++++++++++++++++++- lib/pleroma/web/router.ex | 1 + 2 files changed, 49 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 3b9eab8b8..4096ccbed 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -196,7 +196,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") if pinfo[:fallback] do pack_file_path = Path.join(pack_dir, "pack.json") - File.write!(pack_file_path, Jason.encode!(full_pack)) + File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) end conn |> text("ok") @@ -222,4 +222,51 @@ keeping it in cache for #{div(cache_ms, 1000)}s") conn |> put_status(:internal_server_error) |> text("Couldn't delete the pack #{name}") end end + + def update_metadata(conn, %{"name" => name, "new_data" => new_data}) do + pack_dir = Path.join(@emoji_dir_path, name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + new_data = + if not is_nil(new_data["fallback-src"]) and is_nil(new_data["fallback-src-sha256"]) do + pack_arch = Tesla.get!(new_data["fallback-src"]).body + + {:ok, flist} = :zip.unzip(pack_arch, [:memory]) + + # Check if all files from the pack.json are in the archive + has_all_files = + Enum.all?(full_pack["files"], fn {_, from_manifest} -> + Enum.find(flist, fn {from_archive, _} -> + to_string(from_archive) == from_manifest + end) + end) + + unless has_all_files do + {:error, + conn + |> put_status(:bad_request) + |> text("The fallback archive does not have all files specified in pack.json")} + else + fallback_sha = :crypto.hash(:sha256, pack_arch) |> Base.encode16() + + {:ok, new_data |> Map.put("fallback-src-sha256", fallback_sha)} + end + else + {:ok, new_data} + end + + case new_data do + {:ok, new_data} -> + full_pack = Map.put(full_pack, "pack", new_data) + File.write!(pack_file_p, Jason.encode!(full_pack, pretty: true)) + + # Send new data back with fallback sha filled + conn |> json(new_data) + + {:error, e} -> + e + end + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 4df0ca3c3..471d09c43 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -218,6 +218,7 @@ defmodule Pleroma.Web.Router do # Modifying packs pipe_through([:admin_api, :oauth_write]) + post("/update_metadata/:name", EmojiAPIController, :update_metadata) delete("/delete/:name", EmojiAPIController, :delete) post("/download_from", EmojiAPIController, :download_from) end -- cgit v1.2.3 From 261d92f9c2605c720e7fce8b05025e5ac452e5c9 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Fri, 16 Aug 2019 13:30:14 +0300 Subject: Update the pack fallback-src sha generation condition The old one would not regenerate sha when fallback src changed --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 4096ccbed..4873129c4 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -229,8 +229,13 @@ keeping it in cache for #{div(cache_ms, 1000)}s") full_pack = Jason.decode!(File.read!(pack_file_p)) + # The new fallback-src is in the new data and it's not the same as it was in the old data + should_update_fb_sha = + not is_nil(new_data["fallback-src"]) and + new_data["fallback-src"] != full_pack["pack"]["fallback-src"] + new_data = - if not is_nil(new_data["fallback-src"]) and is_nil(new_data["fallback-src-sha256"]) do + if should_update_fb_sha do pack_arch = Tesla.get!(new_data["fallback-src"]).body {:ok, flist} = :zip.unzip(pack_arch, [:memory]) -- cgit v1.2.3 From 9afe7258dd5ca1e5a6333a5a9f93d9ab43d4aaf4 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 18 Aug 2019 22:05:38 +0300 Subject: Implememt emoji pack file updating + write tests --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 132 +++++++++++++++++++++- lib/pleroma/web/router.ex | 3 +- 2 files changed, 133 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 4873129c4..dc3dcf1ea 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -223,7 +223,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end - def update_metadata(conn, %{"name" => name, "new_data" => new_data}) do + def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do pack_dir = Path.join(@emoji_dir_path, name) pack_file_p = Path.join(pack_dir, "pack.json") @@ -274,4 +274,134 @@ keeping it in cache for #{div(cache_ms, 1000)}s") e end end + + def update_file( + conn, + %{"pack_name" => pack_name, "action" => action, "shortcode" => shortcode} = params + ) do + pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + res = + case action do + "add" -> + unless Map.has_key?(full_pack["files"], shortcode) do + with %{"file" => %Plug.Upload{filename: filename, path: upload_path}} <- params do + # If there was a file name provided with the request, use it, otherwise just use the + # uploaded file name + filename = + if Map.has_key?(params, "filename") do + params["filename"] + else + filename + end + + file_path = Path.join(pack_dir, filename) + + # If the name contains directories, create them + if String.contains?(file_path, "/") do + File.mkdir_p!(Path.dirname(file_path)) + end + + # Copy the uploaded file from the temporary directory + File.copy!(upload_path, file_path) + + updated_full_pack = put_in(full_pack, ["files", shortcode], filename) + + {:ok, updated_full_pack} + else + _ -> {:error, conn |> put_status(:bad_request) |> text("\"file\" not provided")} + end + else + {:error, + conn + |> put_status(:conflict) + |> text("An emoji with the \"#{shortcode}\" shortcode already exists")} + end + + "remove" -> + if Map.has_key?(full_pack["files"], shortcode) do + {emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) + + emoji_file_path = Path.join(pack_dir, emoji_file_path) + + # Delete the emoji file + File.rm!(emoji_file_path) + + # If the old directory has no more files, remove it + if String.contains?(emoji_file_path, "/") do + dir = Path.dirname(emoji_file_path) + + if Enum.empty?(File.ls!(dir)) do + File.rmdir!(dir) + end + end + + {:ok, updated_full_pack} + else + {:error, + conn |> put_status(:bad_request) |> text("Emoji \"#{shortcode}\" does not exist")} + end + + "update" -> + if Map.has_key?(full_pack["files"], shortcode) do + with %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params do + # First, remove the old shortcode, saving the old path + {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) + old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) + new_emoji_file_path = Path.join(pack_dir, new_filename) + + # If the name contains directories, create them + if String.contains?(new_emoji_file_path, "/") do + File.mkdir_p!(Path.dirname(new_emoji_file_path)) + end + + # Move/Rename the old filename to a new filename + # These are probably on the same filesystem, so just rename should work + :ok = File.rename(old_emoji_file_path, new_emoji_file_path) + + # If the old directory has no more files, remove it + if String.contains?(old_emoji_file_path, "/") do + dir = Path.dirname(old_emoji_file_path) + + if Enum.empty?(File.ls!(dir)) do + File.rmdir!(dir) + end + end + + # Then, put in the new shortcode with the new path + updated_full_pack = + put_in(updated_full_pack, ["files", new_shortcode], new_filename) + + {:ok, updated_full_pack} + else + _ -> + {:error, + conn + |> put_status(:bad_request) + |> text("new_shortcode or new_file were not specified")} + end + else + {:error, + conn |> put_status(:bad_request) |> text("Emoji \"#{shortcode}\" does not exist")} + end + + _ -> + {:error, conn |> put_status(:bad_request) |> text("Unknown action: #{action}")} + end + + case res do + {:ok, updated_full_pack} -> + # Write the emoji pack file + File.write!(pack_file_p, Jason.encode!(updated_full_pack, pretty: true)) + + # Return the modified file list + conn |> json(updated_full_pack["files"]) + + {:error, e} -> + e + end + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 471d09c43..acd6f740b 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -218,7 +218,8 @@ defmodule Pleroma.Web.Router do # Modifying packs pipe_through([:admin_api, :oauth_write]) - post("/update_metadata/:name", EmojiAPIController, :update_metadata) + post("/update_file/:pack_name", EmojiAPIController, :update_file) + post("/update_metadata/:pack_name", EmojiAPIController, :update_metadata) delete("/delete/:name", EmojiAPIController, :delete) post("/download_from", EmojiAPIController, :download_from) end -- cgit v1.2.3 From 16edfef12e6781971e2056a80a0ac38dcc254b1b Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 19 Aug 2019 19:26:15 +0300 Subject: Handle empty shortcode/filename/new_shortcode/new_filename --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 78 ++++++++++++++--------- 1 file changed, 47 insertions(+), 31 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index dc3dcf1ea..fdecbb700 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -298,19 +298,27 @@ keeping it in cache for #{div(cache_ms, 1000)}s") filename end - file_path = Path.join(pack_dir, filename) + unless String.trim(shortcode) |> String.length() == 0 or + String.trim(filename) |> String.length() == 0 do + file_path = Path.join(pack_dir, filename) - # If the name contains directories, create them - if String.contains?(file_path, "/") do - File.mkdir_p!(Path.dirname(file_path)) - end + # If the name contains directories, create them + if String.contains?(file_path, "/") do + File.mkdir_p!(Path.dirname(file_path)) + end - # Copy the uploaded file from the temporary directory - File.copy!(upload_path, file_path) + # Copy the uploaded file from the temporary directory + File.copy!(upload_path, file_path) - updated_full_pack = put_in(full_pack, ["files", shortcode], filename) + updated_full_pack = put_in(full_pack, ["files", shortcode], filename) - {:ok, updated_full_pack} + {:ok, updated_full_pack} + else + {:error, + conn + |> put_status(:bad_request) + |> text("shortcode or filename cannot be empty")} + end else _ -> {:error, conn |> put_status(:bad_request) |> text("\"file\" not provided")} end @@ -348,34 +356,42 @@ keeping it in cache for #{div(cache_ms, 1000)}s") "update" -> if Map.has_key?(full_pack["files"], shortcode) do with %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params do - # First, remove the old shortcode, saving the old path - {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) - old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) - new_emoji_file_path = Path.join(pack_dir, new_filename) - - # If the name contains directories, create them - if String.contains?(new_emoji_file_path, "/") do - File.mkdir_p!(Path.dirname(new_emoji_file_path)) - end + unless String.trim(new_shortcode) |> String.length() == 0 or + String.trim(new_filename) |> String.length() == 0 do + # First, remove the old shortcode, saving the old path + {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) + old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) + new_emoji_file_path = Path.join(pack_dir, new_filename) + + # If the name contains directories, create them + if String.contains?(new_emoji_file_path, "/") do + File.mkdir_p!(Path.dirname(new_emoji_file_path)) + end - # Move/Rename the old filename to a new filename - # These are probably on the same filesystem, so just rename should work - :ok = File.rename(old_emoji_file_path, new_emoji_file_path) + # Move/Rename the old filename to a new filename + # These are probably on the same filesystem, so just rename should work + :ok = File.rename(old_emoji_file_path, new_emoji_file_path) - # If the old directory has no more files, remove it - if String.contains?(old_emoji_file_path, "/") do - dir = Path.dirname(old_emoji_file_path) + # If the old directory has no more files, remove it + if String.contains?(old_emoji_file_path, "/") do + dir = Path.dirname(old_emoji_file_path) - if Enum.empty?(File.ls!(dir)) do - File.rmdir!(dir) + if Enum.empty?(File.ls!(dir)) do + File.rmdir!(dir) + end end - end - # Then, put in the new shortcode with the new path - updated_full_pack = - put_in(updated_full_pack, ["files", new_shortcode], new_filename) + # Then, put in the new shortcode with the new path + updated_full_pack = + put_in(updated_full_pack, ["files", new_shortcode], new_filename) - {:ok, updated_full_pack} + {:ok, updated_full_pack} + else + {:error, + conn + |> put_status(:bad_request) + |> text("new_shortcode or new_filename cannot be empty")} + end else _ -> {:error, -- cgit v1.2.3 From 8dbdd5c280d15fde4712989001d4ddee1cd37cff Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 20 Aug 2019 14:52:36 +0300 Subject: Allow uploading new emojis to packs from URLs --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 57 ++++++++++++----------- 1 file changed, 31 insertions(+), 26 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index fdecbb700..87ae0e092 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -288,39 +288,44 @@ keeping it in cache for #{div(cache_ms, 1000)}s") case action do "add" -> unless Map.has_key?(full_pack["files"], shortcode) do - with %{"file" => %Plug.Upload{filename: filename, path: upload_path}} <- params do - # If there was a file name provided with the request, use it, otherwise just use the - # uploaded file name - filename = - if Map.has_key?(params, "filename") do - params["filename"] - else - filename + filename = + if Map.has_key?(params, "filename") do + params["filename"] + else + case params["file"] do + %Plug.Upload{filename: filename} -> filename + url when is_binary(url) -> Path.basename(url) end + end - unless String.trim(shortcode) |> String.length() == 0 or - String.trim(filename) |> String.length() == 0 do - file_path = Path.join(pack_dir, filename) + unless String.trim(shortcode) |> String.length() == 0 or + String.trim(filename) |> String.length() == 0 do + file_path = Path.join(pack_dir, filename) - # If the name contains directories, create them - if String.contains?(file_path, "/") do - File.mkdir_p!(Path.dirname(file_path)) - end - - # Copy the uploaded file from the temporary directory - File.copy!(upload_path, file_path) + # If the name contains directories, create them + if String.contains?(file_path, "/") do + File.mkdir_p!(Path.dirname(file_path)) + end - updated_full_pack = put_in(full_pack, ["files", shortcode], filename) + case params["file"] do + %Plug.Upload{path: upload_path} -> + # Copy the uploaded file from the temporary directory + File.copy!(upload_path, file_path) - {:ok, updated_full_pack} - else - {:error, - conn - |> put_status(:bad_request) - |> text("shortcode or filename cannot be empty")} + url when is_binary(url) -> + # Download and write the file + file_contents = Tesla.get!(url).body + File.write!(file_path, file_contents) end + + updated_full_pack = put_in(full_pack, ["files", shortcode], filename) + + {:ok, updated_full_pack} else - _ -> {:error, conn |> put_status(:bad_request) |> text("\"file\" not provided")} + {:error, + conn + |> put_status(:bad_request) + |> text("shortcode or filename cannot be empty")} end else {:error, -- cgit v1.2.3 From f5131540dc9bbf8038e6625f4524ca01b52abbbf Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 28 Aug 2019 19:29:01 +0300 Subject: Add a way to create emoji packs via an endpoint --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 21 +++++++++++++++++++++ lib/pleroma/web/router.ex | 1 + 2 files changed, 22 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 87ae0e092..0bd9cd207 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -211,6 +211,27 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end + def create(conn, %{"name" => name}) do + pack_dir = Path.join(@emoji_dir_path, name) + + unless File.exists?(pack_dir) do + File.mkdir_p!(pack_dir) + + pack_file_p = Path.join(pack_dir, "pack.json") + + File.write!( + pack_file_p, + Jason.encode!(%{pack: %{}, files: %{}}) + ) + + conn |> text("ok") + else + conn + |> put_status(:conflict) + |> text("A pack named \"#{name}\" already exists") + end + end + def delete(conn, %{"name" => name}) do pack_dir = Path.join(@emoji_dir_path, name) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index acd6f740b..a21fefc70 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -220,6 +220,7 @@ defmodule Pleroma.Web.Router do post("/update_file/:pack_name", EmojiAPIController, :update_file) post("/update_metadata/:pack_name", EmojiAPIController, :update_metadata) + post("/create/:name", EmojiAPIController, :create) delete("/delete/:name", EmojiAPIController, :delete) post("/download_from", EmojiAPIController, :download_from) end -- cgit v1.2.3 From 13cd93a0d314238427c217ec0ab8f59f329321f5 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 1 Sep 2019 15:38:45 +0300 Subject: Use && insted of "and" for checking shared-files for packs share-files can be nil and "and" does not like that --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 0bd9cd207..f34a4e08c 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -64,7 +64,7 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do # If the pack is set as shared, check if it can be downloaded # That means that when asked, the pack can be packed and sent to the remote # Otherwise, they'd have to download it from external-src - pack["pack"]["share-files"] and + pack["pack"]["share-files"] && Enum.all?(pack["files"], fn {_, path} -> File.exists?(Path.join(pack_path, path)) end) -- cgit v1.2.3 From 9eb2ee4df0478daec1172eec2289868105b72756 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 10 Sep 2019 21:16:30 +0300 Subject: Allow importing old (emoji.txt / plain) packs from the filesystem --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 66 +++++++++++++++++++++++ lib/pleroma/web/router.ex | 2 + 2 files changed, 68 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index f34a4e08c..dffb91b0f 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -446,4 +446,70 @@ keeping it in cache for #{div(cache_ms, 1000)}s") e end end + + def import_from_fs(conn, _params) do + case File.ls(@emoji_dir_path) do + {:error, _} -> + conn + |> put_status(:internal_server_error) + |> text("Error accessing emoji pack directory") + + {:ok, results} -> + imported_pack_names = + results + |> Enum.filter(fn file -> + dir_path = Path.join(@emoji_dir_path, file) + # Find the directories that do NOT have pack.json + File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) + end) + |> Enum.map(fn dir -> + dir_path = Path.join(@emoji_dir_path, dir) + emoji_txt_path = Path.join(dir_path, "emoji.txt") + + files_for_pack = + if File.exists?(emoji_txt_path) do + # There's an emoji.txt file, it's likely from a pack installed by the pack manager. + # Make a pack.json file from the contents of that emoji.txt fileh + + # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 + + # Create a map of shortcodes to filenames from emoji.txt + + File.read!(emoji_txt_path) + |> String.split("\n") + |> Enum.map(&String.trim/1) + |> Enum.map(fn line -> + case String.split(line, ~r/,\s*/) do + # This matches both strings with and without tags and we don't care about tags here + [name, file | _] -> + {name, file} + + _ -> + nil + end + end) + |> Enum.filter(fn x -> not is_nil(x) end) + |> Enum.into(%{}) + else + # If there's no emoji.txt, assume all files that are of certain extensions from the config + # are emojis and import them all + Pleroma.Emoji.make_shortcode_to_file_map( + dir_path, + Pleroma.Config.get!([:emoji, :pack_extensions]) + ) + end + + pack_json_contents = Jason.encode!(%{pack: %{}, files: files_for_pack}) + + File.write!( + Path.join(dir_path, "pack.json"), + pack_json_contents + ) + + dir + end) + + conn |> json(imported_pack_names) + end + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index a21fefc70..1252048f0 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -218,6 +218,8 @@ defmodule Pleroma.Web.Router do # Modifying packs pipe_through([:admin_api, :oauth_write]) + post("/import_from_fs", EmojiAPIController, :import_from_fs) + post("/update_file/:pack_name", EmojiAPIController, :update_file) post("/update_metadata/:pack_name", EmojiAPIController, :update_metadata) post("/create/:name", EmojiAPIController, :create) -- cgit v1.2.3 From 87057101b0e14eb51ff9367dfe9c5522ea933161 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 10 Sep 2019 21:34:57 +0300 Subject: Add documentation for the emoji api endpoints --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index dffb91b0f..dc676b00f 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -16,6 +16,12 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do @cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) + @doc """ + Lists the packs available on the instance as JSON. + + The information is public and does not require authentification. The format is + a map of "pack directory name" to pack.json contents. + """ def list_packs(conn, _params) do pack_infos = case File.ls(@emoji_dir_path) do @@ -116,6 +122,10 @@ keeping it in cache for #{div(cache_ms, 1000)}s") zip_result end + @doc """ + An endpoint for other instances (via admin UI) or users (via browser) + to download packs that the instance shares. + """ def download_shared(conn, %{"name" => name}) do pack_dir = Path.join(@emoji_dir_path, name) pack_file = Path.join(pack_dir, "pack.json") @@ -143,6 +153,13 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end + @doc """ + An admin endpoint to request downloading a pack named `pack_name` from the instance + `instance_address`. + + If the requested instance's admin chose to share the pack, it will be downloaded + from that instance, otherwise it will be downloaded from the fallback source, if there is one. + """ def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do list_uri = "#{address}/api/pleroma/emoji/packs/list" @@ -211,6 +228,9 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end + @doc """ + Creates an empty pack named `name` which then can be updated via the admin UI. + """ def create(conn, %{"name" => name}) do pack_dir = Path.join(@emoji_dir_path, name) @@ -232,6 +252,9 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end + @doc """ + Deletes the pack `name` and all it's files. + """ def delete(conn, %{"name" => name}) do pack_dir = Path.join(@emoji_dir_path, name) @@ -244,6 +267,11 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end + @doc """ + An endpoint to update `pack_names`'s metadata. + + `new_data` is the new metadata for the pack, that will replace the old metadata. + """ def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do pack_dir = Path.join(@emoji_dir_path, name) pack_file_p = Path.join(pack_dir, "pack.json") @@ -296,6 +324,20 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end + @doc """ + Updates a file in a pack. + + Updating can mean three things: + + - `add` adds an emoji named `shortcode` to the pack `pack_name`, + that means that the emoji file needs to be uploaded with the request + (thus requiring it to be a multipart request) and be named `file`. + There can also be an optional `filename` that will be the new emoji file name + (if it's not there, the name will be taken from the uploaded file). + - `update` changes emoji shortcode (from `shortcode` to `new_shortcode` or moves the file + (from the current filename to `new_filename`) + - `remove` removes the emoji named `shortcode` and it's associated file + """ def update_file( conn, %{"pack_name" => pack_name, "action" => action, "shortcode" => shortcode} = params @@ -447,6 +489,16 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end + @doc """ + Imports emoji from the filesystem. + + Importing means checking all the directories in the + `$instance_static/emoji/` for directories which do not have + `pack.json`. If one has an emoji.txt file, that file will be used + to create a `pack.json` file with it's contents. If the directory has + neither, all the files with specific configured extenstions will be + assumed to be emojis and stored in the new `pack.json` file. + """ def import_from_fs(conn, _params) do case File.ls(@emoji_dir_path) do {:error, _} -> -- cgit v1.2.3 From f6d4acc87181c94fa202ff5673f741ae9cb45b14 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 10 Sep 2019 22:09:20 +0300 Subject: Fix credo warnings --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index dc676b00f..cbd237519 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -532,7 +532,8 @@ keeping it in cache for #{div(cache_ms, 1000)}s") |> Enum.map(&String.trim/1) |> Enum.map(fn line -> case String.split(line, ~r/,\s*/) do - # This matches both strings with and without tags and we don't care about tags here + # This matches both strings with and without tags + # and we don't care about tags here [name, file | _] -> {name, file} @@ -543,8 +544,8 @@ keeping it in cache for #{div(cache_ms, 1000)}s") |> Enum.filter(fn x -> not is_nil(x) end) |> Enum.into(%{}) else - # If there's no emoji.txt, assume all files that are of certain extensions from the config - # are emojis and import them all + # If there's no emoji.txt, assume all files + # that are of certain extensions from the config are emojis and import them all Pleroma.Emoji.make_shortcode_to_file_map( dir_path, Pleroma.Config.get!([:emoji, :pack_extensions]) -- cgit v1.2.3 From 163082de6f789044b4fcb0c69f5b4cfd89731903 Mon Sep 17 00:00:00 2001 From: vaartis Date: Wed, 11 Sep 2019 09:07:19 +0000 Subject: Apply suggestion to lib/pleroma/web/emoji_api/emoji_api_controller.ex --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index cbd237519..499802fa5 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -104,22 +104,14 @@ keeping it in cache for #{div(cache_ms, 1000)}s") # Having a different pack.json md5 invalidates cache pack_file_md5 = :crypto.hash(:md5, File.read!(Path.join(pack_dir, "pack.json"))) - maybe_cached_pack = Cachex.get!(:emoji_packs_cache, name) + case Cachex.get!(:emoji_packs_cache, name) do + %{pack_file_md5: ^pack_file_md5, pack_data: zip_result} -> + Logger.debug("Using cache for the '#{name}' shared emoji pack") + zip_result - zip_result = - if is_nil(maybe_cached_pack) do + _ -> create_archive_and_cache(name, pack, pack_dir, pack_file_md5) - else - if maybe_cached_pack[:pack_file_md5] == pack_file_md5 do - Logger.debug("Using cache for the '#{name}' shared emoji pack") - - maybe_cached_pack[:pack_data] - else - create_archive_and_cache(name, pack, pack_dir, pack_file_md5) - end - end - - zip_result + end end @doc """ -- cgit v1.2.3 From c049c32270b8f70ae679e739730a3f63cdbd7d95 Mon Sep 17 00:00:00 2001 From: vaartis Date: Wed, 11 Sep 2019 09:12:22 +0000 Subject: Fixed a typo in create_archive_and_cache --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 499802fa5..51620a3eb 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -94,7 +94,7 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do ttl: cache_ms ) - Logger.debug("Create an archive for the '#{name}' emoji pack, \ + Logger.debug("Created an archive for the '#{name}' emoji pack, \ keeping it in cache for #{div(cache_ms, 1000)}s") zip_result -- cgit v1.2.3 From f251225caeede08869b472886337afea0cd47d51 Mon Sep 17 00:00:00 2001 From: vaartis Date: Wed, 11 Sep 2019 15:32:54 +0000 Subject: Apply suggestions to emoji_api_controller.ex --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 201 ++++++++++------------ 1 file changed, 95 insertions(+), 106 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 51620a3eb..0c3da6740 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -153,31 +153,32 @@ keeping it in cache for #{div(cache_ms, 1000)}s") from that instance, otherwise it will be downloaded from the fallback source, if there is one. """ def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do - list_uri = "#{address}/api/pleroma/emoji/packs/list" - - list = Tesla.get!(list_uri).body |> Jason.decode!() - full_pack = list[name] + full_pack = + "#{address}/api/pleroma/emoji/packs/list" + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> Map.get(name) pfiles = full_pack["files"] - pack = full_pack["pack"] pack_info_res = - cond do - pack["share-files"] && pack["can-download"] -> + case full_pack["pack"] do + %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> {:ok, %{ - sha: pack["download-sha256"], + sha: sha, uri: "#{address}/api/pleroma/emoji/packs/download_shared/#{name}" }} - pack["fallback-src"] -> + %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> {:ok, %{ - sha: pack["fallback-src-sha256"], - uri: pack["fallback-src"], + sha: sha, + uri: src, fallback: true }} - true -> + _ -> {:error, "The pack was not set as shared and there is no fallback src to download from"} end @@ -194,9 +195,9 @@ keeping it in cache for #{div(cache_ms, 1000)}s") File.mkdir_p!(pack_dir) # Fallback cannot contain a pack.json file - files = - unless(pinfo[:fallback], do: ['pack.json'], else: []) ++ - (pfiles |> Enum.map(fn {_, path} -> to_charlist(path) end)) + files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) + # Fallback cannot contain a pack.json file + files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) @@ -226,7 +227,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") def create(conn, %{"name" => name}) do pack_dir = Path.join(@emoji_dir_path, name) - unless File.exists?(pack_dir) do + if not File.exists?(pack_dir) do File.mkdir_p!(pack_dir) pack_file_p = Path.join(pack_dir, "pack.json") @@ -265,8 +266,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") `new_data` is the new metadata for the pack, that will replace the old metadata. """ def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do - pack_dir = Path.join(@emoji_dir_path, name) - pack_file_p = Path.join(pack_dir, "pack.json") + pack_file_p = Path.join([@emoji_dir_path, name, "pack.json"]) full_pack = Jason.decode!(File.read!(pack_file_p)) @@ -275,45 +275,40 @@ keeping it in cache for #{div(cache_ms, 1000)}s") not is_nil(new_data["fallback-src"]) and new_data["fallback-src"] != full_pack["pack"]["fallback-src"] - new_data = - if should_update_fb_sha do - pack_arch = Tesla.get!(new_data["fallback-src"]).body + with {_, true} <- {:should_update?, should_update_fb_sha}, + %{body: pack_arch} <- Tesla.get!(new_data["fallback-src"]), + {:ok, flist} <- :zip.unzip(pack_arch, [:memory]), + {_, true} <- {:has_all_files?, has_all_files?(full_pack, flist)} do + fallback_sha = :crypto.hash(:sha256, pack_arch) |> Base.encode16() - {:ok, flist} = :zip.unzip(pack_arch, [:memory]) - - # Check if all files from the pack.json are in the archive - has_all_files = - Enum.all?(full_pack["files"], fn {_, from_manifest} -> - Enum.find(flist, fn {from_archive, _} -> - to_string(from_archive) == from_manifest - end) - end) - - unless has_all_files do - {:error, - conn - |> put_status(:bad_request) - |> text("The fallback archive does not have all files specified in pack.json")} - else - fallback_sha = :crypto.hash(:sha256, pack_arch) |> Base.encode16() + new_data = Map.put(new_data, "fallback-src-sha256", fallback_sha) + update_metadata_and_send(conn, full_pack, new_data, pack_file_p) + else + {:should_update?, _} -> + update_metadata_and_send(conn, full_pack, new_data, pack_file_p) - {:ok, new_data |> Map.put("fallback-src-sha256", fallback_sha)} - end - else - {:ok, new_data} - end + {:has_all_files?, _} -> + conn + |> put_status(:bad_request) + |> text("The fallback archive does not have all files specified in pack.json") + end + end - case new_data do - {:ok, new_data} -> - full_pack = Map.put(full_pack, "pack", new_data) - File.write!(pack_file_p, Jason.encode!(full_pack, pretty: true)) + # Check if all files from the pack.json are in the archive + defp has_all_files?(%{"files" => files}, flist) do + Enum.all?(files, fn {_, from_manifest} -> + Enum.find(flist, fn {from_archive, _} -> + to_string(from_archive) == from_manifest + end) + end) + end - # Send new data back with fallback sha filled - conn |> json(new_data) + defp update_metadata_and_send(conn, full_pack, new_data, pack_file_p) do + full_pack = Map.put(full_pack, "pack", new_data) + File.write!(pack_file_p, Jason.encode!(full_pack, pretty: true)) - {:error, e} -> - e - end + # Send new data back with fallback sha filled + json(conn, new_data) end @doc """ @@ -492,69 +487,63 @@ keeping it in cache for #{div(cache_ms, 1000)}s") assumed to be emojis and stored in the new `pack.json` file. """ def import_from_fs(conn, _params) do - case File.ls(@emoji_dir_path) do + with {:ok, results} <- File.ls(@emoji_dir_path) do + imported_pack_names = + results + |> Enum.filter(fn file -> + dir_path = Path.join(@emoji_dir_path, file) + # Find the directories that do NOT have pack.json + File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) + end) + |> Enum.map(&write_pack_json_contents/1) + + json(conn, imported_pack_names) + else {:error, _} -> conn |> put_status(:internal_server_error) |> text("Error accessing emoji pack directory") + end + end - {:ok, results} -> - imported_pack_names = - results - |> Enum.filter(fn file -> - dir_path = Path.join(@emoji_dir_path, file) - # Find the directories that do NOT have pack.json - File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) - end) - |> Enum.map(fn dir -> - dir_path = Path.join(@emoji_dir_path, dir) - emoji_txt_path = Path.join(dir_path, "emoji.txt") - - files_for_pack = - if File.exists?(emoji_txt_path) do - # There's an emoji.txt file, it's likely from a pack installed by the pack manager. - # Make a pack.json file from the contents of that emoji.txt fileh - - # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 - - # Create a map of shortcodes to filenames from emoji.txt - - File.read!(emoji_txt_path) - |> String.split("\n") - |> Enum.map(&String.trim/1) - |> Enum.map(fn line -> - case String.split(line, ~r/,\s*/) do - # This matches both strings with and without tags - # and we don't care about tags here - [name, file | _] -> - {name, file} - - _ -> - nil - end - end) - |> Enum.filter(fn x -> not is_nil(x) end) - |> Enum.into(%{}) - else - # If there's no emoji.txt, assume all files - # that are of certain extensions from the config are emojis and import them all - Pleroma.Emoji.make_shortcode_to_file_map( - dir_path, - Pleroma.Config.get!([:emoji, :pack_extensions]) - ) - end + defp write_pack_json_contents(dir) do + dir_path = Path.join(@emoji_dir_path, dir) + emoji_txt_path = Path.join(dir_path, "emoji.txt") - pack_json_contents = Jason.encode!(%{pack: %{}, files: files_for_pack}) + files_for_pack = files_for_pack(emoji_txt_path, dir_path) + pack_json_contents = Jason.encode!(%{pack: %{}, files: files_for_pack}) - File.write!( - Path.join(dir_path, "pack.json"), - pack_json_contents - ) + File.write!(Path.join(dir_path, "pack.json"), pack_json_contents) - dir - end) + dir + end - conn |> json(imported_pack_names) + defp files_for_pack(emoji_txt_path, dir_path) do + if File.exists?(emoji_txt_path) do + # There's an emoji.txt file, it's likely from a pack installed by the pack manager. + # Make a pack.json file from the contents of that emoji.txt fileh + + # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 + + # Create a map of shortcodes to filenames from emoji.txt + File.read!(emoji_txt_path) + |> String.split("\n") + |> Enum.map(&String.trim/1) + |> Enum.map(fn line -> + case String.split(line, ~r/,\s*/) do + # This matches both strings with and without tags + # and we don't care about tags here + [name, file | _] -> {name, file} + _ -> nil + end + end) + |> Enum.filter(fn x -> not is_nil(x) end) + |> Enum.into(%{}) + else + # If there's no emoji.txt, assume all files + # that are of certain extensions from the config are emojis and import them all + pack_extensions = Pleroma.Config.get!([:emoji, :pack_extensions]) + Pleroma.Emoji.make_shortcode_to_file_map(dir_path, pack_extensions) end end end -- cgit v1.2.3 From b8a214b0ab264a64ca287e40e99acd401810ef58 Mon Sep 17 00:00:00 2001 From: vaartis Date: Wed, 11 Sep 2019 15:48:51 +0000 Subject: Split list_packs --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 83 ++++++++++++----------- 1 file changed, 43 insertions(+), 40 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 0c3da6740..22619f4d7 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -23,47 +23,49 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do a map of "pack directory name" to pack.json contents. """ def list_packs(conn, _params) do - pack_infos = - case File.ls(@emoji_dir_path) do - {:error, _} -> - %{} - - {:ok, results} -> - results - |> Enum.filter(fn file -> - dir_path = Path.join(@emoji_dir_path, file) - # Filter to only use the pack.json packs - File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json")) - end) - |> Enum.map(fn pack_name -> - pack_path = Path.join(@emoji_dir_path, pack_name) - pack_file = Path.join(pack_path, "pack.json") - - {pack_name, Jason.decode!(File.read!(pack_file))} - end) - # Transform into a map of pack-name => pack-data - # Check if all the files are in place and can be sent - |> Enum.map(fn {name, pack} -> - pack_path = Path.join(@emoji_dir_path, name) - - if can_download?(pack, pack_path) do - archive_for_sha = make_archive(name, pack, pack_path) - archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() - - {name, - pack - |> put_in(["pack", "can-download"], true) - |> put_in(["pack", "download-sha256"], archive_sha)} - else - {name, - pack - |> put_in(["pack", "can-download"], false)} - end - end) - |> Enum.into(%{}) - end + with {:ok, results} <- File.ls(@emoji_dir_path) do + pack_infos = + results + |> Enum.filter(&has_pack_json?/1) + |> Enum.map(&load_pack/1) + # Check if all the files are in place and can be sent + |> Enum.map(&validate_pack/1) + # Transform into a map of pack-name => pack-data + |> Enum.into(%{}) + + json(conn, pack_infos) + end + end + + defp has_pack_json?(file) do + dir_path = Path.join(@emoji_dir_path, file) + # Filter to only use the pack.json packs + File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json")) + end - conn |> json(pack_infos) + defp load_pack(pack_name) do + pack_path = Path.join(@emoji_dir_path, pack_name) + pack_file = Path.join(pack_path, "pack.json") + + {pack_name, Jason.decode!(File.read!(pack_file))} + end + + defp validate_pack({name, pack}) do + pack_path = Path.join(@emoji_dir_path, name) + + if can_download?(pack, pack_path) do + archive_for_sha = make_archive(name, pack, pack_path) + archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() + + pack = + pack + |> put_in(["pack", "can-download"], true) + |> put_in(["pack", "download-sha256"], archive_sha) + + {name, pack} + else + {name, put_in(pack, ["pack", "can-download"], false)} + end end defp can_download?(pack, pack_path) do @@ -159,6 +161,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") |> Map.get(:body) |> Jason.decode!() |> Map.get(name) + pfiles = full_pack["files"] pack_info_res = -- cgit v1.2.3 From 8790365fef9d5f76b7ac1c94933e2ee218e76285 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 11 Sep 2019 18:52:21 +0300 Subject: Remove unused variable --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 2 -- 1 file changed, 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 22619f4d7..8ef6ae71f 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -162,8 +162,6 @@ keeping it in cache for #{div(cache_ms, 1000)}s") |> Jason.decode!() |> Map.get(name) - pfiles = full_pack["files"] - pack_info_res = case full_pack["pack"] do %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> -- cgit v1.2.3 From 8f509e6d1ee8955fc430d1f4ed7929ba0d91177c Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 11 Sep 2019 18:59:31 +0300 Subject: Use with w/ pack_info_res --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 52 ++++++++++------------- 1 file changed, 23 insertions(+), 29 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 8ef6ae71f..9e0ff0b28 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -183,42 +183,36 @@ keeping it in cache for #{div(cache_ms, 1000)}s") {:error, "The pack was not set as shared and there is no fallback src to download from"} end - case pack_info_res do - {:ok, %{sha: sha, uri: uri} = pinfo} -> - sha = Base.decode16!(sha) - emoji_archive = Tesla.get!(uri).body - - got_sha = :crypto.hash(:sha256, emoji_archive) - - if got_sha == sha do - local_name = data["as"] || name - pack_dir = Path.join(@emoji_dir_path, local_name) - File.mkdir_p!(pack_dir) - - # Fallback cannot contain a pack.json file - files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) - # Fallback cannot contain a pack.json file - files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files + with {:ok, %{sha: sha, uri: uri} = pinfo} <- pack_info_res, + %{body: emoji_archive} <- Tesla.get!(uri), + {_, true} <- {:sha, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do + local_name = data["as"] || name + pack_dir = Path.join(@emoji_dir_path, local_name) + File.mkdir_p!(pack_dir) - {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) + files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) + # Fallback cannot contain a pack.json file + files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files - # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 - # in it to depend on itself - if pinfo[:fallback] do - pack_file_path = Path.join(pack_dir, "pack.json") + {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) - File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) - end + # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 + # in it to depend on itself + if pinfo[:fallback] do + pack_file_path = Path.join(pack_dir, "pack.json") - conn |> text("ok") - else - conn - |> put_status(:internal_server_error) - |> text("SHA256 for the pack doesn't match the one sent by the server") - end + File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) + end + text(conn, "ok") + else {:error, e} -> conn |> put_status(:internal_server_error) |> text(e) + + {:sha, _} -> + conn + |> put_status(:internal_server_error) + |> text("SHA256 for the pack doesn't match the one sent by the server") end end -- cgit v1.2.3 From cb125ffaf7f744e60fc134ef6b7b847d3838922a Mon Sep 17 00:00:00 2001 From: vaartis Date: Wed, 11 Sep 2019 16:00:48 +0000 Subject: Apply suggestion to lib/pleroma/web/emoji_api/emoji_api_controller.ex --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 32 ++++++++++------------- 1 file changed, 14 insertions(+), 18 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 9e0ff0b28..28eaf5ae3 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -124,26 +124,22 @@ keeping it in cache for #{div(cache_ms, 1000)}s") pack_dir = Path.join(@emoji_dir_path, name) pack_file = Path.join(pack_dir, "pack.json") - if File.exists?(pack_file) do - pack = Jason.decode!(File.read!(pack_file)) - - if can_download?(pack, pack_dir) do - zip_result = make_archive(name, pack, pack_dir) + with {_, true} <- {:exists?, File.exists?(pack_file)}, + pack = Jason.decode!(File.read!(pack_file)), + {_, true} <- {:can_download?, can_download?(pack, pack_dir)} do + zip_result = make_archive(name, pack, pack_dir) + send_download(conn, {:binary, zip_result}, filename: "#{name}.zip") + else + {:can_download?, _} -> + conn + |> put_status(:forbidden) + |> text("Pack #{name} cannot be downloaded from this instance, either pack sharing\ + was disabled for this pack or some files are missing") + {:exists?, _} -> conn - |> send_download({:binary, zip_result}, filename: "#{name}.zip") - else - {:error, - conn - |> put_status(:forbidden) - |> text("Pack #{name} cannot be downloaded from this instance, either pack sharing\ - was disabled for this pack or some files are missing")} - end - else - {:error, - conn - |> put_status(:not_found) - |> text("Pack #{name} does not exist")} + |> put_status(:not_found) + |> text("Pack #{name} does not exist") end end -- cgit v1.2.3 From f24731788ef9dcbeb29c9dc5db9270a5787caff6 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 11 Sep 2019 19:01:21 +0300 Subject: Move emoji pack list from /list to / --- lib/pleroma/web/router.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 1252048f0..17f7406fd 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -229,7 +229,7 @@ defmodule Pleroma.Web.Router do scope "/packs" do # Pack info / downloading - get("/list", EmojiAPIController, :list_packs) + get("/", EmojiAPIController, :list_packs) get("/download_shared/:name", EmojiAPIController, :download_shared) end end -- cgit v1.2.3 From 7c784128fd8016e133c59e9c5076fa2d77a9bdee Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 11 Sep 2019 19:39:47 +0300 Subject: Change emoji api responses to JSON --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 316 ++++++++++++---------- 1 file changed, 168 insertions(+), 148 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 28eaf5ae3..1c5b7c687 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do def reload(conn, _params) do Pleroma.Emoji.reload() - conn |> text("ok") + conn |> json("ok") end @emoji_dir_path Path.join( @@ -133,13 +133,15 @@ keeping it in cache for #{div(cache_ms, 1000)}s") {:can_download?, _} -> conn |> put_status(:forbidden) - |> text("Pack #{name} cannot be downloaded from this instance, either pack sharing\ - was disabled for this pack or some files are missing") + |> json(%{ + error: "Pack #{name} cannot be downloaded from this instance, either pack sharing\ + was disabled for this pack or some files are missing" + }) {:exists?, _} -> conn |> put_status(:not_found) - |> text("Pack #{name} does not exist") + |> json(%{error: "Pack #{name} does not exist"}) end end @@ -200,15 +202,15 @@ keeping it in cache for #{div(cache_ms, 1000)}s") File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) end - text(conn, "ok") + json(conn, "ok") else {:error, e} -> - conn |> put_status(:internal_server_error) |> text(e) + conn |> put_status(:internal_server_error) |> json(%{error: e}) {:sha, _} -> conn |> put_status(:internal_server_error) - |> text("SHA256 for the pack doesn't match the one sent by the server") + |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) end end @@ -228,11 +230,11 @@ keeping it in cache for #{div(cache_ms, 1000)}s") Jason.encode!(%{pack: %{}, files: %{}}) ) - conn |> text("ok") + conn |> json("ok") else conn |> put_status(:conflict) - |> text("A pack named \"#{name}\" already exists") + |> json(%{error: "A pack named \"#{name}\" already exists"}) end end @@ -244,10 +246,12 @@ keeping it in cache for #{div(cache_ms, 1000)}s") case File.rm_rf(pack_dir) do {:ok, _} -> - conn |> text("ok") + conn |> json("ok") {:error, _} -> - conn |> put_status(:internal_server_error) |> text("Couldn't delete the pack #{name}") + conn + |> put_status(:internal_server_error) + |> json(%{error: "Couldn't delete the pack #{name}"}) end end @@ -281,7 +285,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") {:has_all_files?, _} -> conn |> put_status(:bad_request) - |> text("The fallback archive does not have all files specified in pack.json") + |> json(%{error: "The fallback archive does not have all files specified in pack.json"}) end end @@ -302,6 +306,25 @@ keeping it in cache for #{div(cache_ms, 1000)}s") json(conn, new_data) end + defp get_filename(%{"filename" => filename}), do: filename + + defp get_filename(%{"file" => file}) do + case file do + %Plug.Upload{filename: filename} -> filename + url when is_binary(url) -> Path.basename(url) + end + end + + defp empty?(str), do: String.trim(str) == "" + + defp update_file_and_send(conn, updated_full_pack, pack_file_p) do + # Write the emoji pack file + File.write!(pack_file_p, Jason.encode!(updated_full_pack, pretty: true)) + + # Return the modified file list + json(conn, updated_full_pack["files"]) + end + @doc """ Updates a file in a pack. @@ -316,157 +339,154 @@ keeping it in cache for #{div(cache_ms, 1000)}s") (from the current filename to `new_filename`) - `remove` removes the emoji named `shortcode` and it's associated file """ + + # Add def update_file( conn, - %{"pack_name" => pack_name, "action" => action, "shortcode" => shortcode} = params + %{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params ) do pack_dir = Path.join(@emoji_dir_path, pack_name) pack_file_p = Path.join(pack_dir, "pack.json") full_pack = Jason.decode!(File.read!(pack_file_p)) - res = - case action do - "add" -> - unless Map.has_key?(full_pack["files"], shortcode) do - filename = - if Map.has_key?(params, "filename") do - params["filename"] - else - case params["file"] do - %Plug.Upload{filename: filename} -> filename - url when is_binary(url) -> Path.basename(url) - end - end - - unless String.trim(shortcode) |> String.length() == 0 or - String.trim(filename) |> String.length() == 0 do - file_path = Path.join(pack_dir, filename) - - # If the name contains directories, create them - if String.contains?(file_path, "/") do - File.mkdir_p!(Path.dirname(file_path)) - end - - case params["file"] do - %Plug.Upload{path: upload_path} -> - # Copy the uploaded file from the temporary directory - File.copy!(upload_path, file_path) - - url when is_binary(url) -> - # Download and write the file - file_contents = Tesla.get!(url).body - File.write!(file_path, file_contents) - end - - updated_full_pack = put_in(full_pack, ["files", shortcode], filename) - - {:ok, updated_full_pack} - else - {:error, - conn - |> put_status(:bad_request) - |> text("shortcode or filename cannot be empty")} - end - else - {:error, - conn - |> put_status(:conflict) - |> text("An emoji with the \"#{shortcode}\" shortcode already exists")} - end - - "remove" -> - if Map.has_key?(full_pack["files"], shortcode) do - {emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) - - emoji_file_path = Path.join(pack_dir, emoji_file_path) - - # Delete the emoji file - File.rm!(emoji_file_path) - - # If the old directory has no more files, remove it - if String.contains?(emoji_file_path, "/") do - dir = Path.dirname(emoji_file_path) - - if Enum.empty?(File.ls!(dir)) do - File.rmdir!(dir) - end - end - - {:ok, updated_full_pack} - else - {:error, - conn |> put_status(:bad_request) |> text("Emoji \"#{shortcode}\" does not exist")} - end - - "update" -> - if Map.has_key?(full_pack["files"], shortcode) do - with %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params do - unless String.trim(new_shortcode) |> String.length() == 0 or - String.trim(new_filename) |> String.length() == 0 do - # First, remove the old shortcode, saving the old path - {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) - old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) - new_emoji_file_path = Path.join(pack_dir, new_filename) - - # If the name contains directories, create them - if String.contains?(new_emoji_file_path, "/") do - File.mkdir_p!(Path.dirname(new_emoji_file_path)) - end - - # Move/Rename the old filename to a new filename - # These are probably on the same filesystem, so just rename should work - :ok = File.rename(old_emoji_file_path, new_emoji_file_path) - - # If the old directory has no more files, remove it - if String.contains?(old_emoji_file_path, "/") do - dir = Path.dirname(old_emoji_file_path) - - if Enum.empty?(File.ls!(dir)) do - File.rmdir!(dir) - end - end - - # Then, put in the new shortcode with the new path - updated_full_pack = - put_in(updated_full_pack, ["files", new_shortcode], new_filename) - - {:ok, updated_full_pack} - else - {:error, - conn - |> put_status(:bad_request) - |> text("new_shortcode or new_filename cannot be empty")} - end - else - _ -> - {:error, - conn - |> put_status(:bad_request) - |> text("new_shortcode or new_file were not specified")} - end - else - {:error, - conn |> put_status(:bad_request) |> text("Emoji \"#{shortcode}\" does not exist")} - end + with {_, false} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, + filename <- get_filename(params), + false <- empty?(shortcode), + false <- empty?(filename) do + file_path = Path.join(pack_dir, filename) - _ -> - {:error, conn |> put_status(:bad_request) |> text("Unknown action: #{action}")} + # If the name contains directories, create them + if String.contains?(file_path, "/") do + File.mkdir_p!(Path.dirname(file_path)) end - case res do - {:ok, updated_full_pack} -> - # Write the emoji pack file - File.write!(pack_file_p, Jason.encode!(updated_full_pack, pretty: true)) + case params["file"] do + %Plug.Upload{path: upload_path} -> + # Copy the uploaded file from the temporary directory + File.copy!(upload_path, file_path) + + url when is_binary(url) -> + # Download and write the file + file_contents = Tesla.get!(url).body + File.write!(file_path, file_contents) + end - # Return the modified file list - conn |> json(updated_full_pack["files"]) + updated_full_pack = put_in(full_pack, ["files", shortcode], filename) + update_file_and_send(conn, updated_full_pack, pack_file_p) + else + {:has_shortcode, _} -> + conn + |> put_status(:conflict) + |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) - {:error, e} -> - e + true -> + conn + |> put_status(:bad_request) + |> json(%{error: "shortcode or filename cannot be empty"}) + end + end + + # Remove + def update_file(conn, %{ + "pack_name" => pack_name, + "action" => "remove", + "shortcode" => shortcode + }) do + pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + if Map.has_key?(full_pack["files"], shortcode) do + {emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) + + emoji_file_path = Path.join(pack_dir, emoji_file_path) + + # Delete the emoji file + File.rm!(emoji_file_path) + + # If the old directory has no more files, remove it + if String.contains?(emoji_file_path, "/") do + dir = Path.dirname(emoji_file_path) + + if Enum.empty?(File.ls!(dir)) do + File.rmdir!(dir) + end + end + + update_file_and_send(conn, updated_full_pack, pack_file_p) + else + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) end end + # Update + def update_file( + conn, + %{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params + ) do + pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + with {_, true} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, + %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params, + false <- empty?(new_shortcode), + false <- empty?(new_filename) do + # First, remove the old shortcode, saving the old path + {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) + old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) + new_emoji_file_path = Path.join(pack_dir, new_filename) + + # If the name contains directories, create them + if String.contains?(new_emoji_file_path, "/") do + File.mkdir_p!(Path.dirname(new_emoji_file_path)) + end + + # Move/Rename the old filename to a new filename + # These are probably on the same filesystem, so just rename should work + :ok = File.rename(old_emoji_file_path, new_emoji_file_path) + + # If the old directory has no more files, remove it + if String.contains?(old_emoji_file_path, "/") do + dir = Path.dirname(old_emoji_file_path) + + if Enum.empty?(File.ls!(dir)) do + File.rmdir!(dir) + end + end + + # Then, put in the new shortcode with the new path + updated_full_pack = put_in(updated_full_pack, ["files", new_shortcode], new_filename) + update_file_and_send(conn, updated_full_pack, pack_file_p) + else + {:has_shortcode, _} -> + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) + + true -> + conn + |> put_status(:bad_request) + |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + + _ -> + conn + |> put_status(:bad_request) + |> json(%{error: "new_shortcode or new_file were not specified"}) + end + end + + def update_file(conn, %{"action" => action}) do + conn + |> put_status(:bad_request) + |> json(%{error: "Unknown action: #{action}"}) + end + @doc """ Imports emoji from the filesystem. @@ -493,7 +513,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") {:error, _} -> conn |> put_status(:internal_server_error) - |> text("Error accessing emoji pack directory") + |> json(%{error: "Error accessing emoji pack directory"}) end end -- cgit v1.2.3 From 3971bf9c5f00d12a0a2048eb3676069d58a9f243 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 11 Sep 2019 21:43:16 +0300 Subject: Change :sha to :checksum --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 1c5b7c687..0d4a17c61 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -183,7 +183,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") with {:ok, %{sha: sha, uri: uri} = pinfo} <- pack_info_res, %{body: emoji_archive} <- Tesla.get!(uri), - {_, true} <- {:sha, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do + {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do local_name = data["as"] || name pack_dir = Path.join(@emoji_dir_path, local_name) File.mkdir_p!(pack_dir) @@ -207,7 +207,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") {:error, e} -> conn |> put_status(:internal_server_error) |> json(%{error: e}) - {:sha, _} -> + {:checksum, _} -> conn |> put_status(:internal_server_error) |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) -- cgit v1.2.3 From 6cd651a38be898456c06d8fee7fd15f1b406848c Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 11 Sep 2019 21:50:55 +0300 Subject: Make the emoji controller api more RESTy --- lib/pleroma/web/router.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 17f7406fd..bae25c60a 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -220,17 +220,17 @@ defmodule Pleroma.Web.Router do post("/import_from_fs", EmojiAPIController, :import_from_fs) - post("/update_file/:pack_name", EmojiAPIController, :update_file) - post("/update_metadata/:pack_name", EmojiAPIController, :update_metadata) - post("/create/:name", EmojiAPIController, :create) - delete("/delete/:name", EmojiAPIController, :delete) + post("/:pack_name/update_file", EmojiAPIController, :update_file) + post("/:pack_name/update_metadata", EmojiAPIController, :update_metadata) + put("/:name", EmojiAPIController, :create) + delete("/:name", EmojiAPIController, :delete) post("/download_from", EmojiAPIController, :download_from) end scope "/packs" do # Pack info / downloading get("/", EmojiAPIController, :list_packs) - get("/download_shared/:name", EmojiAPIController, :download_shared) + get("/:name/download_shared/", EmojiAPIController, :download_shared) end end -- cgit v1.2.3 From 74fb6d864760ccaa18b9a20d148c590254779454 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 11 Sep 2019 22:43:00 +0300 Subject: Move EmojiAPIController from EmojiAPI to PleromaAPI --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 2 +- lib/pleroma/web/router.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index 0d4a17c61..a83f8af57 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -1,4 +1,4 @@ -defmodule Pleroma.Web.EmojiAPI.EmojiAPIController do +defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do use Pleroma.Web, :controller require Logger diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index bae25c60a..715e4ba68 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -207,7 +207,7 @@ defmodule Pleroma.Web.Router do get("/moderation_log", AdminAPIController, :list_log) end - scope "/api/pleroma/emoji", Pleroma.Web.EmojiAPI do + scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do scope [] do pipe_through([:admin_api, :oauth_write]) -- cgit v1.2.3 From 36f2275dc9f6c58163e4e07f8ace9d75e96033c7 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 11 Sep 2019 22:58:55 +0300 Subject: A feature for shareable emoji packs, use it in download_from & tests --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 111 ++++++++++++---------- lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 1 + 2 files changed, 64 insertions(+), 48 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex index a83f8af57..36ca2c804 100644 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ b/lib/pleroma/web/emoji_api/emoji_api_controller.ex @@ -153,64 +153,79 @@ keeping it in cache for #{div(cache_ms, 1000)}s") from that instance, otherwise it will be downloaded from the fallback source, if there is one. """ def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do - full_pack = - "#{address}/api/pleroma/emoji/packs/list" + shareable_packs_available = + "#{address}/nodeinfo/2.1.json" |> Tesla.get!() |> Map.get(:body) |> Jason.decode!() - |> Map.get(name) - - pack_info_res = - case full_pack["pack"] do - %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> - {:ok, - %{ - sha: sha, - uri: "#{address}/api/pleroma/emoji/packs/download_shared/#{name}" - }} - - %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> - {:ok, - %{ - sha: sha, - uri: src, - fallback: true - }} - - _ -> - {:error, "The pack was not set as shared and there is no fallback src to download from"} - end + |> Map.get("features") + |> Enum.member?("shareable_emoji_packs") + + if shareable_packs_available do + full_pack = + "#{address}/api/pleroma/emoji/packs/list" + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> Map.get(name) + + pack_info_res = + case full_pack["pack"] do + %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> + {:ok, + %{ + sha: sha, + uri: "#{address}/api/pleroma/emoji/packs/download_shared/#{name}" + }} + + %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> + {:ok, + %{ + sha: sha, + uri: src, + fallback: true + }} + + _ -> + {:error, + "The pack was not set as shared and there is no fallback src to download from"} + end - with {:ok, %{sha: sha, uri: uri} = pinfo} <- pack_info_res, - %{body: emoji_archive} <- Tesla.get!(uri), - {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do - local_name = data["as"] || name - pack_dir = Path.join(@emoji_dir_path, local_name) - File.mkdir_p!(pack_dir) + with {:ok, %{sha: sha, uri: uri} = pinfo} <- pack_info_res, + %{body: emoji_archive} <- Tesla.get!(uri), + {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do + local_name = data["as"] || name + pack_dir = Path.join(@emoji_dir_path, local_name) + File.mkdir_p!(pack_dir) - files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) - # Fallback cannot contain a pack.json file - files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files + files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) + # Fallback cannot contain a pack.json file + files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files - {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) + {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) - # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 - # in it to depend on itself - if pinfo[:fallback] do - pack_file_path = Path.join(pack_dir, "pack.json") + # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 + # in it to depend on itself + if pinfo[:fallback] do + pack_file_path = Path.join(pack_dir, "pack.json") - File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) - end + File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) + end - json(conn, "ok") - else - {:error, e} -> - conn |> put_status(:internal_server_error) |> json(%{error: e}) + json(conn, "ok") + else + {:error, e} -> + conn |> put_status(:internal_server_error) |> json(%{error: e}) - {:checksum, _} -> - conn - |> put_status(:internal_server_error) - |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) + {:checksum, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) + end + else + conn + |> put_status(:internal_server_error) + |> json(%{error: "The requested instance does not support sharing emoji packs"}) end end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index ee14cfd6b..192984242 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -57,6 +57,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do "mastodon_api_streaming", "polls", "pleroma_explicit_addressing", + "shareable_emoji_packs", if Config.get([:media_proxy, :enabled]) do "media_proxy" end, -- cgit v1.2.3 From 7680aec17d6690ccf7383354572456c2118a8750 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Thu, 12 Sep 2019 00:00:28 +0300 Subject: Move emoji api to pleroma api dir --- lib/pleroma/web/emoji_api/emoji_api_controller.ex | 575 --------------------- .../web/pleroma_api/emoji_api_controller.ex | 575 +++++++++++++++++++++ 2 files changed, 575 insertions(+), 575 deletions(-) delete mode 100644 lib/pleroma/web/emoji_api/emoji_api_controller.ex create mode 100644 lib/pleroma/web/pleroma_api/emoji_api_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/emoji_api/emoji_api_controller.ex b/lib/pleroma/web/emoji_api/emoji_api_controller.ex deleted file mode 100644 index 36ca2c804..000000000 --- a/lib/pleroma/web/emoji_api/emoji_api_controller.ex +++ /dev/null @@ -1,575 +0,0 @@ -defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do - use Pleroma.Web, :controller - - require Logger - - def reload(conn, _params) do - Pleroma.Emoji.reload() - - conn |> json("ok") - end - - @emoji_dir_path Path.join( - Pleroma.Config.get!([:instance, :static_dir]), - "emoji" - ) - - @cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) - - @doc """ - Lists the packs available on the instance as JSON. - - The information is public and does not require authentification. The format is - a map of "pack directory name" to pack.json contents. - """ - def list_packs(conn, _params) do - with {:ok, results} <- File.ls(@emoji_dir_path) do - pack_infos = - results - |> Enum.filter(&has_pack_json?/1) - |> Enum.map(&load_pack/1) - # Check if all the files are in place and can be sent - |> Enum.map(&validate_pack/1) - # Transform into a map of pack-name => pack-data - |> Enum.into(%{}) - - json(conn, pack_infos) - end - end - - defp has_pack_json?(file) do - dir_path = Path.join(@emoji_dir_path, file) - # Filter to only use the pack.json packs - File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json")) - end - - defp load_pack(pack_name) do - pack_path = Path.join(@emoji_dir_path, pack_name) - pack_file = Path.join(pack_path, "pack.json") - - {pack_name, Jason.decode!(File.read!(pack_file))} - end - - defp validate_pack({name, pack}) do - pack_path = Path.join(@emoji_dir_path, name) - - if can_download?(pack, pack_path) do - archive_for_sha = make_archive(name, pack, pack_path) - archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() - - pack = - pack - |> put_in(["pack", "can-download"], true) - |> put_in(["pack", "download-sha256"], archive_sha) - - {name, pack} - else - {name, put_in(pack, ["pack", "can-download"], false)} - end - end - - defp can_download?(pack, pack_path) do - # If the pack is set as shared, check if it can be downloaded - # That means that when asked, the pack can be packed and sent to the remote - # Otherwise, they'd have to download it from external-src - pack["pack"]["share-files"] && - Enum.all?(pack["files"], fn {_, path} -> - File.exists?(Path.join(pack_path, path)) - end) - end - - defp create_archive_and_cache(name, pack, pack_dir, md5) do - files = - ['pack.json'] ++ - (pack["files"] |> Enum.map(fn {_, path} -> to_charlist(path) end)) - - {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) - - cache_ms = :timer.seconds(@cache_seconds_per_file * Enum.count(files)) - - Cachex.put!( - :emoji_packs_cache, - name, - # if pack.json MD5 changes, the cache is not valid anymore - %{pack_json_md5: md5, pack_data: zip_result}, - # Add a minute to cache time for every file in the pack - ttl: cache_ms - ) - - Logger.debug("Created an archive for the '#{name}' emoji pack, \ -keeping it in cache for #{div(cache_ms, 1000)}s") - - zip_result - end - - defp make_archive(name, pack, pack_dir) do - # Having a different pack.json md5 invalidates cache - pack_file_md5 = :crypto.hash(:md5, File.read!(Path.join(pack_dir, "pack.json"))) - - case Cachex.get!(:emoji_packs_cache, name) do - %{pack_file_md5: ^pack_file_md5, pack_data: zip_result} -> - Logger.debug("Using cache for the '#{name}' shared emoji pack") - zip_result - - _ -> - create_archive_and_cache(name, pack, pack_dir, pack_file_md5) - end - end - - @doc """ - An endpoint for other instances (via admin UI) or users (via browser) - to download packs that the instance shares. - """ - def download_shared(conn, %{"name" => name}) do - pack_dir = Path.join(@emoji_dir_path, name) - pack_file = Path.join(pack_dir, "pack.json") - - with {_, true} <- {:exists?, File.exists?(pack_file)}, - pack = Jason.decode!(File.read!(pack_file)), - {_, true} <- {:can_download?, can_download?(pack, pack_dir)} do - zip_result = make_archive(name, pack, pack_dir) - send_download(conn, {:binary, zip_result}, filename: "#{name}.zip") - else - {:can_download?, _} -> - conn - |> put_status(:forbidden) - |> json(%{ - error: "Pack #{name} cannot be downloaded from this instance, either pack sharing\ - was disabled for this pack or some files are missing" - }) - - {:exists?, _} -> - conn - |> put_status(:not_found) - |> json(%{error: "Pack #{name} does not exist"}) - end - end - - @doc """ - An admin endpoint to request downloading a pack named `pack_name` from the instance - `instance_address`. - - If the requested instance's admin chose to share the pack, it will be downloaded - from that instance, otherwise it will be downloaded from the fallback source, if there is one. - """ - def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do - shareable_packs_available = - "#{address}/nodeinfo/2.1.json" - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> Map.get("features") - |> Enum.member?("shareable_emoji_packs") - - if shareable_packs_available do - full_pack = - "#{address}/api/pleroma/emoji/packs/list" - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> Map.get(name) - - pack_info_res = - case full_pack["pack"] do - %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> - {:ok, - %{ - sha: sha, - uri: "#{address}/api/pleroma/emoji/packs/download_shared/#{name}" - }} - - %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> - {:ok, - %{ - sha: sha, - uri: src, - fallback: true - }} - - _ -> - {:error, - "The pack was not set as shared and there is no fallback src to download from"} - end - - with {:ok, %{sha: sha, uri: uri} = pinfo} <- pack_info_res, - %{body: emoji_archive} <- Tesla.get!(uri), - {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do - local_name = data["as"] || name - pack_dir = Path.join(@emoji_dir_path, local_name) - File.mkdir_p!(pack_dir) - - files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) - # Fallback cannot contain a pack.json file - files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files - - {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) - - # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 - # in it to depend on itself - if pinfo[:fallback] do - pack_file_path = Path.join(pack_dir, "pack.json") - - File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) - end - - json(conn, "ok") - else - {:error, e} -> - conn |> put_status(:internal_server_error) |> json(%{error: e}) - - {:checksum, _} -> - conn - |> put_status(:internal_server_error) - |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) - end - else - conn - |> put_status(:internal_server_error) - |> json(%{error: "The requested instance does not support sharing emoji packs"}) - end - end - - @doc """ - Creates an empty pack named `name` which then can be updated via the admin UI. - """ - def create(conn, %{"name" => name}) do - pack_dir = Path.join(@emoji_dir_path, name) - - if not File.exists?(pack_dir) do - File.mkdir_p!(pack_dir) - - pack_file_p = Path.join(pack_dir, "pack.json") - - File.write!( - pack_file_p, - Jason.encode!(%{pack: %{}, files: %{}}) - ) - - conn |> json("ok") - else - conn - |> put_status(:conflict) - |> json(%{error: "A pack named \"#{name}\" already exists"}) - end - end - - @doc """ - Deletes the pack `name` and all it's files. - """ - def delete(conn, %{"name" => name}) do - pack_dir = Path.join(@emoji_dir_path, name) - - case File.rm_rf(pack_dir) do - {:ok, _} -> - conn |> json("ok") - - {:error, _} -> - conn - |> put_status(:internal_server_error) - |> json(%{error: "Couldn't delete the pack #{name}"}) - end - end - - @doc """ - An endpoint to update `pack_names`'s metadata. - - `new_data` is the new metadata for the pack, that will replace the old metadata. - """ - def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do - pack_file_p = Path.join([@emoji_dir_path, name, "pack.json"]) - - full_pack = Jason.decode!(File.read!(pack_file_p)) - - # The new fallback-src is in the new data and it's not the same as it was in the old data - should_update_fb_sha = - not is_nil(new_data["fallback-src"]) and - new_data["fallback-src"] != full_pack["pack"]["fallback-src"] - - with {_, true} <- {:should_update?, should_update_fb_sha}, - %{body: pack_arch} <- Tesla.get!(new_data["fallback-src"]), - {:ok, flist} <- :zip.unzip(pack_arch, [:memory]), - {_, true} <- {:has_all_files?, has_all_files?(full_pack, flist)} do - fallback_sha = :crypto.hash(:sha256, pack_arch) |> Base.encode16() - - new_data = Map.put(new_data, "fallback-src-sha256", fallback_sha) - update_metadata_and_send(conn, full_pack, new_data, pack_file_p) - else - {:should_update?, _} -> - update_metadata_and_send(conn, full_pack, new_data, pack_file_p) - - {:has_all_files?, _} -> - conn - |> put_status(:bad_request) - |> json(%{error: "The fallback archive does not have all files specified in pack.json"}) - end - end - - # Check if all files from the pack.json are in the archive - defp has_all_files?(%{"files" => files}, flist) do - Enum.all?(files, fn {_, from_manifest} -> - Enum.find(flist, fn {from_archive, _} -> - to_string(from_archive) == from_manifest - end) - end) - end - - defp update_metadata_and_send(conn, full_pack, new_data, pack_file_p) do - full_pack = Map.put(full_pack, "pack", new_data) - File.write!(pack_file_p, Jason.encode!(full_pack, pretty: true)) - - # Send new data back with fallback sha filled - json(conn, new_data) - end - - defp get_filename(%{"filename" => filename}), do: filename - - defp get_filename(%{"file" => file}) do - case file do - %Plug.Upload{filename: filename} -> filename - url when is_binary(url) -> Path.basename(url) - end - end - - defp empty?(str), do: String.trim(str) == "" - - defp update_file_and_send(conn, updated_full_pack, pack_file_p) do - # Write the emoji pack file - File.write!(pack_file_p, Jason.encode!(updated_full_pack, pretty: true)) - - # Return the modified file list - json(conn, updated_full_pack["files"]) - end - - @doc """ - Updates a file in a pack. - - Updating can mean three things: - - - `add` adds an emoji named `shortcode` to the pack `pack_name`, - that means that the emoji file needs to be uploaded with the request - (thus requiring it to be a multipart request) and be named `file`. - There can also be an optional `filename` that will be the new emoji file name - (if it's not there, the name will be taken from the uploaded file). - - `update` changes emoji shortcode (from `shortcode` to `new_shortcode` or moves the file - (from the current filename to `new_filename`) - - `remove` removes the emoji named `shortcode` and it's associated file - """ - - # Add - def update_file( - conn, - %{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params - ) do - pack_dir = Path.join(@emoji_dir_path, pack_name) - pack_file_p = Path.join(pack_dir, "pack.json") - - full_pack = Jason.decode!(File.read!(pack_file_p)) - - with {_, false} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, - filename <- get_filename(params), - false <- empty?(shortcode), - false <- empty?(filename) do - file_path = Path.join(pack_dir, filename) - - # If the name contains directories, create them - if String.contains?(file_path, "/") do - File.mkdir_p!(Path.dirname(file_path)) - end - - case params["file"] do - %Plug.Upload{path: upload_path} -> - # Copy the uploaded file from the temporary directory - File.copy!(upload_path, file_path) - - url when is_binary(url) -> - # Download and write the file - file_contents = Tesla.get!(url).body - File.write!(file_path, file_contents) - end - - updated_full_pack = put_in(full_pack, ["files", shortcode], filename) - update_file_and_send(conn, updated_full_pack, pack_file_p) - else - {:has_shortcode, _} -> - conn - |> put_status(:conflict) - |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) - - true -> - conn - |> put_status(:bad_request) - |> json(%{error: "shortcode or filename cannot be empty"}) - end - end - - # Remove - def update_file(conn, %{ - "pack_name" => pack_name, - "action" => "remove", - "shortcode" => shortcode - }) do - pack_dir = Path.join(@emoji_dir_path, pack_name) - pack_file_p = Path.join(pack_dir, "pack.json") - - full_pack = Jason.decode!(File.read!(pack_file_p)) - - if Map.has_key?(full_pack["files"], shortcode) do - {emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) - - emoji_file_path = Path.join(pack_dir, emoji_file_path) - - # Delete the emoji file - File.rm!(emoji_file_path) - - # If the old directory has no more files, remove it - if String.contains?(emoji_file_path, "/") do - dir = Path.dirname(emoji_file_path) - - if Enum.empty?(File.ls!(dir)) do - File.rmdir!(dir) - end - end - - update_file_and_send(conn, updated_full_pack, pack_file_p) - else - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - end - end - - # Update - def update_file( - conn, - %{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params - ) do - pack_dir = Path.join(@emoji_dir_path, pack_name) - pack_file_p = Path.join(pack_dir, "pack.json") - - full_pack = Jason.decode!(File.read!(pack_file_p)) - - with {_, true} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, - %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params, - false <- empty?(new_shortcode), - false <- empty?(new_filename) do - # First, remove the old shortcode, saving the old path - {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) - old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) - new_emoji_file_path = Path.join(pack_dir, new_filename) - - # If the name contains directories, create them - if String.contains?(new_emoji_file_path, "/") do - File.mkdir_p!(Path.dirname(new_emoji_file_path)) - end - - # Move/Rename the old filename to a new filename - # These are probably on the same filesystem, so just rename should work - :ok = File.rename(old_emoji_file_path, new_emoji_file_path) - - # If the old directory has no more files, remove it - if String.contains?(old_emoji_file_path, "/") do - dir = Path.dirname(old_emoji_file_path) - - if Enum.empty?(File.ls!(dir)) do - File.rmdir!(dir) - end - end - - # Then, put in the new shortcode with the new path - updated_full_pack = put_in(updated_full_pack, ["files", new_shortcode], new_filename) - update_file_and_send(conn, updated_full_pack, pack_file_p) - else - {:has_shortcode, _} -> - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - - true -> - conn - |> put_status(:bad_request) - |> json(%{error: "new_shortcode or new_filename cannot be empty"}) - - _ -> - conn - |> put_status(:bad_request) - |> json(%{error: "new_shortcode or new_file were not specified"}) - end - end - - def update_file(conn, %{"action" => action}) do - conn - |> put_status(:bad_request) - |> json(%{error: "Unknown action: #{action}"}) - end - - @doc """ - Imports emoji from the filesystem. - - Importing means checking all the directories in the - `$instance_static/emoji/` for directories which do not have - `pack.json`. If one has an emoji.txt file, that file will be used - to create a `pack.json` file with it's contents. If the directory has - neither, all the files with specific configured extenstions will be - assumed to be emojis and stored in the new `pack.json` file. - """ - def import_from_fs(conn, _params) do - with {:ok, results} <- File.ls(@emoji_dir_path) do - imported_pack_names = - results - |> Enum.filter(fn file -> - dir_path = Path.join(@emoji_dir_path, file) - # Find the directories that do NOT have pack.json - File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) - end) - |> Enum.map(&write_pack_json_contents/1) - - json(conn, imported_pack_names) - else - {:error, _} -> - conn - |> put_status(:internal_server_error) - |> json(%{error: "Error accessing emoji pack directory"}) - end - end - - defp write_pack_json_contents(dir) do - dir_path = Path.join(@emoji_dir_path, dir) - emoji_txt_path = Path.join(dir_path, "emoji.txt") - - files_for_pack = files_for_pack(emoji_txt_path, dir_path) - pack_json_contents = Jason.encode!(%{pack: %{}, files: files_for_pack}) - - File.write!(Path.join(dir_path, "pack.json"), pack_json_contents) - - dir - end - - defp files_for_pack(emoji_txt_path, dir_path) do - if File.exists?(emoji_txt_path) do - # There's an emoji.txt file, it's likely from a pack installed by the pack manager. - # Make a pack.json file from the contents of that emoji.txt fileh - - # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 - - # Create a map of shortcodes to filenames from emoji.txt - File.read!(emoji_txt_path) - |> String.split("\n") - |> Enum.map(&String.trim/1) - |> Enum.map(fn line -> - case String.split(line, ~r/,\s*/) do - # This matches both strings with and without tags - # and we don't care about tags here - [name, file | _] -> {name, file} - _ -> nil - end - end) - |> Enum.filter(fn x -> not is_nil(x) end) - |> Enum.into(%{}) - else - # If there's no emoji.txt, assume all files - # that are of certain extensions from the config are emojis and import them all - pack_extensions = Pleroma.Config.get!([:emoji, :pack_extensions]) - Pleroma.Emoji.make_shortcode_to_file_map(dir_path, pack_extensions) - end - end -end diff --git a/lib/pleroma/web/pleroma_api/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/emoji_api_controller.ex new file mode 100644 index 000000000..36ca2c804 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/emoji_api_controller.ex @@ -0,0 +1,575 @@ +defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do + use Pleroma.Web, :controller + + require Logger + + def reload(conn, _params) do + Pleroma.Emoji.reload() + + conn |> json("ok") + end + + @emoji_dir_path Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + + @cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) + + @doc """ + Lists the packs available on the instance as JSON. + + The information is public and does not require authentification. The format is + a map of "pack directory name" to pack.json contents. + """ + def list_packs(conn, _params) do + with {:ok, results} <- File.ls(@emoji_dir_path) do + pack_infos = + results + |> Enum.filter(&has_pack_json?/1) + |> Enum.map(&load_pack/1) + # Check if all the files are in place and can be sent + |> Enum.map(&validate_pack/1) + # Transform into a map of pack-name => pack-data + |> Enum.into(%{}) + + json(conn, pack_infos) + end + end + + defp has_pack_json?(file) do + dir_path = Path.join(@emoji_dir_path, file) + # Filter to only use the pack.json packs + File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json")) + end + + defp load_pack(pack_name) do + pack_path = Path.join(@emoji_dir_path, pack_name) + pack_file = Path.join(pack_path, "pack.json") + + {pack_name, Jason.decode!(File.read!(pack_file))} + end + + defp validate_pack({name, pack}) do + pack_path = Path.join(@emoji_dir_path, name) + + if can_download?(pack, pack_path) do + archive_for_sha = make_archive(name, pack, pack_path) + archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() + + pack = + pack + |> put_in(["pack", "can-download"], true) + |> put_in(["pack", "download-sha256"], archive_sha) + + {name, pack} + else + {name, put_in(pack, ["pack", "can-download"], false)} + end + end + + defp can_download?(pack, pack_path) do + # If the pack is set as shared, check if it can be downloaded + # That means that when asked, the pack can be packed and sent to the remote + # Otherwise, they'd have to download it from external-src + pack["pack"]["share-files"] && + Enum.all?(pack["files"], fn {_, path} -> + File.exists?(Path.join(pack_path, path)) + end) + end + + defp create_archive_and_cache(name, pack, pack_dir, md5) do + files = + ['pack.json'] ++ + (pack["files"] |> Enum.map(fn {_, path} -> to_charlist(path) end)) + + {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) + + cache_ms = :timer.seconds(@cache_seconds_per_file * Enum.count(files)) + + Cachex.put!( + :emoji_packs_cache, + name, + # if pack.json MD5 changes, the cache is not valid anymore + %{pack_json_md5: md5, pack_data: zip_result}, + # Add a minute to cache time for every file in the pack + ttl: cache_ms + ) + + Logger.debug("Created an archive for the '#{name}' emoji pack, \ +keeping it in cache for #{div(cache_ms, 1000)}s") + + zip_result + end + + defp make_archive(name, pack, pack_dir) do + # Having a different pack.json md5 invalidates cache + pack_file_md5 = :crypto.hash(:md5, File.read!(Path.join(pack_dir, "pack.json"))) + + case Cachex.get!(:emoji_packs_cache, name) do + %{pack_file_md5: ^pack_file_md5, pack_data: zip_result} -> + Logger.debug("Using cache for the '#{name}' shared emoji pack") + zip_result + + _ -> + create_archive_and_cache(name, pack, pack_dir, pack_file_md5) + end + end + + @doc """ + An endpoint for other instances (via admin UI) or users (via browser) + to download packs that the instance shares. + """ + def download_shared(conn, %{"name" => name}) do + pack_dir = Path.join(@emoji_dir_path, name) + pack_file = Path.join(pack_dir, "pack.json") + + with {_, true} <- {:exists?, File.exists?(pack_file)}, + pack = Jason.decode!(File.read!(pack_file)), + {_, true} <- {:can_download?, can_download?(pack, pack_dir)} do + zip_result = make_archive(name, pack, pack_dir) + send_download(conn, {:binary, zip_result}, filename: "#{name}.zip") + else + {:can_download?, _} -> + conn + |> put_status(:forbidden) + |> json(%{ + error: "Pack #{name} cannot be downloaded from this instance, either pack sharing\ + was disabled for this pack or some files are missing" + }) + + {:exists?, _} -> + conn + |> put_status(:not_found) + |> json(%{error: "Pack #{name} does not exist"}) + end + end + + @doc """ + An admin endpoint to request downloading a pack named `pack_name` from the instance + `instance_address`. + + If the requested instance's admin chose to share the pack, it will be downloaded + from that instance, otherwise it will be downloaded from the fallback source, if there is one. + """ + def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do + shareable_packs_available = + "#{address}/nodeinfo/2.1.json" + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> Map.get("features") + |> Enum.member?("shareable_emoji_packs") + + if shareable_packs_available do + full_pack = + "#{address}/api/pleroma/emoji/packs/list" + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> Map.get(name) + + pack_info_res = + case full_pack["pack"] do + %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> + {:ok, + %{ + sha: sha, + uri: "#{address}/api/pleroma/emoji/packs/download_shared/#{name}" + }} + + %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> + {:ok, + %{ + sha: sha, + uri: src, + fallback: true + }} + + _ -> + {:error, + "The pack was not set as shared and there is no fallback src to download from"} + end + + with {:ok, %{sha: sha, uri: uri} = pinfo} <- pack_info_res, + %{body: emoji_archive} <- Tesla.get!(uri), + {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do + local_name = data["as"] || name + pack_dir = Path.join(@emoji_dir_path, local_name) + File.mkdir_p!(pack_dir) + + files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) + # Fallback cannot contain a pack.json file + files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files + + {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) + + # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 + # in it to depend on itself + if pinfo[:fallback] do + pack_file_path = Path.join(pack_dir, "pack.json") + + File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) + end + + json(conn, "ok") + else + {:error, e} -> + conn |> put_status(:internal_server_error) |> json(%{error: e}) + + {:checksum, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) + end + else + conn + |> put_status(:internal_server_error) + |> json(%{error: "The requested instance does not support sharing emoji packs"}) + end + end + + @doc """ + Creates an empty pack named `name` which then can be updated via the admin UI. + """ + def create(conn, %{"name" => name}) do + pack_dir = Path.join(@emoji_dir_path, name) + + if not File.exists?(pack_dir) do + File.mkdir_p!(pack_dir) + + pack_file_p = Path.join(pack_dir, "pack.json") + + File.write!( + pack_file_p, + Jason.encode!(%{pack: %{}, files: %{}}) + ) + + conn |> json("ok") + else + conn + |> put_status(:conflict) + |> json(%{error: "A pack named \"#{name}\" already exists"}) + end + end + + @doc """ + Deletes the pack `name` and all it's files. + """ + def delete(conn, %{"name" => name}) do + pack_dir = Path.join(@emoji_dir_path, name) + + case File.rm_rf(pack_dir) do + {:ok, _} -> + conn |> json("ok") + + {:error, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Couldn't delete the pack #{name}"}) + end + end + + @doc """ + An endpoint to update `pack_names`'s metadata. + + `new_data` is the new metadata for the pack, that will replace the old metadata. + """ + def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do + pack_file_p = Path.join([@emoji_dir_path, name, "pack.json"]) + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + # The new fallback-src is in the new data and it's not the same as it was in the old data + should_update_fb_sha = + not is_nil(new_data["fallback-src"]) and + new_data["fallback-src"] != full_pack["pack"]["fallback-src"] + + with {_, true} <- {:should_update?, should_update_fb_sha}, + %{body: pack_arch} <- Tesla.get!(new_data["fallback-src"]), + {:ok, flist} <- :zip.unzip(pack_arch, [:memory]), + {_, true} <- {:has_all_files?, has_all_files?(full_pack, flist)} do + fallback_sha = :crypto.hash(:sha256, pack_arch) |> Base.encode16() + + new_data = Map.put(new_data, "fallback-src-sha256", fallback_sha) + update_metadata_and_send(conn, full_pack, new_data, pack_file_p) + else + {:should_update?, _} -> + update_metadata_and_send(conn, full_pack, new_data, pack_file_p) + + {:has_all_files?, _} -> + conn + |> put_status(:bad_request) + |> json(%{error: "The fallback archive does not have all files specified in pack.json"}) + end + end + + # Check if all files from the pack.json are in the archive + defp has_all_files?(%{"files" => files}, flist) do + Enum.all?(files, fn {_, from_manifest} -> + Enum.find(flist, fn {from_archive, _} -> + to_string(from_archive) == from_manifest + end) + end) + end + + defp update_metadata_and_send(conn, full_pack, new_data, pack_file_p) do + full_pack = Map.put(full_pack, "pack", new_data) + File.write!(pack_file_p, Jason.encode!(full_pack, pretty: true)) + + # Send new data back with fallback sha filled + json(conn, new_data) + end + + defp get_filename(%{"filename" => filename}), do: filename + + defp get_filename(%{"file" => file}) do + case file do + %Plug.Upload{filename: filename} -> filename + url when is_binary(url) -> Path.basename(url) + end + end + + defp empty?(str), do: String.trim(str) == "" + + defp update_file_and_send(conn, updated_full_pack, pack_file_p) do + # Write the emoji pack file + File.write!(pack_file_p, Jason.encode!(updated_full_pack, pretty: true)) + + # Return the modified file list + json(conn, updated_full_pack["files"]) + end + + @doc """ + Updates a file in a pack. + + Updating can mean three things: + + - `add` adds an emoji named `shortcode` to the pack `pack_name`, + that means that the emoji file needs to be uploaded with the request + (thus requiring it to be a multipart request) and be named `file`. + There can also be an optional `filename` that will be the new emoji file name + (if it's not there, the name will be taken from the uploaded file). + - `update` changes emoji shortcode (from `shortcode` to `new_shortcode` or moves the file + (from the current filename to `new_filename`) + - `remove` removes the emoji named `shortcode` and it's associated file + """ + + # Add + def update_file( + conn, + %{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params + ) do + pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + with {_, false} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, + filename <- get_filename(params), + false <- empty?(shortcode), + false <- empty?(filename) do + file_path = Path.join(pack_dir, filename) + + # If the name contains directories, create them + if String.contains?(file_path, "/") do + File.mkdir_p!(Path.dirname(file_path)) + end + + case params["file"] do + %Plug.Upload{path: upload_path} -> + # Copy the uploaded file from the temporary directory + File.copy!(upload_path, file_path) + + url when is_binary(url) -> + # Download and write the file + file_contents = Tesla.get!(url).body + File.write!(file_path, file_contents) + end + + updated_full_pack = put_in(full_pack, ["files", shortcode], filename) + update_file_and_send(conn, updated_full_pack, pack_file_p) + else + {:has_shortcode, _} -> + conn + |> put_status(:conflict) + |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) + + true -> + conn + |> put_status(:bad_request) + |> json(%{error: "shortcode or filename cannot be empty"}) + end + end + + # Remove + def update_file(conn, %{ + "pack_name" => pack_name, + "action" => "remove", + "shortcode" => shortcode + }) do + pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + if Map.has_key?(full_pack["files"], shortcode) do + {emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) + + emoji_file_path = Path.join(pack_dir, emoji_file_path) + + # Delete the emoji file + File.rm!(emoji_file_path) + + # If the old directory has no more files, remove it + if String.contains?(emoji_file_path, "/") do + dir = Path.dirname(emoji_file_path) + + if Enum.empty?(File.ls!(dir)) do + File.rmdir!(dir) + end + end + + update_file_and_send(conn, updated_full_pack, pack_file_p) + else + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) + end + end + + # Update + def update_file( + conn, + %{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params + ) do + pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + with {_, true} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, + %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params, + false <- empty?(new_shortcode), + false <- empty?(new_filename) do + # First, remove the old shortcode, saving the old path + {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) + old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) + new_emoji_file_path = Path.join(pack_dir, new_filename) + + # If the name contains directories, create them + if String.contains?(new_emoji_file_path, "/") do + File.mkdir_p!(Path.dirname(new_emoji_file_path)) + end + + # Move/Rename the old filename to a new filename + # These are probably on the same filesystem, so just rename should work + :ok = File.rename(old_emoji_file_path, new_emoji_file_path) + + # If the old directory has no more files, remove it + if String.contains?(old_emoji_file_path, "/") do + dir = Path.dirname(old_emoji_file_path) + + if Enum.empty?(File.ls!(dir)) do + File.rmdir!(dir) + end + end + + # Then, put in the new shortcode with the new path + updated_full_pack = put_in(updated_full_pack, ["files", new_shortcode], new_filename) + update_file_and_send(conn, updated_full_pack, pack_file_p) + else + {:has_shortcode, _} -> + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) + + true -> + conn + |> put_status(:bad_request) + |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + + _ -> + conn + |> put_status(:bad_request) + |> json(%{error: "new_shortcode or new_file were not specified"}) + end + end + + def update_file(conn, %{"action" => action}) do + conn + |> put_status(:bad_request) + |> json(%{error: "Unknown action: #{action}"}) + end + + @doc """ + Imports emoji from the filesystem. + + Importing means checking all the directories in the + `$instance_static/emoji/` for directories which do not have + `pack.json`. If one has an emoji.txt file, that file will be used + to create a `pack.json` file with it's contents. If the directory has + neither, all the files with specific configured extenstions will be + assumed to be emojis and stored in the new `pack.json` file. + """ + def import_from_fs(conn, _params) do + with {:ok, results} <- File.ls(@emoji_dir_path) do + imported_pack_names = + results + |> Enum.filter(fn file -> + dir_path = Path.join(@emoji_dir_path, file) + # Find the directories that do NOT have pack.json + File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) + end) + |> Enum.map(&write_pack_json_contents/1) + + json(conn, imported_pack_names) + else + {:error, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Error accessing emoji pack directory"}) + end + end + + defp write_pack_json_contents(dir) do + dir_path = Path.join(@emoji_dir_path, dir) + emoji_txt_path = Path.join(dir_path, "emoji.txt") + + files_for_pack = files_for_pack(emoji_txt_path, dir_path) + pack_json_contents = Jason.encode!(%{pack: %{}, files: files_for_pack}) + + File.write!(Path.join(dir_path, "pack.json"), pack_json_contents) + + dir + end + + defp files_for_pack(emoji_txt_path, dir_path) do + if File.exists?(emoji_txt_path) do + # There's an emoji.txt file, it's likely from a pack installed by the pack manager. + # Make a pack.json file from the contents of that emoji.txt fileh + + # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 + + # Create a map of shortcodes to filenames from emoji.txt + File.read!(emoji_txt_path) + |> String.split("\n") + |> Enum.map(&String.trim/1) + |> Enum.map(fn line -> + case String.split(line, ~r/,\s*/) do + # This matches both strings with and without tags + # and we don't care about tags here + [name, file | _] -> {name, file} + _ -> nil + end + end) + |> Enum.filter(fn x -> not is_nil(x) end) + |> Enum.into(%{}) + else + # If there's no emoji.txt, assume all files + # that are of certain extensions from the config are emojis and import them all + pack_extensions = Pleroma.Config.get!([:emoji, :pack_extensions]) + Pleroma.Emoji.make_shortcode_to_file_map(dir_path, pack_extensions) + end + end +end -- cgit v1.2.3 From d51e5e447ee944e77646b15a7aabc0214e99c351 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Thu, 12 Sep 2019 20:38:57 +0300 Subject: Move emoji reloading to admin api --- lib/pleroma/web/admin_api/admin_api_controller.ex | 6 ++++++ lib/pleroma/web/pleroma_api/emoji_api_controller.ex | 6 ------ lib/pleroma/web/router.ex | 8 ++------ 3 files changed, 8 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 8a8091daa..4d4e862dd 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -599,6 +599,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> render("index.json", %{configs: updated}) end + def reload_emoji(conn, _params) do + Pleroma.Emoji.reload() + + conn |> json("ok") + end + def errors(conn, {:error, :not_found}) do conn |> put_status(:not_found) diff --git a/lib/pleroma/web/pleroma_api/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/emoji_api_controller.ex index 36ca2c804..bc1639095 100644 --- a/lib/pleroma/web/pleroma_api/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/emoji_api_controller.ex @@ -3,12 +3,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do require Logger - def reload(conn, _params) do - Pleroma.Emoji.reload() - - conn |> json("ok") - end - @emoji_dir_path Path.join( Pleroma.Config.get!([:instance, :static_dir]), "emoji" diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 715e4ba68..71ef382c5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -205,15 +205,11 @@ defmodule Pleroma.Web.Router do get("/config/migrate_from_db", AdminAPIController, :migrate_from_db) get("/moderation_log", AdminAPIController, :list_log) + + post("/reload_emoji", AdminAPIController, :reload_emoji) end scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do - scope [] do - pipe_through([:admin_api, :oauth_write]) - - post("/reload", EmojiAPIController, :reload) - end - scope "/packs" do # Modifying packs pipe_through([:admin_api, :oauth_write]) -- cgit v1.2.3 From a1325d5fd9b540017cbffbb73db85ee9fa9f12d0 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 18 Sep 2019 18:09:57 +0300 Subject: Change path from nodeinfo to metadata->features --- lib/pleroma/web/pleroma_api/emoji_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/emoji_api_controller.ex index bc1639095..391c317e7 100644 --- a/lib/pleroma/web/pleroma_api/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/emoji_api_controller.ex @@ -152,7 +152,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") |> Tesla.get!() |> Map.get(:body) |> Jason.decode!() - |> Map.get("features") + |> get_in(["metadata", "features"]) |> Enum.member?("shareable_emoji_packs") if shareable_packs_available do -- cgit v1.2.3 From b585134c9092b49e7b5c24e04d6d6315d45dd0a2 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 18 Sep 2019 19:48:25 +0300 Subject: Get the nodeinfo address from the well-known --- lib/pleroma/web/pleroma_api/emoji_api_controller.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/emoji_api_controller.ex index 391c317e7..6beca426a 100644 --- a/lib/pleroma/web/pleroma_api/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/emoji_api_controller.ex @@ -148,7 +148,13 @@ keeping it in cache for #{div(cache_ms, 1000)}s") """ def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do shareable_packs_available = - "#{address}/nodeinfo/2.1.json" + "#{address}/.well-known/nodeinfo" + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> List.last() + |> Map.get("href") + # Get the actual nodeinfo address and fetch it |> Tesla.get!() |> Map.get(:body) |> Jason.decode!() -- cgit v1.2.3 From 447514dfa2759e3415399412e82bf772ff119e04 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 18 Sep 2019 23:20:54 +0200 Subject: Bump copyright years of files changed in 2019 Done via the following command: git diff 1e6c102bfcfe0e4835a48f2483f2376f9bf86a20 --stat --name-only | cat - | xargs sed -i 's/2017-2018 Pleroma Authors/2017-2019 Pleroma Authors/' --- lib/mix/pleroma.ex | 2 +- lib/mix/tasks/pleroma/database.ex | 2 +- lib/mix/tasks/pleroma/ecto/ecto.ex | 2 +- lib/mix/tasks/pleroma/ecto/migrate.ex | 2 +- lib/mix/tasks/pleroma/ecto/rollback.ex | 2 +- lib/mix/tasks/pleroma/emoji.ex | 2 +- lib/mix/tasks/pleroma/instance.ex | 2 +- lib/mix/tasks/pleroma/relay.ex | 2 +- lib/mix/tasks/pleroma/uploads.ex | 2 +- lib/mix/tasks/pleroma/user.ex | 2 +- lib/pleroma/activity/queries.ex | 2 +- lib/pleroma/user/query.ex | 2 +- lib/pleroma/web/oauth/token/clean_worker.ex | 2 +- lib/pleroma/web/oauth/token/query.ex | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 1b758ea33..faeb30e1d 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Pleroma do diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index bcc2052d6..890a383df 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.Database do diff --git a/lib/mix/tasks/pleroma/ecto/ecto.ex b/lib/mix/tasks/pleroma/ecto/ecto.ex index b66f63376..36808b93f 100644 --- a/lib/mix/tasks/pleroma/ecto/ecto.ex +++ b/lib/mix/tasks/pleroma/ecto/ecto.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-onl defmodule Mix.Tasks.Pleroma.Ecto do diff --git a/lib/mix/tasks/pleroma/ecto/migrate.ex b/lib/mix/tasks/pleroma/ecto/migrate.ex index 855c977f6..d87b6957d 100644 --- a/lib/mix/tasks/pleroma/ecto/migrate.ex +++ b/lib/mix/tasks/pleroma/ecto/migrate.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-onl defmodule Mix.Tasks.Pleroma.Ecto.Migrate do diff --git a/lib/mix/tasks/pleroma/ecto/rollback.ex b/lib/mix/tasks/pleroma/ecto/rollback.ex index 2ffb0901c..a1af73fa1 100644 --- a/lib/mix/tasks/pleroma/ecto/rollback.ex +++ b/lib/mix/tasks/pleroma/ecto/rollback.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-onl defmodule Mix.Tasks.Pleroma.Ecto.Rollback do diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index c2225af7d..238d8dcd9 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.Emoji do diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index b9b1991c2..1a1634fe9 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.Instance do diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex index a738fae75..200721163 100644 --- a/lib/mix/tasks/pleroma/relay.ex +++ b/lib/mix/tasks/pleroma/relay.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.Relay do diff --git a/lib/mix/tasks/pleroma/uploads.ex b/lib/mix/tasks/pleroma/uploads.ex index be45383ee..95392d81b 100644 --- a/lib/mix/tasks/pleroma/uploads.ex +++ b/lib/mix/tasks/pleroma/uploads.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.Uploads do diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index a3f8bc945..eb0052144 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.User do diff --git a/lib/pleroma/activity/queries.ex b/lib/pleroma/activity/queries.ex index 13fa33831..949f010a8 100644 --- a/lib/pleroma/activity/queries.ex +++ b/lib/pleroma/activity/queries.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Activity.Queries do diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index f9bcc9e19..2baf016cf 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.User.Query do diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex index eb94bf86f..f639f9c6f 100644 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Token.CleanWorker do diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex index d92e1f071..9642103e6 100644 --- a/lib/pleroma/web/oauth/token/query.ex +++ b/lib/pleroma/web/oauth/token/query.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors +# Copyright © 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Token.Query do -- cgit v1.2.3 From fe5e0b784604b1352e98e7915c3c67d59ac4f709 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Thu, 19 Sep 2019 08:27:55 +0300 Subject: Mastodon API: Return `pleroma.direct_conversation_id` when creating direct messages (`POST /api/v1/statuses`) --- .../web/mastodon_api/controllers/mastodon_api_controller.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 37eeb2ac3..6704ee7e8 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -611,7 +611,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do {:ok, activity} -> conn |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) + |> try_render("status.json", %{ + activity: activity, + for: user, + as: :activity, + with_direct_conversation_id: true + }) end end end -- cgit v1.2.3 From cf3041220a7a14dc3fac24177fac1f4aecc77f5f Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 17 Sep 2019 15:22:46 +0700 Subject: Add support for `rel="ugc"` --- lib/pleroma/html.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 3951f0f51..937bafed5 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -184,7 +184,8 @@ defmodule Pleroma.HTML.Scrubber.Default do "tag", "nofollow", "noopener", - "noreferrer" + "noreferrer", + "ugc" ]) Meta.allow_tag_with_these_attributes("a", ["name", "title"]) @@ -304,7 +305,8 @@ defmodule Pleroma.HTML.Scrubber.LinksOnly do "nofollow", "noopener", "noreferrer", - "me" + "me", + "ugc" ]) Meta.allow_tag_with_these_attributes("a", ["name", "title"]) -- cgit v1.2.3 From 95c948110ca130559fd6a5302011aa58900274ac Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 19 Sep 2019 14:39:52 +0700 Subject: Add `rel="ugc"` to hashtags and mentions --- lib/pleroma/formatter.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 607843a5b..23a5ac8fe 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -36,9 +36,9 @@ defmodule Pleroma.Formatter do nickname_text = get_nickname_text(nickname, opts) link = - "@#{ + ~s(@#{ nickname_text - }" + }) {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}} @@ -50,7 +50,7 @@ defmodule Pleroma.Formatter do def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do tag = String.downcase(tag) url = "#{Pleroma.Web.base_url()}/tag/#{tag}" - link = "" + link = ~s(#{tag_text}) {link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}} end -- cgit v1.2.3 From 0e6085da106cb966c340fac2d307d9e8e26e91ed Mon Sep 17 00:00:00 2001 From: D Anzorge Date: Thu, 19 Sep 2019 16:09:07 +0200 Subject: Fix pagination in AP outbox.json --- lib/pleroma/web/activity_pub/views/user_view.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 164b973d0..a2f73e140 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -227,11 +227,12 @@ defmodule Pleroma.Web.ActivityPub.UserView do activities = ActivityPub.fetch_user_activities(user, nil, params) + # this is sorted chronologically, so first activity is the newest (max) {max_id, min_id, collection} = if length(activities) > 0 do { - Enum.at(Enum.reverse(activities), 0).id, Enum.at(activities, 0).id, + Enum.at(Enum.reverse(activities), 0).id, Enum.map(activities, fn act -> {:ok, data} = Transmogrifier.prepare_outgoing(act.data) data -- cgit v1.2.3 From 7cf125245512eb49a118535eda52ddbdd0c4c6bf Mon Sep 17 00:00:00 2001 From: eugenijm Date: Fri, 20 Sep 2019 17:54:38 +0300 Subject: Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`) --- lib/pleroma/web/activity_pub/activity_pub.ex | 5 +++-- lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index e1e90d667..1cf8b6151 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -520,9 +520,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def fetch_public_activities(opts \\ %{}) do - q = fetch_activities_query([Pleroma.Constants.as_public()], opts) + opts = Map.drop(opts, ["user"]) - q + [Pleroma.Constants.as_public()] + |> fetch_activities_query(opts) |> restrict_unlisted() |> Pagination.fetch_paginated(opts) |> Enum.reverse() diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 6704ee7e8..6421c2c53 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -381,7 +381,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Map.put("local_only", local_only) |> Map.put("blocking_user", user) |> Map.put("muting_user", user) - |> Map.put("user", user) |> ActivityPub.fetch_public_activities() |> Enum.reverse() -- cgit v1.2.3 From 6f25668215f7f9fe20bfaf3dd72e2262a6d8915e Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Sun, 22 Sep 2019 16:08:07 +0300 Subject: Admin API: Add ability to force user's password reset --- lib/pleroma/user.ex | 17 +++++++++++++++++ lib/pleroma/user/info.ex | 13 ++++++++++--- lib/pleroma/web/admin_api/admin_api_controller.ex | 9 +++++++++ lib/pleroma/web/oauth/oauth_controller.ex | 5 +++++ lib/pleroma/web/router.ex | 1 + lib/pleroma/workers/background_worker.ex | 5 +++++ 6 files changed, 47 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index fb1f24254..ab253a274 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -269,6 +269,7 @@ defmodule Pleroma.User do |> validate_required([:password, :password_confirmation]) |> validate_confirmation(:password) |> put_password_hash + |> put_embed(:info, User.Info.set_password_reset_pending(struct.info, false)) end @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} @@ -285,6 +286,20 @@ defmodule Pleroma.User do end end + def force_password_reset_async(user) do + BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id}) + end + + @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} + def force_password_reset(user) do + info_cng = User.Info.set_password_reset_pending(user.info, true) + + user + |> change() + |> put_embed(:info, info_cng) + |> update_and_set_cache() + end + def register_changeset(struct, params \\ %{}, opts \\ []) do bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) @@ -1115,6 +1130,8 @@ defmodule Pleroma.User do BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end + def perform(:force_password_reset, user), do: force_password_reset(user) + @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:delete, %User{} = user) do {:ok, _user} = ActivityPub.delete(user) diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index b150a57cd..67abc3ecd 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -20,6 +20,7 @@ defmodule Pleroma.User.Info do field(:following_count, :integer, default: nil) field(:locked, :boolean, default: false) field(:confirmation_pending, :boolean, default: false) + field(:password_reset_pending, :boolean, default: false) field(:confirmation_token, :string, default: nil) field(:default_scope, :string, default: "public") field(:blocks, {:array, :string}, default: []) @@ -82,6 +83,14 @@ defmodule Pleroma.User.Info do |> validate_required([:deactivated]) end + def set_password_reset_pending(info, pending) do + params = %{password_reset_pending: pending} + + info + |> cast(params, [:password_reset_pending]) + |> validate_required([:password_reset_pending]) + end + def update_notification_settings(info, settings) do settings = settings @@ -333,9 +342,7 @@ defmodule Pleroma.User.Info do name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255) value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255) - is_binary(name) && - is_binary(value) && - String.length(name) <= name_limit && + is_binary(name) && is_binary(value) && String.length(name) <= name_limit && String.length(value) <= value_limit end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 8a8091daa..711e4dfc2 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -447,6 +447,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> json(token.token) end + @doc "Force password reset for a given user" + def force_password_reset(conn, %{"nickname" => nickname}) do + (%User{local: true} = user) = User.get_cached_by_nickname(nickname) + + User.force_password_reset_async(user) + + json_response(conn, :no_content, "") + end + def list_reports(conn, params) do params = params diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 81eae2c8b..a57670e02 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -202,6 +202,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:ok, app} <- Token.Utils.fetch_app(conn), {:auth_active, true} <- {:auth_active, User.auth_active?(user)}, {:user_active, true} <- {:user_active, !user.info.deactivated}, + {:password_reset_pending, false} <- + {:password_reset_pending, user.info.password_reset_pending}, {:ok, scopes} <- validate_scopes(app, params), {:ok, auth} <- Authorization.create_authorization(app, user, scopes), {:ok, token} <- Token.exchange_token(app, auth) do @@ -215,6 +217,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:user_active, false} -> render_error(conn, :forbidden, "Your account is currently disabled") + {:password_reset_pending, true} -> + render_error(conn, :forbidden, "Password reset is required") + _error -> render_invalid_credentials_error(conn) end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index b9b85fd67..a306c1b80 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -186,6 +186,7 @@ defmodule Pleroma.Web.Router do post("/users/email_invite", AdminAPIController, :email_invite) get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset) + patch("/users/:nickname/force_password_reset", AdminAPIController, :force_password_reset) get("/users", AdminAPIController, :list_users) get("/users/:nickname", AdminAPIController, :user_show) diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 082f20ab7..7ffc8eabe 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -26,6 +26,11 @@ defmodule Pleroma.Workers.BackgroundWorker do User.perform(:delete, user) end + def perform(%{"op" => "force_password_reset", "user_id" => user_id}, _job) do + user = User.get_cached_by_id(user_id) + User.perform(:force_password_reset, user) + end + def perform( %{ "op" => "blocks_import", -- cgit v1.2.3 From 72a01f1350239d286978007883a087f8f3985d1b Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Sun, 22 Sep 2019 16:36:59 +0300 Subject: Use router helper to generate reset password link --- lib/pleroma/web/admin_api/admin_api_controller.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 54ab6e032..b2df1e5b8 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -17,7 +17,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.AdminAPI.ReportView alias Pleroma.Web.AdminAPI.Search alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Endpoint alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Router import Pleroma.Web.ControllerHelper, only: [json_response: 3] @@ -432,13 +434,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def get_password_reset(conn, %{"nickname" => nickname}) do (%User{local: true} = user) = User.get_cached_by_nickname(nickname) {:ok, token} = Pleroma.PasswordResetToken.create_token(user) - host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) - protocol = Pleroma.Config.get([Pleroma.Web.Endpoint, :protocol]) conn |> json(%{ token: token.token, - link: "#{protocol}://#{host}/api/pleroma/password_reset/#{token.token}" + link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token) }) end -- cgit v1.2.3 From d72d4757a8e66c29d58e0a3b7fb36356ae419a54 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Sun, 22 Sep 2019 23:13:48 +0300 Subject: Format --- lib/pleroma/user/info.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 67abc3ecd..99745f496 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -342,7 +342,9 @@ defmodule Pleroma.User.Info do name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255) value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255) - is_binary(name) && is_binary(value) && String.length(name) <= name_limit && + is_binary(name) && + is_binary(value) && + String.length(name) <= name_limit && String.length(value) <= value_limit end -- cgit v1.2.3 From 6b3d5ed6db6a3c73eb1f8373ebd670427aa8849d Mon Sep 17 00:00:00 2001 From: rinpatch Date: Mon, 23 Sep 2019 21:14:51 +0300 Subject: Emoji API Controller: Follow phoenix directory structure --- .../controllers/emoji_api_controller.ex | 575 +++++++++++++++++++++ .../controllers/pleroma_api_controller.ex | 89 ++++ .../web/pleroma_api/emoji_api_controller.ex | 575 --------------------- .../web/pleroma_api/pleroma_api_controller.ex | 89 ---- 4 files changed, 664 insertions(+), 664 deletions(-) create mode 100644 lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex create mode 100644 lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex delete mode 100644 lib/pleroma/web/pleroma_api/emoji_api_controller.ex delete mode 100644 lib/pleroma/web/pleroma_api/pleroma_api_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex new file mode 100644 index 000000000..6beca426a --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -0,0 +1,575 @@ +defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do + use Pleroma.Web, :controller + + require Logger + + @emoji_dir_path Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + + @cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) + + @doc """ + Lists the packs available on the instance as JSON. + + The information is public and does not require authentification. The format is + a map of "pack directory name" to pack.json contents. + """ + def list_packs(conn, _params) do + with {:ok, results} <- File.ls(@emoji_dir_path) do + pack_infos = + results + |> Enum.filter(&has_pack_json?/1) + |> Enum.map(&load_pack/1) + # Check if all the files are in place and can be sent + |> Enum.map(&validate_pack/1) + # Transform into a map of pack-name => pack-data + |> Enum.into(%{}) + + json(conn, pack_infos) + end + end + + defp has_pack_json?(file) do + dir_path = Path.join(@emoji_dir_path, file) + # Filter to only use the pack.json packs + File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json")) + end + + defp load_pack(pack_name) do + pack_path = Path.join(@emoji_dir_path, pack_name) + pack_file = Path.join(pack_path, "pack.json") + + {pack_name, Jason.decode!(File.read!(pack_file))} + end + + defp validate_pack({name, pack}) do + pack_path = Path.join(@emoji_dir_path, name) + + if can_download?(pack, pack_path) do + archive_for_sha = make_archive(name, pack, pack_path) + archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() + + pack = + pack + |> put_in(["pack", "can-download"], true) + |> put_in(["pack", "download-sha256"], archive_sha) + + {name, pack} + else + {name, put_in(pack, ["pack", "can-download"], false)} + end + end + + defp can_download?(pack, pack_path) do + # If the pack is set as shared, check if it can be downloaded + # That means that when asked, the pack can be packed and sent to the remote + # Otherwise, they'd have to download it from external-src + pack["pack"]["share-files"] && + Enum.all?(pack["files"], fn {_, path} -> + File.exists?(Path.join(pack_path, path)) + end) + end + + defp create_archive_and_cache(name, pack, pack_dir, md5) do + files = + ['pack.json'] ++ + (pack["files"] |> Enum.map(fn {_, path} -> to_charlist(path) end)) + + {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) + + cache_ms = :timer.seconds(@cache_seconds_per_file * Enum.count(files)) + + Cachex.put!( + :emoji_packs_cache, + name, + # if pack.json MD5 changes, the cache is not valid anymore + %{pack_json_md5: md5, pack_data: zip_result}, + # Add a minute to cache time for every file in the pack + ttl: cache_ms + ) + + Logger.debug("Created an archive for the '#{name}' emoji pack, \ +keeping it in cache for #{div(cache_ms, 1000)}s") + + zip_result + end + + defp make_archive(name, pack, pack_dir) do + # Having a different pack.json md5 invalidates cache + pack_file_md5 = :crypto.hash(:md5, File.read!(Path.join(pack_dir, "pack.json"))) + + case Cachex.get!(:emoji_packs_cache, name) do + %{pack_file_md5: ^pack_file_md5, pack_data: zip_result} -> + Logger.debug("Using cache for the '#{name}' shared emoji pack") + zip_result + + _ -> + create_archive_and_cache(name, pack, pack_dir, pack_file_md5) + end + end + + @doc """ + An endpoint for other instances (via admin UI) or users (via browser) + to download packs that the instance shares. + """ + def download_shared(conn, %{"name" => name}) do + pack_dir = Path.join(@emoji_dir_path, name) + pack_file = Path.join(pack_dir, "pack.json") + + with {_, true} <- {:exists?, File.exists?(pack_file)}, + pack = Jason.decode!(File.read!(pack_file)), + {_, true} <- {:can_download?, can_download?(pack, pack_dir)} do + zip_result = make_archive(name, pack, pack_dir) + send_download(conn, {:binary, zip_result}, filename: "#{name}.zip") + else + {:can_download?, _} -> + conn + |> put_status(:forbidden) + |> json(%{ + error: "Pack #{name} cannot be downloaded from this instance, either pack sharing\ + was disabled for this pack or some files are missing" + }) + + {:exists?, _} -> + conn + |> put_status(:not_found) + |> json(%{error: "Pack #{name} does not exist"}) + end + end + + @doc """ + An admin endpoint to request downloading a pack named `pack_name` from the instance + `instance_address`. + + If the requested instance's admin chose to share the pack, it will be downloaded + from that instance, otherwise it will be downloaded from the fallback source, if there is one. + """ + def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do + shareable_packs_available = + "#{address}/.well-known/nodeinfo" + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> List.last() + |> Map.get("href") + # Get the actual nodeinfo address and fetch it + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> get_in(["metadata", "features"]) + |> Enum.member?("shareable_emoji_packs") + + if shareable_packs_available do + full_pack = + "#{address}/api/pleroma/emoji/packs/list" + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> Map.get(name) + + pack_info_res = + case full_pack["pack"] do + %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> + {:ok, + %{ + sha: sha, + uri: "#{address}/api/pleroma/emoji/packs/download_shared/#{name}" + }} + + %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> + {:ok, + %{ + sha: sha, + uri: src, + fallback: true + }} + + _ -> + {:error, + "The pack was not set as shared and there is no fallback src to download from"} + end + + with {:ok, %{sha: sha, uri: uri} = pinfo} <- pack_info_res, + %{body: emoji_archive} <- Tesla.get!(uri), + {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do + local_name = data["as"] || name + pack_dir = Path.join(@emoji_dir_path, local_name) + File.mkdir_p!(pack_dir) + + files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) + # Fallback cannot contain a pack.json file + files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files + + {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) + + # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 + # in it to depend on itself + if pinfo[:fallback] do + pack_file_path = Path.join(pack_dir, "pack.json") + + File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) + end + + json(conn, "ok") + else + {:error, e} -> + conn |> put_status(:internal_server_error) |> json(%{error: e}) + + {:checksum, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) + end + else + conn + |> put_status(:internal_server_error) + |> json(%{error: "The requested instance does not support sharing emoji packs"}) + end + end + + @doc """ + Creates an empty pack named `name` which then can be updated via the admin UI. + """ + def create(conn, %{"name" => name}) do + pack_dir = Path.join(@emoji_dir_path, name) + + if not File.exists?(pack_dir) do + File.mkdir_p!(pack_dir) + + pack_file_p = Path.join(pack_dir, "pack.json") + + File.write!( + pack_file_p, + Jason.encode!(%{pack: %{}, files: %{}}) + ) + + conn |> json("ok") + else + conn + |> put_status(:conflict) + |> json(%{error: "A pack named \"#{name}\" already exists"}) + end + end + + @doc """ + Deletes the pack `name` and all it's files. + """ + def delete(conn, %{"name" => name}) do + pack_dir = Path.join(@emoji_dir_path, name) + + case File.rm_rf(pack_dir) do + {:ok, _} -> + conn |> json("ok") + + {:error, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Couldn't delete the pack #{name}"}) + end + end + + @doc """ + An endpoint to update `pack_names`'s metadata. + + `new_data` is the new metadata for the pack, that will replace the old metadata. + """ + def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do + pack_file_p = Path.join([@emoji_dir_path, name, "pack.json"]) + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + # The new fallback-src is in the new data and it's not the same as it was in the old data + should_update_fb_sha = + not is_nil(new_data["fallback-src"]) and + new_data["fallback-src"] != full_pack["pack"]["fallback-src"] + + with {_, true} <- {:should_update?, should_update_fb_sha}, + %{body: pack_arch} <- Tesla.get!(new_data["fallback-src"]), + {:ok, flist} <- :zip.unzip(pack_arch, [:memory]), + {_, true} <- {:has_all_files?, has_all_files?(full_pack, flist)} do + fallback_sha = :crypto.hash(:sha256, pack_arch) |> Base.encode16() + + new_data = Map.put(new_data, "fallback-src-sha256", fallback_sha) + update_metadata_and_send(conn, full_pack, new_data, pack_file_p) + else + {:should_update?, _} -> + update_metadata_and_send(conn, full_pack, new_data, pack_file_p) + + {:has_all_files?, _} -> + conn + |> put_status(:bad_request) + |> json(%{error: "The fallback archive does not have all files specified in pack.json"}) + end + end + + # Check if all files from the pack.json are in the archive + defp has_all_files?(%{"files" => files}, flist) do + Enum.all?(files, fn {_, from_manifest} -> + Enum.find(flist, fn {from_archive, _} -> + to_string(from_archive) == from_manifest + end) + end) + end + + defp update_metadata_and_send(conn, full_pack, new_data, pack_file_p) do + full_pack = Map.put(full_pack, "pack", new_data) + File.write!(pack_file_p, Jason.encode!(full_pack, pretty: true)) + + # Send new data back with fallback sha filled + json(conn, new_data) + end + + defp get_filename(%{"filename" => filename}), do: filename + + defp get_filename(%{"file" => file}) do + case file do + %Plug.Upload{filename: filename} -> filename + url when is_binary(url) -> Path.basename(url) + end + end + + defp empty?(str), do: String.trim(str) == "" + + defp update_file_and_send(conn, updated_full_pack, pack_file_p) do + # Write the emoji pack file + File.write!(pack_file_p, Jason.encode!(updated_full_pack, pretty: true)) + + # Return the modified file list + json(conn, updated_full_pack["files"]) + end + + @doc """ + Updates a file in a pack. + + Updating can mean three things: + + - `add` adds an emoji named `shortcode` to the pack `pack_name`, + that means that the emoji file needs to be uploaded with the request + (thus requiring it to be a multipart request) and be named `file`. + There can also be an optional `filename` that will be the new emoji file name + (if it's not there, the name will be taken from the uploaded file). + - `update` changes emoji shortcode (from `shortcode` to `new_shortcode` or moves the file + (from the current filename to `new_filename`) + - `remove` removes the emoji named `shortcode` and it's associated file + """ + + # Add + def update_file( + conn, + %{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params + ) do + pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + with {_, false} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, + filename <- get_filename(params), + false <- empty?(shortcode), + false <- empty?(filename) do + file_path = Path.join(pack_dir, filename) + + # If the name contains directories, create them + if String.contains?(file_path, "/") do + File.mkdir_p!(Path.dirname(file_path)) + end + + case params["file"] do + %Plug.Upload{path: upload_path} -> + # Copy the uploaded file from the temporary directory + File.copy!(upload_path, file_path) + + url when is_binary(url) -> + # Download and write the file + file_contents = Tesla.get!(url).body + File.write!(file_path, file_contents) + end + + updated_full_pack = put_in(full_pack, ["files", shortcode], filename) + update_file_and_send(conn, updated_full_pack, pack_file_p) + else + {:has_shortcode, _} -> + conn + |> put_status(:conflict) + |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) + + true -> + conn + |> put_status(:bad_request) + |> json(%{error: "shortcode or filename cannot be empty"}) + end + end + + # Remove + def update_file(conn, %{ + "pack_name" => pack_name, + "action" => "remove", + "shortcode" => shortcode + }) do + pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + if Map.has_key?(full_pack["files"], shortcode) do + {emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) + + emoji_file_path = Path.join(pack_dir, emoji_file_path) + + # Delete the emoji file + File.rm!(emoji_file_path) + + # If the old directory has no more files, remove it + if String.contains?(emoji_file_path, "/") do + dir = Path.dirname(emoji_file_path) + + if Enum.empty?(File.ls!(dir)) do + File.rmdir!(dir) + end + end + + update_file_and_send(conn, updated_full_pack, pack_file_p) + else + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) + end + end + + # Update + def update_file( + conn, + %{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params + ) do + pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_file_p = Path.join(pack_dir, "pack.json") + + full_pack = Jason.decode!(File.read!(pack_file_p)) + + with {_, true} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, + %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params, + false <- empty?(new_shortcode), + false <- empty?(new_filename) do + # First, remove the old shortcode, saving the old path + {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) + old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) + new_emoji_file_path = Path.join(pack_dir, new_filename) + + # If the name contains directories, create them + if String.contains?(new_emoji_file_path, "/") do + File.mkdir_p!(Path.dirname(new_emoji_file_path)) + end + + # Move/Rename the old filename to a new filename + # These are probably on the same filesystem, so just rename should work + :ok = File.rename(old_emoji_file_path, new_emoji_file_path) + + # If the old directory has no more files, remove it + if String.contains?(old_emoji_file_path, "/") do + dir = Path.dirname(old_emoji_file_path) + + if Enum.empty?(File.ls!(dir)) do + File.rmdir!(dir) + end + end + + # Then, put in the new shortcode with the new path + updated_full_pack = put_in(updated_full_pack, ["files", new_shortcode], new_filename) + update_file_and_send(conn, updated_full_pack, pack_file_p) + else + {:has_shortcode, _} -> + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) + + true -> + conn + |> put_status(:bad_request) + |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + + _ -> + conn + |> put_status(:bad_request) + |> json(%{error: "new_shortcode or new_file were not specified"}) + end + end + + def update_file(conn, %{"action" => action}) do + conn + |> put_status(:bad_request) + |> json(%{error: "Unknown action: #{action}"}) + end + + @doc """ + Imports emoji from the filesystem. + + Importing means checking all the directories in the + `$instance_static/emoji/` for directories which do not have + `pack.json`. If one has an emoji.txt file, that file will be used + to create a `pack.json` file with it's contents. If the directory has + neither, all the files with specific configured extenstions will be + assumed to be emojis and stored in the new `pack.json` file. + """ + def import_from_fs(conn, _params) do + with {:ok, results} <- File.ls(@emoji_dir_path) do + imported_pack_names = + results + |> Enum.filter(fn file -> + dir_path = Path.join(@emoji_dir_path, file) + # Find the directories that do NOT have pack.json + File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) + end) + |> Enum.map(&write_pack_json_contents/1) + + json(conn, imported_pack_names) + else + {:error, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Error accessing emoji pack directory"}) + end + end + + defp write_pack_json_contents(dir) do + dir_path = Path.join(@emoji_dir_path, dir) + emoji_txt_path = Path.join(dir_path, "emoji.txt") + + files_for_pack = files_for_pack(emoji_txt_path, dir_path) + pack_json_contents = Jason.encode!(%{pack: %{}, files: files_for_pack}) + + File.write!(Path.join(dir_path, "pack.json"), pack_json_contents) + + dir + end + + defp files_for_pack(emoji_txt_path, dir_path) do + if File.exists?(emoji_txt_path) do + # There's an emoji.txt file, it's likely from a pack installed by the pack manager. + # Make a pack.json file from the contents of that emoji.txt fileh + + # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 + + # Create a map of shortcodes to filenames from emoji.txt + File.read!(emoji_txt_path) + |> String.split("\n") + |> Enum.map(&String.trim/1) + |> Enum.map(fn line -> + case String.split(line, ~r/,\s*/) do + # This matches both strings with and without tags + # and we don't care about tags here + [name, file | _] -> {name, file} + _ -> nil + end + end) + |> Enum.filter(fn x -> not is_nil(x) end) + |> Enum.into(%{}) + else + # If there's no emoji.txt, assume all files + # that are of certain extensions from the config are emojis and import them all + pack_extensions = Pleroma.Config.get!([:emoji, :pack_extensions]) + Pleroma.Emoji.make_shortcode_to_file_map(dir_path, pack_extensions) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex new file mode 100644 index 000000000..d17ccf84d --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -0,0 +1,89 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.Conversation.Participation + alias Pleroma.Notification + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.MastodonAPI.ConversationView + alias Pleroma.Web.MastodonAPI.NotificationView + alias Pleroma.Web.MastodonAPI.StatusView + + def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do + with %Participation{} = participation <- Participation.get(participation_id), + true <- user.id == participation.user_id do + conn + |> put_view(ConversationView) + |> render("participation.json", %{participation: participation, for: user}) + end + end + + def conversation_statuses( + %{assigns: %{user: user}} = conn, + %{"id" => participation_id} = params + ) do + participation = Participation.get(participation_id, preload: [:conversation]) + + if user.id == participation.user_id do + params = + params + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + + activities = + participation.conversation.ap_id + |> ActivityPub.fetch_activities_for_context(params) + |> Enum.reverse() + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) + end + end + + def update_conversation( + %{assigns: %{user: user}} = conn, + %{"id" => participation_id, "recipients" => recipients} + ) do + participation = + participation_id + |> Participation.get() + + with true <- user.id == participation.user_id, + {:ok, participation} <- Participation.set_recipients(participation, recipients) do + conn + |> put_view(ConversationView) + |> render("participation.json", %{participation: participation, for: user}) + end + end + + def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do + with {:ok, notification} <- Notification.read_one(user, notification_id) do + conn + |> put_view(NotificationView) + |> render("show.json", %{notification: notification, for: user}) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + end + end + + def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do + with notifications <- Notification.set_read_up_to(user, max_id) do + notifications = Enum.take(notifications, 80) + + conn + |> put_view(NotificationView) + |> render("index.json", %{notifications: notifications, for: user}) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/emoji_api_controller.ex deleted file mode 100644 index 6beca426a..000000000 --- a/lib/pleroma/web/pleroma_api/emoji_api_controller.ex +++ /dev/null @@ -1,575 +0,0 @@ -defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do - use Pleroma.Web, :controller - - require Logger - - @emoji_dir_path Path.join( - Pleroma.Config.get!([:instance, :static_dir]), - "emoji" - ) - - @cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) - - @doc """ - Lists the packs available on the instance as JSON. - - The information is public and does not require authentification. The format is - a map of "pack directory name" to pack.json contents. - """ - def list_packs(conn, _params) do - with {:ok, results} <- File.ls(@emoji_dir_path) do - pack_infos = - results - |> Enum.filter(&has_pack_json?/1) - |> Enum.map(&load_pack/1) - # Check if all the files are in place and can be sent - |> Enum.map(&validate_pack/1) - # Transform into a map of pack-name => pack-data - |> Enum.into(%{}) - - json(conn, pack_infos) - end - end - - defp has_pack_json?(file) do - dir_path = Path.join(@emoji_dir_path, file) - # Filter to only use the pack.json packs - File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json")) - end - - defp load_pack(pack_name) do - pack_path = Path.join(@emoji_dir_path, pack_name) - pack_file = Path.join(pack_path, "pack.json") - - {pack_name, Jason.decode!(File.read!(pack_file))} - end - - defp validate_pack({name, pack}) do - pack_path = Path.join(@emoji_dir_path, name) - - if can_download?(pack, pack_path) do - archive_for_sha = make_archive(name, pack, pack_path) - archive_sha = :crypto.hash(:sha256, archive_for_sha) |> Base.encode16() - - pack = - pack - |> put_in(["pack", "can-download"], true) - |> put_in(["pack", "download-sha256"], archive_sha) - - {name, pack} - else - {name, put_in(pack, ["pack", "can-download"], false)} - end - end - - defp can_download?(pack, pack_path) do - # If the pack is set as shared, check if it can be downloaded - # That means that when asked, the pack can be packed and sent to the remote - # Otherwise, they'd have to download it from external-src - pack["pack"]["share-files"] && - Enum.all?(pack["files"], fn {_, path} -> - File.exists?(Path.join(pack_path, path)) - end) - end - - defp create_archive_and_cache(name, pack, pack_dir, md5) do - files = - ['pack.json'] ++ - (pack["files"] |> Enum.map(fn {_, path} -> to_charlist(path) end)) - - {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) - - cache_ms = :timer.seconds(@cache_seconds_per_file * Enum.count(files)) - - Cachex.put!( - :emoji_packs_cache, - name, - # if pack.json MD5 changes, the cache is not valid anymore - %{pack_json_md5: md5, pack_data: zip_result}, - # Add a minute to cache time for every file in the pack - ttl: cache_ms - ) - - Logger.debug("Created an archive for the '#{name}' emoji pack, \ -keeping it in cache for #{div(cache_ms, 1000)}s") - - zip_result - end - - defp make_archive(name, pack, pack_dir) do - # Having a different pack.json md5 invalidates cache - pack_file_md5 = :crypto.hash(:md5, File.read!(Path.join(pack_dir, "pack.json"))) - - case Cachex.get!(:emoji_packs_cache, name) do - %{pack_file_md5: ^pack_file_md5, pack_data: zip_result} -> - Logger.debug("Using cache for the '#{name}' shared emoji pack") - zip_result - - _ -> - create_archive_and_cache(name, pack, pack_dir, pack_file_md5) - end - end - - @doc """ - An endpoint for other instances (via admin UI) or users (via browser) - to download packs that the instance shares. - """ - def download_shared(conn, %{"name" => name}) do - pack_dir = Path.join(@emoji_dir_path, name) - pack_file = Path.join(pack_dir, "pack.json") - - with {_, true} <- {:exists?, File.exists?(pack_file)}, - pack = Jason.decode!(File.read!(pack_file)), - {_, true} <- {:can_download?, can_download?(pack, pack_dir)} do - zip_result = make_archive(name, pack, pack_dir) - send_download(conn, {:binary, zip_result}, filename: "#{name}.zip") - else - {:can_download?, _} -> - conn - |> put_status(:forbidden) - |> json(%{ - error: "Pack #{name} cannot be downloaded from this instance, either pack sharing\ - was disabled for this pack or some files are missing" - }) - - {:exists?, _} -> - conn - |> put_status(:not_found) - |> json(%{error: "Pack #{name} does not exist"}) - end - end - - @doc """ - An admin endpoint to request downloading a pack named `pack_name` from the instance - `instance_address`. - - If the requested instance's admin chose to share the pack, it will be downloaded - from that instance, otherwise it will be downloaded from the fallback source, if there is one. - """ - def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do - shareable_packs_available = - "#{address}/.well-known/nodeinfo" - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> List.last() - |> Map.get("href") - # Get the actual nodeinfo address and fetch it - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> get_in(["metadata", "features"]) - |> Enum.member?("shareable_emoji_packs") - - if shareable_packs_available do - full_pack = - "#{address}/api/pleroma/emoji/packs/list" - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> Map.get(name) - - pack_info_res = - case full_pack["pack"] do - %{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> - {:ok, - %{ - sha: sha, - uri: "#{address}/api/pleroma/emoji/packs/download_shared/#{name}" - }} - - %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> - {:ok, - %{ - sha: sha, - uri: src, - fallback: true - }} - - _ -> - {:error, - "The pack was not set as shared and there is no fallback src to download from"} - end - - with {:ok, %{sha: sha, uri: uri} = pinfo} <- pack_info_res, - %{body: emoji_archive} <- Tesla.get!(uri), - {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do - local_name = data["as"] || name - pack_dir = Path.join(@emoji_dir_path, local_name) - File.mkdir_p!(pack_dir) - - files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) - # Fallback cannot contain a pack.json file - files = if pinfo[:fallback], do: files, else: ['pack.json'] ++ files - - {:ok, _} = :zip.unzip(emoji_archive, cwd: to_charlist(pack_dir), file_list: files) - - # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256 - # in it to depend on itself - if pinfo[:fallback] do - pack_file_path = Path.join(pack_dir, "pack.json") - - File.write!(pack_file_path, Jason.encode!(full_pack, pretty: true)) - end - - json(conn, "ok") - else - {:error, e} -> - conn |> put_status(:internal_server_error) |> json(%{error: e}) - - {:checksum, _} -> - conn - |> put_status(:internal_server_error) - |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) - end - else - conn - |> put_status(:internal_server_error) - |> json(%{error: "The requested instance does not support sharing emoji packs"}) - end - end - - @doc """ - Creates an empty pack named `name` which then can be updated via the admin UI. - """ - def create(conn, %{"name" => name}) do - pack_dir = Path.join(@emoji_dir_path, name) - - if not File.exists?(pack_dir) do - File.mkdir_p!(pack_dir) - - pack_file_p = Path.join(pack_dir, "pack.json") - - File.write!( - pack_file_p, - Jason.encode!(%{pack: %{}, files: %{}}) - ) - - conn |> json("ok") - else - conn - |> put_status(:conflict) - |> json(%{error: "A pack named \"#{name}\" already exists"}) - end - end - - @doc """ - Deletes the pack `name` and all it's files. - """ - def delete(conn, %{"name" => name}) do - pack_dir = Path.join(@emoji_dir_path, name) - - case File.rm_rf(pack_dir) do - {:ok, _} -> - conn |> json("ok") - - {:error, _} -> - conn - |> put_status(:internal_server_error) - |> json(%{error: "Couldn't delete the pack #{name}"}) - end - end - - @doc """ - An endpoint to update `pack_names`'s metadata. - - `new_data` is the new metadata for the pack, that will replace the old metadata. - """ - def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do - pack_file_p = Path.join([@emoji_dir_path, name, "pack.json"]) - - full_pack = Jason.decode!(File.read!(pack_file_p)) - - # The new fallback-src is in the new data and it's not the same as it was in the old data - should_update_fb_sha = - not is_nil(new_data["fallback-src"]) and - new_data["fallback-src"] != full_pack["pack"]["fallback-src"] - - with {_, true} <- {:should_update?, should_update_fb_sha}, - %{body: pack_arch} <- Tesla.get!(new_data["fallback-src"]), - {:ok, flist} <- :zip.unzip(pack_arch, [:memory]), - {_, true} <- {:has_all_files?, has_all_files?(full_pack, flist)} do - fallback_sha = :crypto.hash(:sha256, pack_arch) |> Base.encode16() - - new_data = Map.put(new_data, "fallback-src-sha256", fallback_sha) - update_metadata_and_send(conn, full_pack, new_data, pack_file_p) - else - {:should_update?, _} -> - update_metadata_and_send(conn, full_pack, new_data, pack_file_p) - - {:has_all_files?, _} -> - conn - |> put_status(:bad_request) - |> json(%{error: "The fallback archive does not have all files specified in pack.json"}) - end - end - - # Check if all files from the pack.json are in the archive - defp has_all_files?(%{"files" => files}, flist) do - Enum.all?(files, fn {_, from_manifest} -> - Enum.find(flist, fn {from_archive, _} -> - to_string(from_archive) == from_manifest - end) - end) - end - - defp update_metadata_and_send(conn, full_pack, new_data, pack_file_p) do - full_pack = Map.put(full_pack, "pack", new_data) - File.write!(pack_file_p, Jason.encode!(full_pack, pretty: true)) - - # Send new data back with fallback sha filled - json(conn, new_data) - end - - defp get_filename(%{"filename" => filename}), do: filename - - defp get_filename(%{"file" => file}) do - case file do - %Plug.Upload{filename: filename} -> filename - url when is_binary(url) -> Path.basename(url) - end - end - - defp empty?(str), do: String.trim(str) == "" - - defp update_file_and_send(conn, updated_full_pack, pack_file_p) do - # Write the emoji pack file - File.write!(pack_file_p, Jason.encode!(updated_full_pack, pretty: true)) - - # Return the modified file list - json(conn, updated_full_pack["files"]) - end - - @doc """ - Updates a file in a pack. - - Updating can mean three things: - - - `add` adds an emoji named `shortcode` to the pack `pack_name`, - that means that the emoji file needs to be uploaded with the request - (thus requiring it to be a multipart request) and be named `file`. - There can also be an optional `filename` that will be the new emoji file name - (if it's not there, the name will be taken from the uploaded file). - - `update` changes emoji shortcode (from `shortcode` to `new_shortcode` or moves the file - (from the current filename to `new_filename`) - - `remove` removes the emoji named `shortcode` and it's associated file - """ - - # Add - def update_file( - conn, - %{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params - ) do - pack_dir = Path.join(@emoji_dir_path, pack_name) - pack_file_p = Path.join(pack_dir, "pack.json") - - full_pack = Jason.decode!(File.read!(pack_file_p)) - - with {_, false} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, - filename <- get_filename(params), - false <- empty?(shortcode), - false <- empty?(filename) do - file_path = Path.join(pack_dir, filename) - - # If the name contains directories, create them - if String.contains?(file_path, "/") do - File.mkdir_p!(Path.dirname(file_path)) - end - - case params["file"] do - %Plug.Upload{path: upload_path} -> - # Copy the uploaded file from the temporary directory - File.copy!(upload_path, file_path) - - url when is_binary(url) -> - # Download and write the file - file_contents = Tesla.get!(url).body - File.write!(file_path, file_contents) - end - - updated_full_pack = put_in(full_pack, ["files", shortcode], filename) - update_file_and_send(conn, updated_full_pack, pack_file_p) - else - {:has_shortcode, _} -> - conn - |> put_status(:conflict) - |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) - - true -> - conn - |> put_status(:bad_request) - |> json(%{error: "shortcode or filename cannot be empty"}) - end - end - - # Remove - def update_file(conn, %{ - "pack_name" => pack_name, - "action" => "remove", - "shortcode" => shortcode - }) do - pack_dir = Path.join(@emoji_dir_path, pack_name) - pack_file_p = Path.join(pack_dir, "pack.json") - - full_pack = Jason.decode!(File.read!(pack_file_p)) - - if Map.has_key?(full_pack["files"], shortcode) do - {emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) - - emoji_file_path = Path.join(pack_dir, emoji_file_path) - - # Delete the emoji file - File.rm!(emoji_file_path) - - # If the old directory has no more files, remove it - if String.contains?(emoji_file_path, "/") do - dir = Path.dirname(emoji_file_path) - - if Enum.empty?(File.ls!(dir)) do - File.rmdir!(dir) - end - end - - update_file_and_send(conn, updated_full_pack, pack_file_p) - else - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - end - end - - # Update - def update_file( - conn, - %{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params - ) do - pack_dir = Path.join(@emoji_dir_path, pack_name) - pack_file_p = Path.join(pack_dir, "pack.json") - - full_pack = Jason.decode!(File.read!(pack_file_p)) - - with {_, true} <- {:has_shortcode, Map.has_key?(full_pack["files"], shortcode)}, - %{"new_shortcode" => new_shortcode, "new_filename" => new_filename} <- params, - false <- empty?(new_shortcode), - false <- empty?(new_filename) do - # First, remove the old shortcode, saving the old path - {old_emoji_file_path, updated_full_pack} = pop_in(full_pack, ["files", shortcode]) - old_emoji_file_path = Path.join(pack_dir, old_emoji_file_path) - new_emoji_file_path = Path.join(pack_dir, new_filename) - - # If the name contains directories, create them - if String.contains?(new_emoji_file_path, "/") do - File.mkdir_p!(Path.dirname(new_emoji_file_path)) - end - - # Move/Rename the old filename to a new filename - # These are probably on the same filesystem, so just rename should work - :ok = File.rename(old_emoji_file_path, new_emoji_file_path) - - # If the old directory has no more files, remove it - if String.contains?(old_emoji_file_path, "/") do - dir = Path.dirname(old_emoji_file_path) - - if Enum.empty?(File.ls!(dir)) do - File.rmdir!(dir) - end - end - - # Then, put in the new shortcode with the new path - updated_full_pack = put_in(updated_full_pack, ["files", new_shortcode], new_filename) - update_file_and_send(conn, updated_full_pack, pack_file_p) - else - {:has_shortcode, _} -> - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - - true -> - conn - |> put_status(:bad_request) - |> json(%{error: "new_shortcode or new_filename cannot be empty"}) - - _ -> - conn - |> put_status(:bad_request) - |> json(%{error: "new_shortcode or new_file were not specified"}) - end - end - - def update_file(conn, %{"action" => action}) do - conn - |> put_status(:bad_request) - |> json(%{error: "Unknown action: #{action}"}) - end - - @doc """ - Imports emoji from the filesystem. - - Importing means checking all the directories in the - `$instance_static/emoji/` for directories which do not have - `pack.json`. If one has an emoji.txt file, that file will be used - to create a `pack.json` file with it's contents. If the directory has - neither, all the files with specific configured extenstions will be - assumed to be emojis and stored in the new `pack.json` file. - """ - def import_from_fs(conn, _params) do - with {:ok, results} <- File.ls(@emoji_dir_path) do - imported_pack_names = - results - |> Enum.filter(fn file -> - dir_path = Path.join(@emoji_dir_path, file) - # Find the directories that do NOT have pack.json - File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) - end) - |> Enum.map(&write_pack_json_contents/1) - - json(conn, imported_pack_names) - else - {:error, _} -> - conn - |> put_status(:internal_server_error) - |> json(%{error: "Error accessing emoji pack directory"}) - end - end - - defp write_pack_json_contents(dir) do - dir_path = Path.join(@emoji_dir_path, dir) - emoji_txt_path = Path.join(dir_path, "emoji.txt") - - files_for_pack = files_for_pack(emoji_txt_path, dir_path) - pack_json_contents = Jason.encode!(%{pack: %{}, files: files_for_pack}) - - File.write!(Path.join(dir_path, "pack.json"), pack_json_contents) - - dir - end - - defp files_for_pack(emoji_txt_path, dir_path) do - if File.exists?(emoji_txt_path) do - # There's an emoji.txt file, it's likely from a pack installed by the pack manager. - # Make a pack.json file from the contents of that emoji.txt fileh - - # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 - - # Create a map of shortcodes to filenames from emoji.txt - File.read!(emoji_txt_path) - |> String.split("\n") - |> Enum.map(&String.trim/1) - |> Enum.map(fn line -> - case String.split(line, ~r/,\s*/) do - # This matches both strings with and without tags - # and we don't care about tags here - [name, file | _] -> {name, file} - _ -> nil - end - end) - |> Enum.filter(fn x -> not is_nil(x) end) - |> Enum.into(%{}) - else - # If there's no emoji.txt, assume all files - # that are of certain extensions from the config are emojis and import them all - pack_extensions = Pleroma.Config.get!([:emoji, :pack_extensions]) - Pleroma.Emoji.make_shortcode_to_file_map(dir_path, pack_extensions) - end - end -end diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex deleted file mode 100644 index d17ccf84d..000000000 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ /dev/null @@ -1,89 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do - use Pleroma.Web, :controller - - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] - - alias Pleroma.Conversation.Participation - alias Pleroma.Notification - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.MastodonAPI.ConversationView - alias Pleroma.Web.MastodonAPI.NotificationView - alias Pleroma.Web.MastodonAPI.StatusView - - def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do - with %Participation{} = participation <- Participation.get(participation_id), - true <- user.id == participation.user_id do - conn - |> put_view(ConversationView) - |> render("participation.json", %{participation: participation, for: user}) - end - end - - def conversation_statuses( - %{assigns: %{user: user}} = conn, - %{"id" => participation_id} = params - ) do - participation = Participation.get(participation_id, preload: [:conversation]) - - if user.id == participation.user_id do - params = - params - |> Map.put("blocking_user", user) - |> Map.put("muting_user", user) - |> Map.put("user", user) - - activities = - participation.conversation.ap_id - |> ActivityPub.fetch_activities_for_context(params) - |> Enum.reverse() - - conn - |> add_link_headers(activities) - |> put_view(StatusView) - |> render("index.json", %{activities: activities, for: user, as: :activity}) - end - end - - def update_conversation( - %{assigns: %{user: user}} = conn, - %{"id" => participation_id, "recipients" => recipients} - ) do - participation = - participation_id - |> Participation.get() - - with true <- user.id == participation.user_id, - {:ok, participation} <- Participation.set_recipients(participation, recipients) do - conn - |> put_view(ConversationView) - |> render("participation.json", %{participation: participation, for: user}) - end - end - - def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do - with {:ok, notification} <- Notification.read_one(user, notification_id) do - conn - |> put_view(NotificationView) - |> render("show.json", %{notification: notification, for: user}) - else - {:error, message} -> - conn - |> put_status(:bad_request) - |> json(%{"error" => message}) - end - end - - def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do - with notifications <- Notification.set_read_up_to(user, max_id) do - notifications = Enum.take(notifications, 80) - - conn - |> put_view(NotificationView) - |> render("index.json", %{notifications: notifications, for: user}) - end - end -end -- cgit v1.2.3 From 494bb6bac64361860db194aed57618450a76177d Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 23 Sep 2019 22:37:30 +0300 Subject: updated tests --- .../controllers/mastodon_api_controller.ex | 18 ++++++++---------- lib/pleroma/web/mastodon_api/views/status_view.ex | 4 +--- 2 files changed, 9 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 0c2b8dbb7..da74e4aa2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -44,6 +44,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Scopes alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.RichMedia alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.ControllerHelper @@ -1530,19 +1531,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do - with %Activity{} = activity <- Activity.get_by_id(status_id), + def status_card(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id(id), true <- Visibility.visible_for_user?(activity, user) do - data = - StatusView.render( - "card.json", - Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - ) + data = RichMedia.Helpers.fetch_data_for_activity(activity) - json(conn, data) + conn + |> put_view(StatusView) + |> render("card.json", data) else - _e -> - json(conn, %{}) + _e -> {:error, :not_found} 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 ef796cddd..0450ed4d9 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -343,9 +343,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end - def render("card.json", _) do - nil - end + def render("card.json", _), do: nil def render("attachment.json", %{attachment: attachment}) do [attachment_url | _] = attachment["url"] -- cgit v1.2.3 From 63af6951fa42429d0a02861d5ad1afdd053864cf Mon Sep 17 00:00:00 2001 From: Rachel Fae Fox Date: Mon, 23 Sep 2019 20:38:53 +0000 Subject: add tunable for stream uploads, as needed for jortage to work. --- lib/pleroma/uploaders/s3.ex | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex index 8c353bed3..9876b6398 100644 --- a/lib/pleroma/uploaders/s3.ex +++ b/lib/pleroma/uploaders/s3.ex @@ -38,16 +38,26 @@ defmodule Pleroma.Uploaders.S3 do def put_file(%Pleroma.Upload{} = upload) do config = Config.get([__MODULE__]) bucket = Keyword.get(config, :bucket) + streaming = Keyword.get(config, :streaming_enabled) s3_name = strict_encode(upload.path) op = - upload.tempfile - |> ExAws.S3.Upload.stream_file() - |> ExAws.S3.upload(bucket, s3_name, [ - {:acl, :public_read}, - {:content_type, upload.content_type} - ]) + if streaming do + upload.tempfile + |> ExAws.S3.Upload.stream_file() + |> ExAws.S3.upload(bucket, s3_name, [ + {:acl, :public_read}, + {:content_type, upload.content_type} + ]) + else + {:ok, file_data} = File.read(upload.tempfile) + + ExAws.S3.put_object(bucket, s3_name, file_data, [ + {:acl, :public_read}, + {:content_type, upload.content_type} + ]) + end case ExAws.request(op) do {:ok, _} -> -- cgit v1.2.3 From e63f167f013c1c159c40745ee44535c65b999ffb Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 24 Sep 2019 00:37:27 +0300 Subject: Also pretty print pack.json --- lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 6beca426a..3ad29bd38 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -242,7 +242,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") File.write!( pack_file_p, - Jason.encode!(%{pack: %{}, files: %{}}) + Jason.encode!(%{pack: %{}, files: %{}}, pretty: true) ) conn |> json("ok") -- cgit v1.2.3 From e1d2d69c8799cb6d3efbdc28d9e98867da76b4c2 Mon Sep 17 00:00:00 2001 From: Steven Fuchs Date: Mon, 23 Sep 2019 22:33:59 +0000 Subject: Clean up views --- .../web/activity_pub/activity_pub_controller.ex | 36 ++++++++++++++-------- lib/pleroma/web/activity_pub/views/object_view.ex | 4 +-- lib/pleroma/web/admin_api/admin_api_controller.ex | 20 +++++++----- lib/pleroma/web/admin_api/report.ex | 22 +++++++++++++ lib/pleroma/web/admin_api/views/report_view.ex | 18 ++++------- .../mastodon_api/controllers/search_controller.ex | 5 +-- lib/pleroma/web/ostatus/ostatus_controller.ex | 3 +- 7 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 lib/pleroma/web/admin_api/report.ex (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 01b34fb1d..9eb86106f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -49,7 +49,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("user.json", %{user: user})) + |> put_view(UserView) + |> render("user.json", %{user: user}) else nil -> {:error, :not_found} end @@ -90,7 +91,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do conn |> put_resp_content_type("application/activity+json") - |> json(ObjectView.render("likes.json", ap_id, likes, page)) + |> put_view(ObjectView) + |> render("likes.json", %{ap_id: ap_id, likes: likes, page: page}) else {:public?, false} -> {:error, :not_found} @@ -104,7 +106,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do likes <- Utils.get_object_likes(object) do conn |> put_resp_content_type("application/activity+json") - |> json(ObjectView.render("likes.json", ap_id, likes)) + |> put_view(ObjectView) + |> render("likes.json", %{ap_id: ap_id, likes: likes}) else {:public?, false} -> {:error, :not_found} @@ -158,7 +161,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do def following(%{assigns: %{relay: true}} = conn, _params) do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("following.json", %{user: Relay.get_actor()})) + |> put_view(UserView) + |> render("following.json", %{user: Relay.get_actor()}) end def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do @@ -170,7 +174,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("following.json", %{user: user, page: page, for: for_user})) + |> put_view(UserView) + |> render("following.json", %{user: user, page: page, for: for_user}) else {:show_follows, _} -> conn @@ -184,7 +189,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("following.json", %{user: user, for: for_user})) + |> put_view(UserView) + |> render("following.json", %{user: user, for: for_user}) end end @@ -192,7 +198,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do def followers(%{assigns: %{relay: true}} = conn, _params) do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("followers.json", %{user: Relay.get_actor()})) + |> put_view(UserView) + |> render("followers.json", %{user: Relay.get_actor()}) end def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do @@ -204,7 +211,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user})) + |> put_view(UserView) + |> render("followers.json", %{user: user, page: page, for: for_user}) else {:show_followers, _} -> conn @@ -218,7 +226,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("followers.json", %{user: user, for: for_user})) + |> put_view(UserView) + |> render("followers.json", %{user: user, for: for_user}) end end @@ -227,7 +236,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]})) + |> put_view(UserView) + |> render("outbox.json", %{user: user, max_id: params["max_id"]}) end end @@ -275,7 +285,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do with {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("user.json", %{user: user})) + |> put_view(UserView) + |> render("user.json", %{user: user}) else nil -> {:error, :not_found} end @@ -296,7 +307,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do conn |> put_resp_content_type("application/activity+json") - |> json(UserView.render("user.json", %{user: user})) + |> put_view(UserView) + |> render("user.json", %{user: user}) end def whoami(_conn, _params), do: {:error, :not_found} diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex index 94d05f49b..0d63f0707 100644 --- a/lib/pleroma/web/activity_pub/views/object_view.ex +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -37,12 +37,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do Map.merge(base, additional) end - def render("likes.json", ap_id, likes, page) do + def render("likes.json", %{ap_id: ap_id, likes: likes, page: page}) do collection(likes, "#{ap_id}/likes", page) |> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header()) end - def render("likes.json", ap_id, likes) do + def render("likes.json", %{ap_id: ap_id, likes: likes}) do %{ "id" => "#{ap_id}/likes", "type" => "OrderedCollection", diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 4d4e862dd..251bb1012 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.AdminAPI.Config alias Pleroma.Web.AdminAPI.ConfigView alias Pleroma.Web.AdminAPI.ModerationLogView + alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.AdminAPI.ReportView alias Pleroma.Web.AdminAPI.Search alias Pleroma.Web.CommonAPI @@ -139,7 +140,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def user_show(conn, %{"nickname" => nickname}) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do conn - |> json(AccountView.render("show.json", %{user: user})) + |> put_view(AccountView) + |> render("show.json", %{user: user}) else _ -> {:error, :not_found} end @@ -158,7 +160,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do }) conn - |> json(StatusView.render("index.json", %{activities: activities, as: :activity})) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, as: :activity}) else _ -> {:error, :not_found} end @@ -178,7 +181,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do }) conn - |> json(AccountView.render("show.json", %{user: updated_user})) + |> put_view(AccountView) + |> render("show.json", %{user: updated_user}) end def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do @@ -424,7 +428,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do invites = UserInviteToken.list_invites() conn - |> json(AccountView.render("invites.json", %{invites: invites})) + |> put_view(AccountView) + |> render("invites.json", %{invites: invites}) end @doc "Revokes invite by token" @@ -432,7 +437,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do with {:ok, invite} <- UserInviteToken.find_by_token(token), {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do conn - |> json(AccountView.render("invite.json", %{invite: updated_invite})) + |> put_view(AccountView) + |> render("invite.json", %{invite: updated_invite}) else nil -> {:error, :not_found} end @@ -465,7 +471,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do with %Activity{} = report <- Activity.get_by_id(id) do conn |> put_view(ReportView) - |> render("show.json", %{report: report}) + |> render("show.json", Report.extract_report_info(report)) else _ -> {:error, :not_found} end @@ -481,7 +487,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do conn |> put_view(ReportView) - |> render("show.json", %{report: report}) + |> render("show.json", Report.extract_report_info(report)) end end diff --git a/lib/pleroma/web/admin_api/report.ex b/lib/pleroma/web/admin_api/report.ex new file mode 100644 index 000000000..c751dc2be --- /dev/null +++ b/lib/pleroma/web/admin_api/report.ex @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.Report do + alias Pleroma.Activity + alias Pleroma.User + + def extract_report_info( + %{data: %{"actor" => actor, "object" => [account_ap_id | status_ap_ids]}} = report + ) do + user = User.get_cached_by_ap_id(actor) + account = User.get_cached_by_ap_id(account_ap_id) + + statuses = + Enum.map(status_ap_ids, fn ap_id -> + Activity.get_by_ap_id_with_object(ap_id) + end) + + %{report: report, user: user, account: account, statuses: statuses} + end +end diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 51b95ad5e..8c06364a3 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -4,27 +4,26 @@ defmodule Pleroma.Web.AdminAPI.ReportView do use Pleroma.Web, :view - alias Pleroma.Activity alias Pleroma.HTML alias Pleroma.User + alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.StatusView def render("index.json", %{reports: reports}) do %{ reports: - render_many(reports[:items], __MODULE__, "show.json", as: :report) |> Enum.reverse(), + reports[:items] + |> Enum.map(&Report.extract_report_info(&1)) + |> Enum.map(&render(__MODULE__, "show.json", &1)) + |> Enum.reverse(), total: reports[:total] } end - def render("show.json", %{report: report}) do - user = User.get_cached_by_ap_id(report.data["actor"]) + def render("show.json", %{report: report, user: user, account: account, statuses: statuses}) do created_at = Utils.to_masto_date(report.data["published"]) - [account_ap_id | status_ap_ids] = report.data["object"] - account = User.get_cached_by_ap_id(account_ap_id) - content = unless is_nil(report.data["content"]) do HTML.filter_tags(report.data["content"]) @@ -32,11 +31,6 @@ defmodule Pleroma.Web.AdminAPI.ReportView do nil end - statuses = - Enum.map(status_ap_ids, fn ap_id -> - Activity.get_by_ap_id_with_object(ap_id) - end) - %{ id: report.id, account: merge_account_views(account), diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 9072aa7a4..c91713773 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -19,9 +19,10 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do accounts = User.search(query, search_options(params, user)) - res = AccountView.render("accounts.json", users: accounts, for: user, as: :user) - json(conn, res) + conn + |> put_view(AccountView) + |> render("accounts.json", users: accounts, for: user, as: :user) end def search2(conn, params), do: do_search(:v2, conn, params) diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 64b2c64b3..8f325b28e 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -216,7 +216,8 @@ defmodule Pleroma.Web.OStatus.OStatusController do conn |> put_resp_header("content-type", "application/activity+json") - |> json(ObjectView.render("object.json", %{object: object})) + |> put_view(ObjectView) + |> render("object.json", %{object: object}) end defp represent_activity(_conn, "activity+json", _, _) do -- cgit v1.2.3 From 79b25be4e1e9e97277a831c98ccea86a038914de Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 24 Sep 2019 14:16:52 +0700 Subject: Do not return tuple when unneeded --- lib/mix/tasks/pleroma/user.ex | 8 +-- lib/pleroma/user.ex | 72 ++++++++++------------ lib/pleroma/web/activity_pub/publisher.ex | 4 +- .../controllers/mastodon_api_controller.ex | 10 +-- 4 files changed, 42 insertions(+), 52 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index eb0052144..84c923901 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -228,9 +228,9 @@ defmodule Mix.Tasks.Pleroma.User do shell_info("Deactivating #{user.nickname}") User.deactivate(user) - {:ok, friends} = User.get_friends(user) - - Enum.each(friends, fn friend -> + user + |> User.get_friends() + |> Enum.each(fn friend -> user = User.get_cached_by_id(user.id) shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}") @@ -405,7 +405,7 @@ defmodule Mix.Tasks.Pleroma.User do start_pleroma() with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do - {:ok, _} = User.delete_user_activities(user) + User.delete_user_activities(user) shell_info("User #{nickname} statuses deleted.") else _ -> diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index ab253a274..8d126933b 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -685,9 +685,9 @@ defmodule Pleroma.User do @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} def get_followers(user, page \\ nil) do - q = get_followers_query(user, page) - - {:ok, Repo.all(q)} + user + |> get_followers_query(page) + |> Repo.all() end @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} @@ -720,9 +720,9 @@ defmodule Pleroma.User do def get_friends_query(user), do: get_friends_query(user, nil) def get_friends(user, page \\ nil) do - q = get_friends_query(user, page) - - {:ok, Repo.all(q)} + user + |> get_friends_query(page) + |> Repo.all() end def get_friends_ids(user, page \\ nil) do @@ -733,15 +733,13 @@ defmodule Pleroma.User do @spec get_follow_requests(User.t()) :: {:ok, [User.t()]} def get_follow_requests(%User{} = user) do - users = - Activity.follow_requests_for_actor(user) - |> join(:inner, [a], u in User, on: a.actor == u.ap_id) - |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address])) - |> group_by([a, u], u.id) - |> select([a, u], u) - |> Repo.all() - - {:ok, users} + user + |> Activity.follow_requests_for_actor() + |> join(:inner, [a], u in User, on: a.actor == u.ap_id) + |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address])) + |> group_by([a, u], u.id) + |> select([a, u], u) + |> Repo.all() end def increase_note_count(%User{} = user) do @@ -1104,15 +1102,13 @@ defmodule Pleroma.User do def deactivate(%User{} = user, status \\ true) do info_cng = User.Info.set_activation_status(user.info, status) - with {:ok, friends} <- User.get_friends(user), - {:ok, followers} <- User.get_followers(user), - {:ok, user} <- + with {:ok, user} <- user |> change() |> put_embed(:info, info_cng) |> update_and_set_cache() do - Enum.each(followers, &invalidate_cache(&1)) - Enum.each(friends, &update_follower_count(&1)) + Enum.each(get_followers(user), &invalidate_cache/1) + Enum.each(get_friends(user), &update_follower_count/1) {:ok, user} end @@ -1137,18 +1133,18 @@ defmodule Pleroma.User do {:ok, _user} = ActivityPub.delete(user) # Remove all relationships - {:ok, followers} = User.get_followers(user) - - Enum.each(followers, fn follower -> + user + |> get_followers() + |> Enum.each(fn follower -> ActivityPub.unfollow(follower, user) - User.unfollow(follower, user) + unfollow(follower, user) end) - {:ok, friends} = User.get_friends(user) - - Enum.each(friends, fn followed -> + user + |> get_friends() + |> Enum.each(fn followed -> ActivityPub.unfollow(user, followed) - User.unfollow(user, followed) + unfollow(user, followed) end) delete_user_activities(user) @@ -1160,13 +1156,11 @@ defmodule Pleroma.User do def perform(:fetch_initial_posts, %User{} = user) do pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) - Enum.each( - # Insert all the posts in reverse order, so they're in the right order on the timeline - Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)), - &Pleroma.Web.Federator.incoming_ap_doc/1 - ) - - {:ok, user} + # Insert all the posts in reverse order, so they're in the right order on the timeline + user.info.source_data["outbox"] + |> Utils.fetch_ordered_collection(pages) + |> Enum.reverse() + |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1) end def perform(:deactivate_async, user, status), do: deactivate(user, status) @@ -1252,16 +1246,12 @@ defmodule Pleroma.User do }) end - def delete_user_activities(%User{ap_id: ap_id} = user) do + def delete_user_activities(%User{ap_id: ap_id}) do ap_id |> Activity.Queries.by_actor() |> RepoStreamer.chunk_stream(50) - |> Stream.each(fn activities -> - Enum.each(activities, &delete_activity(&1)) - end) + |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end) |> Stream.run() - - {:ok, user} end defp delete_activity(%{data: %{"type" => "Create"}} = activity) do diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 114251b24..3866dacee 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -111,11 +111,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do @spec recipients(User.t(), Activity.t()) :: list(User.t()) | [] defp recipients(actor, activity) do - {:ok, followers} = + followers = if actor.follower_address in activity.recipients do User.get_external_followers(actor) else - {:ok, []} + [] end fetchers = diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 6421c2c53..270c74089 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -958,11 +958,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def follow_requests(%{assigns: %{user: followed}} = conn, _params) do - with {:ok, follow_requests} <- User.get_follow_requests(followed) do - conn - |> put_view(AccountView) - |> render("accounts.json", %{for: followed, users: follow_requests, as: :user}) - end + follow_requests = User.get_follow_requests(followed) + + conn + |> put_view(AccountView) + |> render("accounts.json", %{for: followed, users: follow_requests, as: :user}) end def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do -- cgit v1.2.3 From a66a7a328ffe908bda4e8453111559aa7cd579a6 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 24 Sep 2019 15:16:44 +0700 Subject: Extract notification actions from `MastodonAPIController` into `NotificationController` --- .../controllers/mastodon_api_controller.ex | 45 ----------------- .../controllers/notification_controller.ex | 57 ++++++++++++++++++++++ lib/pleroma/web/router.ex | 10 ++-- 3 files changed, 62 insertions(+), 50 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 6421c2c53..1d53f7509 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -16,7 +16,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Filter alias Pleroma.Formatter alias Pleroma.HTTP - alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Pagination alias Pleroma.Plugs.RateLimiter @@ -35,7 +34,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonView - alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.ReportView alias Pleroma.Web.MastodonAPI.ScheduledActivityView alias Pleroma.Web.MastodonAPI.StatusView @@ -722,49 +720,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def notifications(%{assigns: %{user: user}} = conn, params) do - notifications = MastodonAPI.get_notifications(user, params) - - conn - |> add_link_headers(notifications) - |> put_view(NotificationView) - |> render("index.json", %{notifications: notifications, for: user}) - end - - def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do - with {:ok, notification} <- Notification.get(user, id) do - conn - |> put_view(NotificationView) - |> render("show.json", %{notification: notification, for: user}) - else - {:error, reason} -> - conn - |> put_status(:forbidden) - |> json(%{"error" => reason}) - end - end - - def clear_notifications(%{assigns: %{user: user}} = conn, _params) do - Notification.clear(user) - json(conn, %{}) - end - - def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do - with {:ok, _notif} <- Notification.dismiss(user, id) do - json(conn, %{}) - else - {:error, reason} -> - conn - |> put_status(:forbidden) - |> json(%{"error" => reason}) - end - end - - def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do - Notification.destroy_multiple(user, ids) - json(conn, %{}) - end - def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do id = List.wrap(id) q = from(u in User, where: u.id in ^id) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex new file mode 100644 index 000000000..7e4d7297c --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -0,0 +1,57 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.NotificationController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.Notification + alias Pleroma.Web.MastodonAPI.MastodonAPI + + # GET /api/v1/notifications + def index(%{assigns: %{user: user}} = conn, params) do + notifications = MastodonAPI.get_notifications(user, params) + + conn + |> add_link_headers(notifications) + |> render("index.json", notifications: notifications, for: user) + end + + # GET /api/v1/notifications/:id + def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with {:ok, notification} <- Notification.get(user, id) do + render(conn, "show.json", notification: notification, for: user) + else + {:error, reason} -> + conn + |> put_status(:forbidden) + |> json(%{"error" => reason}) + end + end + + # POST /api/v1/notifications/clear + def clear(%{assigns: %{user: user}} = conn, _params) do + Notification.clear(user) + json(conn, %{}) + end + + # POST /api/v1/notifications/dismiss + def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + with {:ok, _notif} <- Notification.dismiss(user, id) do + json(conn, %{}) + else + {:error, reason} -> + conn + |> put_status(:forbidden) + |> json(%{"error" => reason}) + end + end + + # DELETE /api/v1/notifications/destroy_multiple + def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do + Notification.destroy_multiple(user, ids) + json(conn, %{}) + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index e583093d2..9fee5beac 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -324,11 +324,11 @@ defmodule Pleroma.Web.Router do get("/favourites", MastodonAPIController, :favourites) get("/bookmarks", MastodonAPIController, :bookmarks) - post("/notifications/clear", MastodonAPIController, :clear_notifications) - post("/notifications/dismiss", MastodonAPIController, :dismiss_notification) - get("/notifications", MastodonAPIController, :notifications) - get("/notifications/:id", MastodonAPIController, :get_notification) - delete("/notifications/destroy_multiple", MastodonAPIController, :destroy_multiple) + get("/notifications", NotificationController, :index) + get("/notifications/:id", NotificationController, :show) + post("/notifications/clear", NotificationController, :clear) + post("/notifications/dismiss", NotificationController, :dismiss) + delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses) get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status) -- cgit v1.2.3 From 209395c7e60afe7115f22afd6936d9c6bdd7bb72 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 24 Sep 2019 19:50:07 +0700 Subject: Add User.change_info/2 and User.update_info/2 --- lib/mix/tasks/pleroma/user.ex | 25 +-- lib/pleroma/user.ex | 173 ++++++--------------- lib/pleroma/user/info.ex | 24 +-- lib/pleroma/web/admin_api/admin_api_controller.ex | 59 +++---- lib/pleroma/web/common_api/common_api.ex | 28 +--- .../controllers/mastodon_api_controller.ex | 68 +++----- .../web/twitter_api/twitter_api_controller.ex | 8 +- 7 files changed, 111 insertions(+), 274 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 84c923901..d93ba8dee 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -4,7 +4,6 @@ defmodule Mix.Tasks.Pleroma.User do use Mix.Task - import Ecto.Changeset import Mix.Pleroma alias Pleroma.User alias Pleroma.UserInviteToken @@ -443,39 +442,21 @@ defmodule Mix.Tasks.Pleroma.User do end defp set_moderator(user, value) do - info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value}) - - user_cng = - Ecto.Changeset.change(user) - |> put_embed(:info, info_cng) - - {:ok, user} = User.update_and_set_cache(user_cng) + {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_moderator: value})) shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}") user end defp set_admin(user, value) do - info_cng = User.Info.admin_api_update(user.info, %{is_admin: value}) - - user_cng = - Ecto.Changeset.change(user) - |> put_embed(:info, info_cng) - - {:ok, user} = User.update_and_set_cache(user_cng) + {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_admin: value})) shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}") user end defp set_locked(user, value) do - info_cng = User.Info.user_upgrade(user.info, %{locked: value}) - - user_cng = - Ecto.Changeset.change(user) - |> put_embed(:info, info_cng) - - {:ok, user} = User.update_and_set_cache(user_cng) + {:ok, user} = User.update_info(user, &User.Info.user_upgrade(&1, %{locked: value})) shell_info("Locked status of #{user.nickname}: #{user.info.locked}") user diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 8d126933b..422bc6fa6 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -197,8 +197,6 @@ defmodule Pleroma.User do |> truncate_if_exists(:name, name_limit) |> truncate_if_exists(:bio, bio_limit) - info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info]) - changes = %User{} |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar]) @@ -208,7 +206,7 @@ defmodule Pleroma.User do |> validate_length(:bio, max: bio_limit) |> validate_length(:name, max: name_limit) |> put_change(:local, false) - |> put_embed(:info, info_cng) + |> change_info(&User.Info.remote_user_creation(&1, params[:info])) if changes.valid? do case info_cng.changes[:source_data] do @@ -245,7 +243,6 @@ defmodule Pleroma.User do name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now()) - info_cng = User.Info.user_upgrade(struct.info, params[:info], remote?) struct |> cast(params, [ @@ -260,7 +257,7 @@ defmodule Pleroma.User do |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, max: name_limit) - |> put_embed(:info, info_cng) + |> change_info(&User.Info.user_upgrade(&1, params[:info], remote?)) end def password_update_changeset(struct, params) do @@ -785,21 +782,15 @@ defmodule Pleroma.User do end def update_note_count(%User{} = user) do - note_count_query = + note_count = from( a in Object, where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data), select: count(a.id) ) + |> Repo.one() - note_count = Repo.one(note_count_query) - - info_cng = User.Info.set_note_count(user.info, note_count) - - user - |> change() - |> put_embed(:info, info_cng) - |> update_and_set_cache() + update_info(user, &User.Info.set_note_count(&1, note_count)) end @spec maybe_fetch_follow_information(User.t()) :: User.t() @@ -816,17 +807,7 @@ defmodule Pleroma.User do def fetch_follow_information(user) do with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do - info_cng = User.Info.follow_information_update(user.info, info) - - changeset = - user - |> change() - |> put_embed(:info, info_cng) - - update_and_set_cache(changeset) - else - {:error, _} = e -> e - e -> {:error, e} + update_info(user, &User.Info.follow_information_update(&1, info)) end end @@ -900,31 +881,11 @@ defmodule Pleroma.User do @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()} def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do - info = muter.info - - info_cng = - User.Info.add_to_mutes(info, ap_id) - |> User.Info.add_to_muted_notifications(info, ap_id, notifications?) - - cng = - change(muter) - |> put_embed(:info, info_cng) - - update_and_set_cache(cng) + update_info(muter, &User.Info.add_to_mutes(&1, ap_id, notifications?)) end def unmute(muter, %{ap_id: ap_id}) do - info = muter.info - - info_cng = - User.Info.remove_from_mutes(info, ap_id) - |> User.Info.remove_from_muted_notifications(info, ap_id) - - cng = - change(muter) - |> put_embed(:info, info_cng) - - update_and_set_cache(cng) + update_info(muter, &User.Info.remove_from_mutes(&1, ap_id)) end def subscribe(subscriber, %{ap_id: ap_id}) do @@ -936,26 +897,14 @@ defmodule Pleroma.User do if blocked do {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"} else - info_cng = - subscribed.info - |> User.Info.add_to_subscribers(subscriber.ap_id) - - change(subscribed) - |> put_embed(:info, info_cng) - |> update_and_set_cache() + update_info(subscribed, &User.Info.add_to_subscribers(&1, subscriber.ap_id)) end end end def unsubscribe(unsubscriber, %{ap_id: ap_id}) do with %User{} = user <- get_cached_by_ap_id(ap_id) do - info_cng = - user.info - |> User.Info.remove_from_subscribers(unsubscriber.ap_id) - - change(user) - |> put_embed(:info, info_cng) - |> update_and_set_cache() + update_info(user, &User.Info.remove_from_subscribers(&1, unsubscriber.ap_id)) end end @@ -990,15 +939,7 @@ defmodule Pleroma.User do {:ok, blocker} = update_follower_count(blocker) - info_cng = - blocker.info - |> User.Info.add_to_block(ap_id) - - cng = - change(blocker) - |> put_embed(:info, info_cng) - - update_and_set_cache(cng) + update_info(blocker, &User.Info.add_to_block(&1, ap_id)) end # helper to handle the block given only an actor's AP id @@ -1007,15 +948,7 @@ defmodule Pleroma.User do end def unblock(blocker, %{ap_id: ap_id}) do - info_cng = - blocker.info - |> User.Info.remove_from_block(ap_id) - - cng = - change(blocker) - |> put_embed(:info, info_cng) - - update_and_set_cache(cng) + update_info(blocker, &User.Info.remove_from_block(&1, ap_id)) end def mutes?(nil, _), do: false @@ -1072,27 +1005,11 @@ defmodule Pleroma.User do end def block_domain(user, domain) do - info_cng = - user.info - |> User.Info.add_to_domain_block(domain) - - cng = - change(user) - |> put_embed(:info, info_cng) - - update_and_set_cache(cng) + update_info(user, &User.Info.add_to_domain_block(&1, domain)) end def unblock_domain(user, domain) do - info_cng = - user.info - |> User.Info.remove_from_domain_block(domain) - - cng = - change(user) - |> put_embed(:info, info_cng) - - update_and_set_cache(cng) + update_info(user, &User.Info.remove_from_domain_block(&1, domain)) end def deactivate_async(user, status \\ true) do @@ -1100,13 +1017,7 @@ defmodule Pleroma.User do end def deactivate(%User{} = user, status \\ true) do - info_cng = User.Info.set_activation_status(user.info, status) - - with {:ok, user} <- - user - |> change() - |> put_embed(:info, info_cng) - |> update_and_set_cache() do + with {:ok, user} <- update_info(user, &User.Info.set_activation_status(&1, status)) do Enum.each(get_followers(user), &invalidate_cache/1) Enum.each(get_friends(user), &update_follower_count/1) @@ -1115,11 +1026,7 @@ defmodule Pleroma.User do end def update_notification_settings(%User{} = user, settings \\ %{}) do - info_changeset = User.Info.update_notification_settings(user.info, settings) - - change(user) - |> put_embed(:info, info_changeset) - |> update_and_set_cache() + update_info(user, &User.Info.update_notification_settings(&1, settings)) end def delete(%User{} = user) do @@ -1560,11 +1467,7 @@ defmodule Pleroma.User do @spec switch_email_notifications(t(), String.t(), boolean()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} def switch_email_notifications(user, type, status) do - info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status}) - - change(user) - |> put_embed(:info, info) - |> update_and_set_cache() + update_info(user, &User.Info.update_email_notifications(&1, %{type => status})) end @doc """ @@ -1586,13 +1489,8 @@ defmodule Pleroma.User do def toggle_confirmation(%User{} = user) do need_confirmation? = !user.info.confirmation_pending - info_changeset = - User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?) - user - |> change() - |> put_embed(:info, info_changeset) - |> update_and_set_cache() + |> update_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?)) end def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do @@ -1615,16 +1513,11 @@ defmodule Pleroma.User do } end - def ensure_keys_present(%User{info: info} = user) do - if info.keys do - {:ok, user} - else - {:ok, pem} = Keys.generate_rsa_pem() + def ensure_keys_present(%{info: %{keys: keys}} = user) when not is_nil(keys), do: {:ok, user} - user - |> Ecto.Changeset.change() - |> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem)) - |> update_and_set_cache() + def ensure_keys_present(%User{} = user) do + with {:ok, pem} <- Keys.generate_rsa_pem() do + update_info(user, &User.Info.set_keys(&1, pem)) end end @@ -1670,4 +1563,26 @@ defmodule Pleroma.User do |> validate_format(:email, @email_regex) |> update_and_set_cache() end + + @doc """ + Changes `user.info` and returns the user changeset. + + `fun` is called with the `user.info`. + """ + def change_info(user, fun) do + changeset = change(user) + info = get_field(changeset, :info) || %User.Info{} + put_embed(changeset, :info, fun.(info)) + end + + @doc """ + Updates `user.info` and sets cache. + + `fun` is called with the `user.info`. + """ + def update_info(user, fun) do + user + |> change_info(fun) + |> update_and_set_cache() + end end diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 99745f496..92e3944f7 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -187,16 +187,11 @@ defmodule Pleroma.User.Info do |> validate_required([:subscribers]) end - @spec add_to_mutes(Info.t(), String.t()) :: Changeset.t() - def add_to_mutes(info, muted) do - set_mutes(info, Enum.uniq([muted | info.mutes])) - end - - @spec add_to_muted_notifications(Changeset.t(), Info.t(), String.t(), boolean()) :: - Changeset.t() - def add_to_muted_notifications(changeset, info, muted, notifications?) do - set_notification_mutes( - changeset, + @spec add_to_mutes(Info.t(), String.t(), boolean()) :: Changeset.t() + def add_to_mutes(info, muted, notifications?) do + info + |> set_mutes(Enum.uniq([muted | info.mutes])) + |> set_notification_mutes( Enum.uniq([muted | info.muted_notifications]), notifications? ) @@ -204,12 +199,9 @@ defmodule Pleroma.User.Info do @spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t() def remove_from_mutes(info, muted) do - set_mutes(info, List.delete(info.mutes, muted)) - end - - @spec remove_from_muted_notifications(Changeset.t(), Info.t(), String.t()) :: Changeset.t() - def remove_from_muted_notifications(changeset, info, muted) do - set_notification_mutes(changeset, List.delete(info.muted_notifications, muted), true) + info + |> set_mutes(List.delete(info.mutes, muted)) + |> set_notification_mutes(List.delete(info.muted_notifications, muted), true) end def add_to_block(info, blocked) do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 0d1db8fa0..6e703d169 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -254,18 +254,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do "nickname" => nickname }) when permission_group in ["moderator", "admin"] do - user = User.get_cached_by_nickname(nickname) - - info = - %{} - |> Map.put("is_" <> permission_group, true) - - info_cng = User.Info.admin_api_update(user.info, info) + info = Map.put(%{}, "is_" <> permission_group, true) - cng = - user - |> Ecto.Changeset.change() - |> Ecto.Changeset.put_embed(:info, info_cng) + {:ok, user} = + nickname + |> User.get_cached_by_nickname() + |> User.update_info(&User.Info.admin_api_update(&1, info)) ModerationLog.insert_log(%{ action: "grant", @@ -274,8 +268,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do permission: permission_group }) - {:ok, _user} = User.update_and_set_cache(cng) - json(conn, info) end @@ -293,40 +285,33 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do }) end + def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do + render_error(conn, :forbidden, "You can't revoke your own admin status.") + end + def right_delete( - %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn, + %{assigns: %{user: admin}} = conn, %{ "permission_group" => permission_group, "nickname" => nickname } ) when permission_group in ["moderator", "admin"] do - if admin_nickname == nickname do - render_error(conn, :forbidden, "You can't revoke your own admin status.") - else - user = User.get_cached_by_nickname(nickname) - - info = - %{} - |> Map.put("is_" <> permission_group, false) - - info_cng = User.Info.admin_api_update(user.info, info) + info = Map.put(%{}, "is_" <> permission_group, false) - cng = - Ecto.Changeset.change(user) - |> Ecto.Changeset.put_embed(:info, info_cng) + {:ok, user} = + nickname + |> User.get_cached_by_nickname() + |> User.update_info(&User.Info.admin_api_update(&1, info)) - {:ok, _user} = User.update_and_set_cache(cng) - - ModerationLog.insert_log(%{ - action: "revoke", - actor: admin, - subject: user, - permission: permission_group - }) + ModerationLog.insert_log(%{ + action: "revoke", + actor: admin, + subject: user, + permission: permission_group + }) - json(conn, info) - end + json(conn, info) end def right_delete(conn, _) do diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 5faddc9f4..2f12ad43a 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -302,12 +302,11 @@ defmodule Pleroma.Web.CommonAPI do # Updates the emojis for a user based on their profile def update(user) do + emoji = emoji_from_profile(user) + source_data = user.info |> Map.get(:source_data, {}) |> Map.put("tag", emoji) + user = - with emoji <- emoji_from_profile(user), - source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji), - info_cng <- User.Info.set_source_data(user.info, source_data), - change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), - {:ok, user} <- User.update_and_set_cache(change) do + with {:ok, user} <- User.update_info(user, &User.Info.set_source_data(&1, source_data)) do user else _e -> @@ -336,10 +335,7 @@ defmodule Pleroma.Web.CommonAPI do } } = activity <- get_by_id_or_ap_id(id_or_ap_id), true <- Visibility.is_public?(activity), - %{valid?: true} = info_changeset <- User.Info.add_pinnned_activity(user.info, activity), - changeset <- - Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset), - {:ok, _user} <- User.update_and_set_cache(changeset) do + {:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do {:ok, activity} else %{errors: [pinned_activities: {err, _}]} -> @@ -352,11 +348,7 @@ defmodule Pleroma.Web.CommonAPI do def unpin(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - %{valid?: true} = info_changeset <- - User.Info.remove_pinnned_activity(user.info, activity), - changeset <- - Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset), - {:ok, _user} <- User.update_and_set_cache(changeset) do + {:ok, _user} <- User.update_info(user, &User.Info.remove_pinnned_activity(&1, activity)) do {:ok, activity} else %{errors: [pinned_activities: {err, _}]} -> @@ -462,9 +454,7 @@ defmodule Pleroma.Web.CommonAPI do ap_id = muted.ap_id if ap_id not in user.info.muted_reblogs do - info_changeset = User.Info.add_reblog_mute(user.info, ap_id) - changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset) - User.update_and_set_cache(changeset) + User.update_info(user, &User.Info.add_reblog_mute(&1, ap_id)) end end @@ -472,9 +462,7 @@ defmodule Pleroma.Web.CommonAPI do ap_id = muted.ap_id if ap_id in user.info.muted_reblogs do - info_changeset = User.Info.remove_reblog_mute(user.info, ap_id) - changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset) - User.update_and_set_cache(changeset) + User.update_info(user, &User.Info.remove_reblog_mute(&1, ap_id)) end end end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 270c74089..8a5287079 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -188,14 +188,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end) |> Map.put(:emoji, user_info_emojis) - info_cng = User.Info.profile_update(user.info, info_params) + changeset = + user + |> User.update_changeset(user_params) + |> User.change_info(&User.Info.profile_update(&1, info_params)) - with changeset <- User.update_changeset(user, user_params), - changeset <- Changeset.put_embed(changeset, :info, info_cng), - {:ok, user} <- User.update_and_set_cache(changeset) do - if original_user != user do - CommonAPI.update(user) - end + with {:ok, user} <- User.update_and_set_cache(changeset) do + if original_user != user, do: CommonAPI.update(user) json( conn, @@ -225,12 +224,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do - with new_info <- %{"banner" => %{}}, - info_cng <- User.Info.profile_update(user.info, new_info), - changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng), - {:ok, user} <- User.update_and_set_cache(changeset) do - CommonAPI.update(user) + new_info = %{"banner" => %{}} + with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do + CommonAPI.update(user) json(conn, %{url: nil}) end end @@ -238,9 +235,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def update_banner(%{assigns: %{user: user}} = conn, params) do with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), new_info <- %{"banner" => object.data}, - info_cng <- User.Info.profile_update(user.info, new_info), - changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng), - {:ok, user} <- User.update_and_set_cache(changeset) do + {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do CommonAPI.update(user) %{"url" => [%{"href" => href} | _]} = object.data @@ -249,10 +244,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do - with new_info <- %{"background" => %{}}, - info_cng <- User.Info.profile_update(user.info, new_info), - changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng), - {:ok, _user} <- User.update_and_set_cache(changeset) do + new_info = %{"background" => %{}} + + with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do json(conn, %{url: nil}) end end @@ -260,9 +254,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def update_background(%{assigns: %{user: user}} = conn, params) do with {:ok, object} <- ActivityPub.upload(params, type: :background), new_info <- %{"background" => object.data}, - info_cng <- User.Info.profile_update(user.info, new_info), - changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng), - {:ok, _user} <- User.update_and_set_cache(changeset) do + {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do %{"url" => [%{"href" => href} | _]} = object.data json(conn, %{url: href}) @@ -816,26 +808,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)), %{} = attachment_data <- Map.put(object.data, "id", object.id), - %{type: type} = rendered <- + # Reject if not an image + %{type: "image"} = rendered <- StatusView.render("attachment.json", %{attachment: attachment_data}) do - # Reject if not an image - if type == "image" do - # Sure! - # Save to the user's info - info_changeset = User.Info.mascot_update(user.info, rendered) - - user_changeset = - user - |> Changeset.change() - |> Changeset.put_embed(:info, info_changeset) - - {:ok, _user} = User.update_and_set_cache(user_changeset) + # Sure! + # Save to the user's info + {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, rendered)) - conn - |> json(rendered) - else - render_error(conn, :unsupported_media_type, "mascots can only be images") - end + json(conn, rendered) + else + %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images") end end @@ -1366,11 +1348,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do - info_cng = User.Info.mastodon_settings_update(user.info, settings) - - with changeset <- Changeset.change(user), - changeset <- Changeset.put_embed(changeset, :info, info_cng), - {:ok, _user} <- User.update_and_set_cache(changeset) do + with {:ok, _user} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do json(conn, %{}) else e -> diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 42234ae09..27f3664e0 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do use Pleroma.Web, :controller - alias Ecto.Changeset alias Pleroma.Notification alias Pleroma.User alias Pleroma.Web.OAuth.Token @@ -16,15 +15,14 @@ defmodule Pleroma.Web.TwitterAPI.Controller do action_fallback(:errors) def confirm_email(conn, %{"user_id" => uid, "token" => token}) do - with %User{} = user <- User.get_cached_by_id(uid), true <- user.local, true <- user.info.confirmation_pending, true <- user.info.confirmation_token == token, - info_change <- User.Info.confirmation_changeset(user.info, need_confirmation: false), - changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change), - {:ok, _} <- User.update_and_set_cache(changeset) do conn |> redirect(to: "/") + with %User{info: info} = user <- User.get_cached_by_id(uid), + {:ok, _} <- + User.update_info(user, &User.Info.confirmation_changeset(&1, need_confirmation: false)) do end end -- cgit v1.2.3 From 1bea67cb5e70ae28209a193c33b9da2d3c41cfb7 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 24 Sep 2019 14:14:34 +0700 Subject: Cleanup Pleroma.User --- lib/pleroma/user.ex | 233 +++++++++------------ lib/pleroma/web/common_api/common_api.ex | 25 +-- .../web/twitter_api/twitter_api_controller.ex | 7 +- 3 files changed, 109 insertions(+), 156 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 422bc6fa6..640ef05c4 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -106,9 +106,7 @@ defmodule Pleroma.User do def profile_url(%User{ap_id: ap_id}), do: ap_id def profile_url(_), do: nil - def ap_id(%User{nickname: nickname}) do - "#{Web.base_url()}/users/#{nickname}" - end + def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}" def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers" @@ -119,12 +117,9 @@ defmodule Pleroma.User do def user_info(%User{} = user, args \\ %{}) do following_count = - if args[:following_count], - do: args[:following_count], - else: user.info.following_count || following_count(user) + Map.get(args, :following_count, user.info.following_count || following_count(user)) - follower_count = - if args[:follower_count], do: args[:follower_count], else: user.info.follower_count + follower_count = Map.get(args, :follower_count, user.info.follower_count) %{ note_count: user.info.note_count, @@ -137,12 +132,11 @@ defmodule Pleroma.User do end def follow_state(%User{} = user, %User{} = target) do - follow_activity = Utils.fetch_latest_follow(user, target) - - if follow_activity, - do: follow_activity.data["state"], + case Utils.fetch_latest_follow(user, target) do + %{data: %{"state" => state}} -> state # Ideally this would be nil, but then Cachex does not commit the value - else: false + _ -> false + end end def get_cached_follow_state(user, target) do @@ -152,11 +146,7 @@ defmodule Pleroma.User do @spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()} def set_follow_state_cache(user_ap_id, target_ap_id, state) do - Cachex.put( - :user_cache, - "follow_state:#{user_ap_id}|#{target_ap_id}", - state - ) + Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state) end def set_info_cache(user, args) do @@ -197,32 +187,25 @@ defmodule Pleroma.User do |> truncate_if_exists(:name, name_limit) |> truncate_if_exists(:bio, bio_limit) - changes = - %User{} + changeset = + %User{local: false} |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar]) |> validate_required([:name, :ap_id]) |> unique_constraint(:nickname) |> validate_format(:nickname, @email_regex) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, max: name_limit) - |> put_change(:local, false) |> change_info(&User.Info.remote_user_creation(&1, params[:info])) - if changes.valid? do - case info_cng.changes[:source_data] do - %{"followers" => followers, "following" => following} -> - changes - |> put_change(:follower_address, followers) - |> put_change(:following_address, following) - - _ -> - followers = User.ap_followers(%User{nickname: changes.changes[:nickname]}) + case params[:info][:source_data] do + %{"followers" => followers, "following" => following} -> + changeset + |> put_change(:follower_address, followers) + |> put_change(:following_address, following) - changes - |> put_change(:follower_address, followers) - end - else - changes + _ -> + followers = ap_followers(%User{nickname: get_field(changeset, :nickname)}) + put_change(changeset, :follower_address, followers) end end @@ -308,43 +291,39 @@ defmodule Pleroma.User do opts[:need_confirmation] end - info_change = - User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?) + struct + |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation]) + |> validate_required([:name, :nickname, :password, :password_confirmation]) + |> validate_confirmation(:password) + |> unique_constraint(:email) + |> unique_constraint(:nickname) + |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames])) + |> validate_format(:nickname, local_nickname_regex()) + |> validate_format(:email, @email_regex) + |> validate_length(:bio, max: bio_limit) + |> validate_length(:name, min: 1, max: name_limit) + |> change_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?)) + |> maybe_validate_required_email(opts[:external]) + |> put_password_hash + |> put_ap_id() + |> unique_constraint(:ap_id) + |> put_following_and_follower_address() + end - changeset = - struct - |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation]) - |> validate_required([:name, :nickname, :password, :password_confirmation]) - |> validate_confirmation(:password) - |> unique_constraint(:email) - |> unique_constraint(:nickname) - |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames])) - |> validate_format(:nickname, local_nickname_regex()) - |> validate_format(:email, @email_regex) - |> validate_length(:bio, max: bio_limit) - |> validate_length(:name, min: 1, max: name_limit) - |> put_change(:info, info_change) + def maybe_validate_required_email(changeset, true), do: changeset + def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email]) - changeset = - if opts[:external] do - changeset - else - validate_required(changeset, [:email]) - end + defp put_ap_id(changeset) do + ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)}) + put_change(changeset, :ap_id, ap_id) + end - if changeset.valid? do - ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]}) - followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]}) + defp put_following_and_follower_address(changeset) do + followers = ap_followers(%User{nickname: get_field(changeset, :nickname)}) - changeset - |> put_password_hash - |> put_change(:ap_id, ap_id) - |> unique_constraint(:ap_id) - |> put_change(:following, [followers]) - |> put_change(:follower_address, followers) - else - changeset - end + changeset + |> put_change(:following, [followers]) + |> put_change(:follower_address, followers) end defp autofollow_users(user) do @@ -359,9 +338,8 @@ defmodule Pleroma.User do @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)" def register(%Ecto.Changeset{} = changeset) do - with {:ok, user} <- Repo.insert(changeset), - {:ok, user} <- post_register_action(user) do - {:ok, user} + with {:ok, user} <- Repo.insert(changeset) do + post_register_action(user) end end @@ -407,7 +385,7 @@ defmodule Pleroma.User do end def maybe_direct_follow(%User{} = follower, %User{} = followed) do - if not User.ap_enabled?(followed) do + if not ap_enabled?(followed) do follow(follower, followed) else {:ok, follower} @@ -440,9 +418,7 @@ defmodule Pleroma.User do {1, [follower]} = Repo.update_all(q, []) - Enum.each(followeds, fn followed -> - update_follower_count(followed) - end) + Enum.each(followeds, &update_follower_count/1) set_cache(follower) end @@ -552,8 +528,6 @@ defmodule Pleroma.User do def update_and_set_cache(changeset) do with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do set_cache(user) - else - e -> e end end @@ -590,9 +564,7 @@ defmodule Pleroma.User do key = "nickname:#{nickname}" Cachex.fetch!(:user_cache, key, fn -> - user_result = get_or_fetch_by_nickname(nickname) - - case user_result do + case get_or_fetch_by_nickname(nickname) do {:ok, user} -> {:commit, user} {:error, _error} -> {:ignore, nil} end @@ -632,13 +604,11 @@ defmodule Pleroma.User do def get_cached_user_info(user) do key = "user_info:#{user.id}" - Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end) + Cachex.fetch!(:user_cache, key, fn -> user_info(user) end) end def fetch_by_nickname(nickname) do - ap_try = ActivityPub.make_user_from_nickname(nickname) - - case ap_try do + case ActivityPub.make_user_from_nickname(nickname) do {:ok, user} -> {:ok, user} _ -> OStatus.make_user(nickname) end @@ -673,7 +643,8 @@ defmodule Pleroma.User do end def get_followers_query(user, page) do - from(u in get_followers_query(user, nil)) + user + |> get_followers_query(nil) |> User.Query.paginate(page, 20) end @@ -689,18 +660,17 @@ defmodule Pleroma.User do @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} def get_external_followers(user, page \\ nil) do - q = - user - |> get_followers_query(page) - |> User.Query.build(%{external: true}) - - {:ok, Repo.all(q)} + user + |> get_followers_query(page) + |> User.Query.build(%{external: true}) + |> Repo.all() end def get_followers_ids(user, page \\ nil) do - q = get_followers_query(user, page) - - Repo.all(from(u in q, select: u.id)) + user + |> get_followers_query(page) + |> select([u], u.id) + |> Repo.all() end @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() @@ -709,7 +679,8 @@ defmodule Pleroma.User do end def get_friends_query(user, page) do - from(u in get_friends_query(user, nil)) + user + |> get_friends_query(nil) |> User.Query.paginate(page, 20) end @@ -723,9 +694,10 @@ defmodule Pleroma.User do end def get_friends_ids(user, page \\ nil) do - q = get_friends_query(user, page) - - Repo.all(from(u in q, select: u.id)) + user + |> get_friends_query(page) + |> select([u], u.id) + |> Repo.all() end @spec get_follow_requests(User.t()) :: {:ok, [User.t()]} @@ -889,12 +861,10 @@ defmodule Pleroma.User do end def subscribe(subscriber, %{ap_id: ap_id}) do - deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) - with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do - blocked = blocks?(subscribed, subscriber) and deny_follow_blocked + deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) - if blocked do + if blocks?(subscribed, subscriber) and deny_follow_blocked do {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"} else update_info(subscribed, &User.Info.add_to_subscribers(&1, subscriber.ap_id)) @@ -933,9 +903,7 @@ defmodule Pleroma.User do blocker end - if following?(blocked, blocker) do - unfollow(blocked, blocker) - end + if following?(blocked, blocker), do: unfollow(blocked, blocker) {:ok, blocker} = update_follower_count(blocker) @@ -1168,17 +1136,19 @@ defmodule Pleroma.User do end defp delete_activity(%{data: %{"type" => "Like"}} = activity) do - user = get_cached_by_ap_id(activity.actor) object = Object.normalize(activity) - ActivityPub.unlike(user, object) + activity.actor + |> get_cached_by_ap_id() + |> ActivityPub.unlike(object) end defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do - user = get_cached_by_ap_id(activity.actor) object = Object.normalize(activity) - ActivityPub.unannounce(user, object) + activity.actor + |> get_cached_by_ap_id() + |> ActivityPub.unannounce(object) end defp delete_activity(_activity), do: "Doing nothing" @@ -1190,9 +1160,7 @@ defmodule Pleroma.User do def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy]) def fetch_by_ap_id(ap_id) do - ap_try = ActivityPub.make_user_from_ap_id(ap_id) - - case ap_try do + case ActivityPub.make_user_from_ap_id(ap_id) do {:ok, user} -> {:ok, user} @@ -1207,7 +1175,7 @@ defmodule Pleroma.User do def get_or_fetch_by_ap_id(ap_id) do user = get_cached_by_ap_id(ap_id) - if !is_nil(user) and !User.needs_update?(user) do + if !is_nil(user) and !needs_update?(user) do {:ok, user} else # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled) @@ -1227,19 +1195,20 @@ defmodule Pleroma.User do @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing." def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do - if user = get_cached_by_ap_id(uri) do + with %User{} = user <- get_cached_by_ap_id(uri) do user else - changes = - %User{info: %User.Info{}} - |> cast(%{}, [:ap_id, :nickname, :local]) - |> put_change(:ap_id, uri) - |> put_change(:nickname, nickname) - |> put_change(:local, true) - |> put_change(:follower_address, uri <> "/followers") - - {:ok, user} = Repo.insert(changes) - user + _ -> + {:ok, user} = + %User{info: %User.Info{}} + |> cast(%{}, [:ap_id, :nickname, :local]) + |> put_change(:ap_id, uri) + |> put_change(:nickname, nickname) + |> put_change(:local, true) + |> put_change(:follower_address, uri <> "/followers") + |> Repo.insert() + + user end end @@ -1296,23 +1265,21 @@ defmodule Pleroma.User do # this is because we have synchronous follow APIs and need to simulate them # with an async handshake def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do - with %User{} = a <- User.get_cached_by_id(a.id), - %User{} = b <- User.get_cached_by_id(b.id) do + with %User{} = a <- get_cached_by_id(a.id), + %User{} = b <- get_cached_by_id(b.id) do {:ok, a, b} else - _e -> - :error + nil -> :error end end def wait_and_refresh(timeout, %User{} = a, %User{} = b) do with :ok <- :timer.sleep(timeout), - %User{} = a <- User.get_cached_by_id(a.id), - %User{} = b <- User.get_cached_by_id(b.id) do + %User{} = a <- get_cached_by_id(a.id), + %User{} = b <- get_cached_by_id(b.id) do {:ok, a, b} else - _e -> - :error + nil -> :error end end @@ -1374,7 +1341,7 @@ defmodule Pleroma.User do defp normalize_tags(tags) do [tags] |> List.flatten() - |> Enum.map(&String.downcase(&1)) + |> Enum.map(&String.downcase/1) end defp local_nickname_regex do diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 2f12ad43a..40eebe2aa 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -309,8 +309,7 @@ defmodule Pleroma.Web.CommonAPI do with {:ok, user} <- User.update_info(user, &User.Info.set_source_data(&1, source_data)) do user else - _e -> - user + _e -> user end ActivityPub.update(%{ @@ -338,11 +337,8 @@ defmodule Pleroma.Web.CommonAPI do {:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do {:ok, activity} else - %{errors: [pinned_activities: {err, _}]} -> - {:error, err} - - _ -> - {:error, dgettext("errors", "Could not pin")} + {:error, %{changes: %{info: %{errors: [pinned_activities: {err, _}]}}}} -> {:error, err} + _ -> {:error, dgettext("errors", "Could not pin")} end end @@ -351,11 +347,8 @@ defmodule Pleroma.Web.CommonAPI do {:ok, _user} <- User.update_info(user, &User.Info.remove_pinnned_activity(&1, activity)) do {:ok, activity} else - %{errors: [pinned_activities: {err, _}]} -> - {:error, err} - - _ -> - {:error, dgettext("errors", "Could not unpin")} + %{errors: [pinned_activities: {err, _}]} -> {:error, err} + _ -> {:error, dgettext("errors", "Could not unpin")} end end @@ -450,17 +443,13 @@ defmodule Pleroma.Web.CommonAPI do defp set_visibility(activity, _), do: {:ok, activity} - def hide_reblogs(user, muted) do - ap_id = muted.ap_id - + def hide_reblogs(user, %{ap_id: ap_id} = _muted) do if ap_id not in user.info.muted_reblogs do User.update_info(user, &User.Info.add_reblog_mute(&1, ap_id)) end end - def show_reblogs(user, muted) do - ap_id = muted.ap_id - + def show_reblogs(user, %{ap_id: ap_id} = _muted) do if ap_id in user.info.muted_reblogs do User.update_info(user, &User.Info.remove_reblog_mute(&1, ap_id)) end diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 27f3664e0..aa06e2630 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -15,14 +15,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do action_fallback(:errors) def confirm_email(conn, %{"user_id" => uid, "token" => token}) do - true <- user.local, - true <- user.info.confirmation_pending, - true <- user.info.confirmation_token == token, - conn - |> redirect(to: "/") with %User{info: info} = user <- User.get_cached_by_id(uid), + true <- user.local and info.confirmation_pending and info.confirmation_token == token, {:ok, _} <- User.update_info(user, &User.Info.confirmation_changeset(&1, need_confirmation: false)) do + redirect(conn, to: "/") end end -- cgit v1.2.3 From 035f22f7849815c5f77a734c56f409c0f08ac853 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 24 Sep 2019 14:49:02 +0700 Subject: Fix Credo warnings --- lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex | 2 +- lib/pleroma/web/twitter_api/twitter_api_controller.ex | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 8a5287079..873ef20bc 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -1348,7 +1348,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do - with {:ok, _user} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do + with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do json(conn, %{}) else e -> diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index aa06e2630..5024ac70d 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -15,10 +15,11 @@ defmodule Pleroma.Web.TwitterAPI.Controller do action_fallback(:errors) def confirm_email(conn, %{"user_id" => uid, "token" => token}) do + new_info = [need_confirmation: false] + with %User{info: info} = user <- User.get_cached_by_id(uid), true <- user.local and info.confirmation_pending and info.confirmation_token == token, - {:ok, _} <- - User.update_info(user, &User.Info.confirmation_changeset(&1, need_confirmation: false)) do + {:ok, _} <- User.update_info(user, &User.Info.confirmation_changeset(&1, new_info)) do redirect(conn, to: "/") end end -- cgit v1.2.3 From 60cbea5bb2e70d6a843d6f595a3c1cfe9cc78d1e Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Wed, 25 Sep 2019 01:25:42 +0300 Subject: Allow activities pagination via limit/offset --- lib/pleroma/pagination.ex | 1 + lib/pleroma/web/activity_pub/activity_pub.ex | 17 +++++++++++------ lib/pleroma/web/admin_api/admin_api_controller.ex | 6 +++++- 3 files changed, 17 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index b55379c4a..9d279fba7 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -64,6 +64,7 @@ defmodule Pleroma.Pagination do def paginate(query, options, :offset) do query + |> restrict(:order, options) |> restrict(:offset, options) |> restrict(:limit, options) end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1cf8b6151..bb0a5ca73 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -519,13 +519,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Repo.one() end - def fetch_public_activities(opts \\ %{}) do + def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do opts = Map.drop(opts, ["user"]) [Pleroma.Constants.as_public()] |> fetch_activities_query(opts) |> restrict_unlisted() - |> Pagination.fetch_paginated(opts) + |> Pagination.fetch_paginated(opts, pagination) |> Enum.reverse() end @@ -918,11 +918,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> exclude_poll_votes(opts) end - def fetch_activities(recipients, opts \\ %{}) do + def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do list_memberships = Pleroma.List.memberships(opts["user"]) fetch_activities_query(recipients ++ list_memberships, opts) - |> Pagination.fetch_paginated(opts) + |> Pagination.fetch_paginated(opts, pagination) |> Enum.reverse() |> maybe_update_cc(list_memberships, opts["user"]) end @@ -953,10 +953,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do ) end - def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do + def fetch_activities_bounded( + recipients, + recipients_with_public, + opts \\ %{}, + pagination \\ :keyset + ) do fetch_activities_query([], opts) |> fetch_activities_bounded_query(recipients, recipients_with_public) - |> Pagination.fetch_paginated(opts) + |> Pagination.fetch_paginated(opts, pagination) |> Enum.reverse() end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 0d1db8fa0..6761c32b9 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -463,13 +463,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def list_reports(conn, params) do + {page, page_size} = page_params(params) + params = params |> Map.put("type", "Flag") |> Map.put("skip_preload", true) |> Map.put("total", true) + |> Map.put("limit", page_size) + |> Map.put("offset", (page - 1) * page_size) - reports = ActivityPub.fetch_activities([], params) + reports = ActivityPub.fetch_activities([], params, :offset) conn |> put_view(ReportView) -- cgit v1.2.3 From b5dfe83433e092a007f85ed9c0ffe5a47dbfcccd Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 18 Sep 2019 21:54:31 +0700 Subject: Replace `Pleroma.FlakeId` with `flake_id` hex package --- lib/pleroma/activity.ex | 2 +- lib/pleroma/activity_expiration.ex | 3 +- lib/pleroma/application.ex | 1 - lib/pleroma/bookmark.ex | 13 +- lib/pleroma/conversation/participation.ex | 4 +- .../conversation/participation_recipient_ship.ex | 2 +- lib/pleroma/delivery.ex | 3 +- lib/pleroma/filter.ex | 2 +- lib/pleroma/flake_id.ex | 182 --------------------- lib/pleroma/list.ex | 2 +- lib/pleroma/notification.ex | 4 +- lib/pleroma/password_reset_token.ex | 2 +- lib/pleroma/registration.ex | 4 +- lib/pleroma/scheduled_activity.ex | 2 +- lib/pleroma/thread_mute.ex | 4 +- lib/pleroma/user.ex | 4 +- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/common_api/utils.ex | 2 +- lib/pleroma/web/oauth/authorization.ex | 2 +- lib/pleroma/web/oauth/token.ex | 2 +- lib/pleroma/web/push/subscription.ex | 2 +- .../web/websub/websub_client_subscription.ex | 2 +- 22 files changed, 31 insertions(+), 215 deletions(-) delete mode 100644 lib/pleroma/flake_id.ex (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index ec558168a..2c04a26f9 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Activity do @type t :: %__MODULE__{} @type actor :: String.t() - @primary_key {:id, Pleroma.FlakeId, autogenerate: true} + @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} # https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19 @mastodon_notification_types %{ diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex index bf57abca4..7ea5c48ca 100644 --- a/lib/pleroma/activity_expiration.ex +++ b/lib/pleroma/activity_expiration.ex @@ -7,7 +7,6 @@ defmodule Pleroma.ActivityExpiration do alias Pleroma.Activity alias Pleroma.ActivityExpiration - alias Pleroma.FlakeId alias Pleroma.Repo import Ecto.Changeset @@ -17,7 +16,7 @@ defmodule Pleroma.ActivityExpiration do @min_activity_lifetime :timer.hours(1) schema "activity_expirations" do - belongs_to(:activity, Activity, type: FlakeId) + belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) field(:scheduled_at, :naive_datetime) end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index dabce771d..e805cefa0 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -35,7 +35,6 @@ defmodule Pleroma.Application do Pleroma.Config.TransferTask, Pleroma.Emoji, Pleroma.Captcha, - Pleroma.FlakeId, Pleroma.Daemons.ScheduledActivityDaemon, Pleroma.Daemons.ActivityExpirationDaemon ] ++ diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex index d976f949c..221a94f34 100644 --- a/lib/pleroma/bookmark.ex +++ b/lib/pleroma/bookmark.ex @@ -10,20 +10,20 @@ defmodule Pleroma.Bookmark do alias Pleroma.Activity alias Pleroma.Bookmark - alias Pleroma.FlakeId alias Pleroma.Repo alias Pleroma.User @type t :: %__MODULE__{} schema "bookmarks" do - belongs_to(:user, User, type: FlakeId) - belongs_to(:activity, Activity, type: FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) + belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) timestamps() end - @spec create(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()} + @spec create(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t()) :: + {:ok, Bookmark.t()} | {:error, Changeset.t()} def create(user_id, activity_id) do attrs = %{ user_id: user_id, @@ -37,7 +37,7 @@ defmodule Pleroma.Bookmark do |> Repo.insert() end - @spec for_user_query(FlakeId.t()) :: Ecto.Query.t() + @spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t() def for_user_query(user_id) do Bookmark |> where(user_id: ^user_id) @@ -52,7 +52,8 @@ defmodule Pleroma.Bookmark do |> Repo.one() end - @spec destroy(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()} + @spec destroy(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t()) :: + {:ok, Bookmark.t()} | {:error, Changeset.t()} def destroy(user_id, activity_id) do from(b in Bookmark, where: b.user_id == ^user_id, diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index ea5b9fe17..e946f6de2 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -13,10 +13,10 @@ defmodule Pleroma.Conversation.Participation do import Ecto.Query schema "conversation_participations" do - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:conversation, Conversation) field(:read, :boolean, default: false) - field(:last_activity_id, Pleroma.FlakeId, virtual: true) + field(:last_activity_id, FlakeId.Ecto.CompatType, virtual: true) has_many(:recipient_ships, RecipientShip) has_many(:recipients, through: [:recipient_ships, :user]) diff --git a/lib/pleroma/conversation/participation_recipient_ship.ex b/lib/pleroma/conversation/participation_recipient_ship.ex index 932cbd04c..e3d158cbc 100644 --- a/lib/pleroma/conversation/participation_recipient_ship.ex +++ b/lib/pleroma/conversation/participation_recipient_ship.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Conversation.Participation.RecipientShip do import Ecto.Changeset schema "conversation_participation_recipient_ships" do - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:participation, Participation) end diff --git a/lib/pleroma/delivery.ex b/lib/pleroma/delivery.ex index 29a1e5a77..1d586a252 100644 --- a/lib/pleroma/delivery.ex +++ b/lib/pleroma/delivery.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Delivery do use Ecto.Schema alias Pleroma.Delivery - alias Pleroma.FlakeId alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User @@ -16,7 +15,7 @@ defmodule Pleroma.Delivery do import Ecto.Query schema "deliveries" do - belongs_to(:user, User, type: FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:object, Object) end diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex index 90457dadf..c87141582 100644 --- a/lib/pleroma/filter.ex +++ b/lib/pleroma/filter.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Filter do alias Pleroma.User schema "filters" do - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) field(:filter_id, :integer) field(:hide, :boolean, default: false) field(:whole_word, :boolean, default: true) diff --git a/lib/pleroma/flake_id.ex b/lib/pleroma/flake_id.ex deleted file mode 100644 index 042cf8659..000000000 --- a/lib/pleroma/flake_id.ex +++ /dev/null @@ -1,182 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.FlakeId do - @moduledoc """ - Flake is a decentralized, k-ordered id generation service. - - Adapted from: - - * [flaky](https://github.com/nirvana/flaky), released under the terms of the Truly Free License, - * [Flake](https://github.com/boundary/flake), Copyright 2012, Boundary, Apache License, Version 2.0 - """ - - @type t :: binary - - use Ecto.Type - use GenServer - require Logger - alias __MODULE__ - import Kernel, except: [to_string: 1] - - defstruct node: nil, time: 0, sq: 0 - - @doc "Converts a binary Flake to a String" - def to_string(<<0::integer-size(64), id::integer-size(64)>>) do - Kernel.to_string(id) - end - - def to_string(<<_::integer-size(64), _::integer-size(48), _::integer-size(16)>> = flake) do - encode_base62(flake) - end - - def to_string(s), do: s - - def from_string(int) when is_integer(int) do - from_string(Kernel.to_string(int)) - end - - for i <- [-1, 0] do - def from_string(unquote(i)), do: <<0::integer-size(128)>> - def from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>> - end - - def from_string(<<_::integer-size(128)>> = flake), do: flake - - def from_string(string) when is_binary(string) and byte_size(string) < 18 do - case Integer.parse(string) do - {id, ""} -> <<0::integer-size(64), id::integer-size(64)>> - _ -> nil - end - end - - def from_string(string) do - string |> decode_base62 |> from_integer - end - - def to_integer(<>), do: integer - - def from_integer(integer) do - <<_time::integer-size(64), _node::integer-size(48), _seq::integer-size(16)>> = - <> - end - - @doc "Generates a Flake" - @spec get :: binary - def get, do: to_string(:gen_server.call(:flake, :get)) - - # checks that ID is is valid FlakeID - # - @spec is_flake_id?(String.t()) :: boolean - def is_flake_id?(id), do: is_flake_id?(String.to_charlist(id), true) - defp is_flake_id?([c | cs], true) when c >= ?0 and c <= ?9, do: is_flake_id?(cs, true) - defp is_flake_id?([c | cs], true) when c >= ?A and c <= ?Z, do: is_flake_id?(cs, true) - defp is_flake_id?([c | cs], true) when c >= ?a and c <= ?z, do: is_flake_id?(cs, true) - defp is_flake_id?([], true), do: true - defp is_flake_id?(_, _), do: false - - # -- Ecto.Type API - @impl Ecto.Type - def type, do: :uuid - - @impl Ecto.Type - def cast(value) do - {:ok, FlakeId.to_string(value)} - end - - @impl Ecto.Type - def load(value) do - {:ok, FlakeId.to_string(value)} - end - - @impl Ecto.Type - def dump(value) do - {:ok, FlakeId.from_string(value)} - end - - def autogenerate, do: get() - - # -- GenServer API - def start_link(_) do - :gen_server.start_link({:local, :flake}, __MODULE__, [], []) - end - - @impl GenServer - def init([]) do - {:ok, %FlakeId{node: worker_id(), time: time()}} - end - - @impl GenServer - def handle_call(:get, _from, state) do - {flake, new_state} = get(time(), state) - {:reply, flake, new_state} - end - - # Matches when the calling time is the same as the state time. Incr. sq - defp get(time, %FlakeId{time: time, node: node, sq: seq}) do - new_state = %FlakeId{time: time, node: node, sq: seq + 1} - {gen_flake(new_state), new_state} - end - - # Matches when the times are different, reset sq - defp get(newtime, %FlakeId{time: time, node: node}) when newtime > time do - new_state = %FlakeId{time: newtime, node: node, sq: 0} - {gen_flake(new_state), new_state} - end - - # Error when clock is running backwards - defp get(newtime, %FlakeId{time: time}) when newtime < time do - {:error, :clock_running_backwards} - end - - defp gen_flake(%FlakeId{time: time, node: node, sq: seq}) do - <> - end - - defp nthchar_base62(n) when n <= 9, do: ?0 + n - defp nthchar_base62(n) when n <= 35, do: ?A + n - 10 - defp nthchar_base62(n), do: ?a + n - 36 - - defp encode_base62(<>) do - integer - |> encode_base62([]) - |> List.to_string() - end - - defp encode_base62(int, acc) when int < 0, do: encode_base62(-int, acc) - defp encode_base62(int, []) when int == 0, do: '0' - defp encode_base62(int, acc) when int == 0, do: acc - - defp encode_base62(int, acc) do - r = rem(int, 62) - id = div(int, 62) - acc = [nthchar_base62(r) | acc] - encode_base62(id, acc) - end - - defp decode_base62(s) do - decode_base62(String.to_charlist(s), 0) - end - - defp decode_base62([c | cs], acc) when c >= ?0 and c <= ?9, - do: decode_base62(cs, 62 * acc + (c - ?0)) - - defp decode_base62([c | cs], acc) when c >= ?A and c <= ?Z, - do: decode_base62(cs, 62 * acc + (c - ?A + 10)) - - defp decode_base62([c | cs], acc) when c >= ?a and c <= ?z, - do: decode_base62(cs, 62 * acc + (c - ?a + 36)) - - defp decode_base62([], acc), do: acc - - defp time do - {mega_seconds, seconds, micro_seconds} = :erlang.timestamp() - 1_000_000_000 * mega_seconds + seconds * 1000 + :erlang.trunc(micro_seconds / 1000) - end - - defp worker_id do - <> = :crypto.strong_rand_bytes(6) - worker - end -end diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex index c572380c2..c5db1cb62 100644 --- a/lib/pleroma/list.ex +++ b/lib/pleroma/list.ex @@ -13,7 +13,7 @@ defmodule Pleroma.List do alias Pleroma.User schema "lists" do - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) field(:title, :string) field(:following, {:array, :string}, default: []) field(:ap_id, :string) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 8012389ac..d94ae5971 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -22,8 +22,8 @@ defmodule Pleroma.Notification do schema "notifications" do field(:seen, :boolean, default: false) - belongs_to(:user, User, type: Pleroma.FlakeId) - belongs_to(:activity, Activity, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) + belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) timestamps() end diff --git a/lib/pleroma/password_reset_token.ex b/lib/pleroma/password_reset_token.ex index 4a833f6a5..db398b1fc 100644 --- a/lib/pleroma/password_reset_token.ex +++ b/lib/pleroma/password_reset_token.ex @@ -12,7 +12,7 @@ defmodule Pleroma.PasswordResetToken do alias Pleroma.User schema "password_reset_tokens" do - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) field(:token, :string) field(:used, :boolean, default: false) diff --git a/lib/pleroma/registration.ex b/lib/pleroma/registration.ex index 21fd1fc3f..8544461db 100644 --- a/lib/pleroma/registration.ex +++ b/lib/pleroma/registration.ex @@ -11,10 +11,10 @@ defmodule Pleroma.Registration do alias Pleroma.Repo alias Pleroma.User - @primary_key {:id, Pleroma.FlakeId, autogenerate: true} + @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} schema "registrations" do - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) field(:provider, :string) field(:uid, :string) field(:info, :map, default: %{}) diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex index de0e54699..fea2cf3ff 100644 --- a/lib/pleroma/scheduled_activity.ex +++ b/lib/pleroma/scheduled_activity.ex @@ -17,7 +17,7 @@ defmodule Pleroma.ScheduledActivity do @min_offset :timer.minutes(5) schema "scheduled_activities" do - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) field(:scheduled_at, :naive_datetime) field(:params, :map) diff --git a/lib/pleroma/thread_mute.ex b/lib/pleroma/thread_mute.ex index 10d31679d..65cbbede3 100644 --- a/lib/pleroma/thread_mute.ex +++ b/lib/pleroma/thread_mute.ex @@ -12,7 +12,7 @@ defmodule Pleroma.ThreadMute do require Ecto.Query schema "thread_mutes" do - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) field(:context, :string) end @@ -24,7 +24,7 @@ defmodule Pleroma.ThreadMute do end def query(user_id, context) do - user_id = Pleroma.FlakeId.from_string(user_id) + {:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id) ThreadMute |> Ecto.Query.where(user_id: ^user_id) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index fb1f24254..b168d50a9 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -34,7 +34,7 @@ defmodule Pleroma.User do @type t :: %__MODULE__{} - @primary_key {:id, Pleroma.FlakeId, autogenerate: true} + @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ @@ -591,7 +591,7 @@ defmodule Pleroma.User do restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) cond do - is_integer(nickname_or_id) or Pleroma.FlakeId.is_flake_id?(nickname_or_id) -> + is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) -> get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id) restrict_to_local == false -> diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index e1e90d667..2486df944 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -510,7 +510,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) :: - Pleroma.FlakeId.t() | nil + FlakeId.Ecto.CompatType.t() | nil def fetch_latest_activity_id_for_context(context, opts \\ %{}) do context |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts)) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 6958c7511..633504a4b 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -25,7 +25,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do # This is a hack for twidere. def get_by_id_or_ap_id(id) do activity = - with true <- Pleroma.FlakeId.is_flake_id?(id), + with true <- FlakeId.flake_id?(id), %Activity{} = activity <- Activity.get_by_id_with_object(id) do activity else diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex index d53e20d12..ed42a34f3 100644 --- a/lib/pleroma/web/oauth/authorization.ex +++ b/lib/pleroma/web/oauth/authorization.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.Authorization do field(:scopes, {:array, :string}, default: []) field(:valid_until, :naive_datetime_usec) field(:used, :boolean, default: false) - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:app, App) timestamps() diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 40f131b57..8ea373805 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Web.OAuth.Token do field(:refresh_token, :string) field(:scopes, {:array, :string}, default: []) field(:valid_until, :naive_datetime_usec) - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:app, App) timestamps() diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index da301fbbc..988fabaeb 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.Push.Subscription do @type t :: %__MODULE__{} schema "push_subscriptions" do - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:token, Token) field(:endpoint, :string) field(:key_p256dh, :string) diff --git a/lib/pleroma/web/websub/websub_client_subscription.ex b/lib/pleroma/web/websub/websub_client_subscription.ex index 77703c496..23a04b87d 100644 --- a/lib/pleroma/web/websub/websub_client_subscription.ex +++ b/lib/pleroma/web/websub/websub_client_subscription.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.Websub.WebsubClientSubscription do field(:state, :string) field(:subscribers, {:array, :string}, default: []) field(:hub, :string) - belongs_to(:user, User, type: Pleroma.FlakeId) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) timestamps() end -- cgit v1.2.3 From cdbe7cd37ab8209acc8979ff5a3132b71feda869 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 24 Sep 2019 09:27:34 +0300 Subject: When listing emoji packs, be sure to create the directory --- .../pleroma_api/controllers/emoji_api_controller.ex | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 370bee9c3..be1f187ec 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -17,7 +17,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do a map of "pack directory name" to pack.json contents. """ def list_packs(conn, _params) do - with {:ok, results} <- File.ls(@emoji_dir_path) do + # Create the directory first if it does not exist. This is probably the first request made + # with the API so it should be sufficient + with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(@emoji_dir_path)}, + {:ls, {:ok, results}} <- {:ls, File.ls(@emoji_dir_path)} do pack_infos = results |> Enum.filter(&has_pack_json?/1) @@ -28,6 +31,19 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do |> Enum.into(%{}) json(conn, pack_infos) + else + {:create_dir, {:error, e}} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Failed to create the emoji pack directory at #{@emoji_dir_path}: #{e}"}) + + {:ls, {:error, e}} -> + conn + |> put_status(:internal_server_error) + |> json(%{ + error: + "Failed to get the contents of the emoji pack directory at #{@emoji_dir_path}: #{e}" + }) end end -- cgit v1.2.3 From f21dbbc021ff6d1e97695e08b93acc906fc861f6 Mon Sep 17 00:00:00 2001 From: vaartis Date: Tue, 24 Sep 2019 12:11:25 +0000 Subject: Move emoji_dir_path & cache_seconds_per_file --- .../controllers/emoji_api_controller.ex | 51 +++++++++++----------- 1 file changed, 26 insertions(+), 25 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index be1f187ec..e8c4f57a7 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -3,12 +3,12 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do require Logger - @emoji_dir_path Path.join( - Pleroma.Config.get!([:instance, :static_dir]), - "emoji" - ) - - @cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) + def emoji_dir_path() do + Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + end @doc """ Lists the packs available on the instance as JSON. @@ -19,8 +19,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do def list_packs(conn, _params) do # Create the directory first if it does not exist. This is probably the first request made # with the API so it should be sufficient - with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(@emoji_dir_path)}, - {:ls, {:ok, results}} <- {:ls, File.ls(@emoji_dir_path)} do + with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_dir_path())}, + {:ls, {:ok, results}} <- {:ls, File.ls(emoji_dir_path())} do pack_infos = results |> Enum.filter(&has_pack_json?/1) @@ -35,33 +35,33 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do {:create_dir, {:error, e}} -> conn |> put_status(:internal_server_error) - |> json(%{error: "Failed to create the emoji pack directory at #{@emoji_dir_path}: #{e}"}) + |> json(%{error: "Failed to create the emoji pack directory at #{emoji_dir_path()}: #{e}"}) {:ls, {:error, e}} -> conn |> put_status(:internal_server_error) |> json(%{ error: - "Failed to get the contents of the emoji pack directory at #{@emoji_dir_path}: #{e}" + "Failed to get the contents of the emoji pack directory at #{emoji_dir_path()}: #{e}" }) end end defp has_pack_json?(file) do - dir_path = Path.join(@emoji_dir_path, file) + dir_path = Path.join(emoji_dir_path(), file) # Filter to only use the pack.json packs File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json")) end defp load_pack(pack_name) do - pack_path = Path.join(@emoji_dir_path, pack_name) + pack_path = Path.join(emoji_dir_path(), pack_name) pack_file = Path.join(pack_path, "pack.json") {pack_name, Jason.decode!(File.read!(pack_file))} end defp validate_pack({name, pack}) do - pack_path = Path.join(@emoji_dir_path, name) + pack_path = Path.join(emoji_dir_path(), name) if can_download?(pack, pack_path) do archive_for_sha = make_archive(name, pack, pack_path) @@ -95,7 +95,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do {:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)]) - cache_ms = :timer.seconds(@cache_seconds_per_file * Enum.count(files)) + cache_seconds_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file]) + cache_ms = :timer.seconds(cache_seconds_per_file * Enum.count(files)) Cachex.put!( :emoji_packs_cache, @@ -131,7 +132,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") to download packs that the instance shares. """ def download_shared(conn, %{"name" => name}) do - pack_dir = Path.join(@emoji_dir_path, name) + pack_dir = Path.join(emoji_dir_path(), name) pack_file = Path.join(pack_dir, "pack.json") with {_, true} <- {:exists?, File.exists?(pack_file)}, @@ -211,7 +212,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") %{body: emoji_archive} <- Tesla.get!(uri), {_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do local_name = data["as"] || name - pack_dir = Path.join(@emoji_dir_path, local_name) + pack_dir = Path.join(emoji_dir_path(), local_name) File.mkdir_p!(pack_dir) files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end) @@ -249,7 +250,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") Creates an empty pack named `name` which then can be updated via the admin UI. """ def create(conn, %{"name" => name}) do - pack_dir = Path.join(@emoji_dir_path, name) + pack_dir = Path.join(emoji_dir_path(), name) if not File.exists?(pack_dir) do File.mkdir_p!(pack_dir) @@ -273,7 +274,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") Deletes the pack `name` and all it's files. """ def delete(conn, %{"name" => name}) do - pack_dir = Path.join(@emoji_dir_path, name) + pack_dir = Path.join(emoji_dir_path(), name) case File.rm_rf(pack_dir) do {:ok, _} -> @@ -292,7 +293,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") `new_data` is the new metadata for the pack, that will replace the old metadata. """ def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do - pack_file_p = Path.join([@emoji_dir_path, name, "pack.json"]) + pack_file_p = Path.join([emoji_dir_path(), name, "pack.json"]) full_pack = Jason.decode!(File.read!(pack_file_p)) @@ -376,7 +377,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") conn, %{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params ) do - pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_dir = Path.join(emoji_dir_path(), pack_name) pack_file_p = Path.join(pack_dir, "pack.json") full_pack = Jason.decode!(File.read!(pack_file_p)) @@ -424,7 +425,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") "action" => "remove", "shortcode" => shortcode }) do - pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_dir = Path.join(emoji_dir_path(), pack_name) pack_file_p = Path.join(pack_dir, "pack.json") full_pack = Jason.decode!(File.read!(pack_file_p)) @@ -459,7 +460,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") conn, %{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params ) do - pack_dir = Path.join(@emoji_dir_path, pack_name) + pack_dir = Path.join(emoji_dir_path(), pack_name) pack_file_p = Path.join(pack_dir, "pack.json") full_pack = Jason.decode!(File.read!(pack_file_p)) @@ -529,11 +530,11 @@ keeping it in cache for #{div(cache_ms, 1000)}s") assumed to be emojis and stored in the new `pack.json` file. """ def import_from_fs(conn, _params) do - with {:ok, results} <- File.ls(@emoji_dir_path) do + with {:ok, results} <- File.ls(emoji_dir_path()) do imported_pack_names = results |> Enum.filter(fn file -> - dir_path = Path.join(@emoji_dir_path, file) + dir_path = Path.join(emoji_dir_path(), file) # Find the directories that do NOT have pack.json File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) end) @@ -549,7 +550,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end defp write_pack_json_contents(dir) do - dir_path = Path.join(@emoji_dir_path, dir) + dir_path = Path.join(emoji_dir_path(), dir) emoji_txt_path = Path.join(dir_path, "emoji.txt") files_for_pack = files_for_pack(emoji_txt_path, dir_path) -- cgit v1.2.3 From a6e85215e1bd88e5cda71f75d0d748e58e227cca Mon Sep 17 00:00:00 2001 From: vaartis Date: Tue, 24 Sep 2019 12:15:52 +0000 Subject: Credo fix (remove parens on function definition) --- lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index e8c4f57a7..b7eede6c9 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -3,7 +3,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do require Logger - def emoji_dir_path() do + def emoji_dir_path do Path.join( Pleroma.Config.get!([:instance, :static_dir]), "emoji" -- cgit v1.2.3 From ba9d35a9049e0d46900d2dd95afd27c09f327a2c Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 24 Sep 2019 19:18:07 +0300 Subject: Add an API endpoint for listing remote packs --- .../controllers/emoji_api_controller.ex | 54 ++++++++++++++++------ lib/pleroma/web/router.ex | 1 + 2 files changed, 40 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index b7eede6c9..cf5a086fe 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -10,6 +10,27 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do ) end + @doc """ + Lists packs from the remote instance. + + Since JS cannot ask remote instances for their packs due to CPS, it has to + be done by the server + """ + def list_from(conn, %{"instance_address" => address}) do + address = String.trim(address) + + if shareable_packs_available(address) do + list_resp = + "#{address}/api/pleroma/emoji/packs" |> Tesla.get!() |> Map.get(:body) |> Jason.decode!() + + json(conn, list_resp) + else + conn + |> put_status(:internal_server_error) + |> json(%{error: "The requested instance does not support sharing emoji packs"}) + end + end + @doc """ Lists the packs available on the instance as JSON. @@ -156,6 +177,21 @@ keeping it in cache for #{div(cache_ms, 1000)}s") end end + defp shareable_packs_available(address) do + "#{address}/.well-known/nodeinfo" + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> List.last() + |> Map.get("href") + # Get the actual nodeinfo address and fetch it + |> Tesla.get!() + |> Map.get(:body) + |> Jason.decode!() + |> get_in(["metadata", "features"]) + |> Enum.member?("shareable_emoji_packs") + end + @doc """ An admin endpoint to request downloading a pack named `pack_name` from the instance `instance_address`. @@ -164,21 +200,9 @@ keeping it in cache for #{div(cache_ms, 1000)}s") from that instance, otherwise it will be downloaded from the fallback source, if there is one. """ def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do - shareable_packs_available = - "#{address}/.well-known/nodeinfo" - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> List.last() - |> Map.get("href") - # Get the actual nodeinfo address and fetch it - |> Tesla.get!() - |> Map.get(:body) - |> Jason.decode!() - |> get_in(["metadata", "features"]) - |> Enum.member?("shareable_emoji_packs") - - if shareable_packs_available do + address = String.trim(address) + + if shareable_packs_available(address) do full_pack = "#{address}/api/pleroma/emoji/packs/list" |> Tesla.get!() diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index e583093d2..8bc051936 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -222,6 +222,7 @@ defmodule Pleroma.Web.Router do put("/:name", EmojiAPIController, :create) delete("/:name", EmojiAPIController, :delete) post("/download_from", EmojiAPIController, :download_from) + post("/list_from", EmojiAPIController, :list_from) end scope "/packs" do -- cgit v1.2.3 From 118d6dcdf4b2c81b4cbe51fd43977722b3eee164 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 24 Sep 2019 19:38:05 +0300 Subject: Fix nodeinfo handling --- lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index cf5a086fe..545ad80c9 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -182,6 +182,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") |> Tesla.get!() |> Map.get(:body) |> Jason.decode!() + |> Map.get("links") |> List.last() |> Map.get("href") # Get the actual nodeinfo address and fetch it -- cgit v1.2.3 From 1fd9c60f8706441d38eb4c17417df80e3cf220b1 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 24 Sep 2019 22:20:48 +0300 Subject: Fix emoji tags for shareable packs to be "pack:{name}" --- lib/pleroma/emoji/loader.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index a29de0a33..4f4ee51d1 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -99,7 +99,7 @@ defmodule Pleroma.Emoji.Loader do contents["files"] |> Enum.map(fn {name, rel_file} -> filename = Path.join("/emoji/#{pack_name}", rel_file) - {name, filename, pack_name} + {name, filename, ["pack:#{pack_name}"]} end) else # Load from emoji.txt / all files -- cgit v1.2.3 From d87be2ec96912b147ad8fb6b17c1ee00d7d30a7f Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 15:59:04 +0300 Subject: Don't embed the first page in inboxes/outboxes and refactor the views to follow View/Controller pattern Note that I mentioned the change in 1.1 section because I intend to backport this, if this is not needed I will move it back to Unreleased. --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- .../web/activity_pub/activity_pub_controller.ex | 70 +++++++++++++++--- lib/pleroma/web/activity_pub/views/user_view.ex | 83 +++------------------- 3 files changed, 74 insertions(+), 81 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1cf8b6151..a97afa665 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -834,7 +834,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted_reblogs(query, _), do: query - defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query + defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query defp exclude_poll_votes(query, _) do if has_named_binding?(query, :object) do diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 9eb86106f..c3e7edf57 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -231,13 +231,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do end end - def outbox(conn, %{"nickname" => nickname} = params) do + def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) + when page? in [true, "true"] do + with %User{} = user <- User.get_cached_by_nickname(nickname), + {:ok, user} <- User.ensure_keys_present(user), + activities <- + (if params["max_id"] do + ActivityPub.fetch_user_activities(user, nil, %{ + "max_id" => params["max_id"], + # This is a hack because postgres generates inefficient queries when filtering by 'Answer', + # poll votes will be hidden by the visibility filter in this case anyway + "include_poll_votes" => true, + "limit" => 10 + }) + else + ActivityPub.fetch_user_activities(user, nil, %{ + "limit" => 10, + "include_poll_votes" => true + }) + end) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection_page.json", %{ + activities: activities, + iri: "#{user.ap_id}/outbox" + }) + end + end + + def outbox(conn, %{"nickname" => nickname}) do with %User{} = user <- User.get_cached_by_nickname(nickname), {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) - |> render("outbox.json", %{user: user, max_id: params["max_id"]}) + |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"}) end end @@ -315,12 +344,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do def read_inbox( %{assigns: %{user: %{nickname: nickname} = user}} = conn, - %{"nickname" => nickname} = params - ) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("inbox.json", user: user, max_id: params["max_id"]) + %{"nickname" => nickname, "page" => page?} = params + ) + when page? in [true, "true"] do + with activities <- + (if params["max_id"] do + ActivityPub.fetch_activities([user.ap_id | user.following], %{ + "max_id" => params["max_id"], + "limit" => 10 + }) + else + ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10}) + end) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection_page.json", %{ + activities: activities, + iri: "#{user.ap_id}/inbox" + }) + end + end + + def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ + "nickname" => nickname + }) do + with {:ok, user} <- User.ensure_keys_present(user) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"}) + end end def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 352d856fa..5dbb5992f 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do alias Pleroma.Keys alias Pleroma.Repo alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Endpoint @@ -210,20 +209,16 @@ defmodule Pleroma.Web.ActivityPub.UserView do |> Map.merge(Utils.make_json_ld_header()) end - def render("outbox.json", %{user: user, max_id: max_qid}) do - params = %{ - "limit" => "10" + def render("activity_collection.json", %{iri: iri}) do + %{ + "id" => iri, + "type" => "OrderedCollection", + "first" => "#{iri}?page=true" } + |> Map.merge(Utils.make_json_ld_header()) + end - params = - if max_qid != nil do - Map.put(params, "max_id", max_qid) - else - params - end - - activities = ActivityPub.fetch_user_activities(user, nil, params) - + def render("activity_collection_page.json", %{activities: activities, iri: iri}) do # this is sorted chronologically, so first activity is the newest (max) {max_id, min_id, collection} = if length(activities) > 0 do @@ -243,71 +238,15 @@ defmodule Pleroma.Web.ActivityPub.UserView do } end - iri = "#{user.ap_id}/outbox" - page = %{ - "id" => "#{iri}?max_id=#{max_id}", + "id" => "#{iri}?max_id=#{max_id}&page=true", "type" => "OrderedCollectionPage", "partOf" => iri, "orderedItems" => collection, - "next" => "#{iri}?max_id=#{min_id}" + "next" => "#{iri}?max_id=#{min_id}&page=true" } - if max_qid == nil do - %{ - "id" => iri, - "type" => "OrderedCollection", - "first" => page - } - |> Map.merge(Utils.make_json_ld_header()) - else - page |> Map.merge(Utils.make_json_ld_header()) - end - end - - def render("inbox.json", %{user: user, max_id: max_qid}) do - params = %{ - "limit" => "10" - } - - params = - if max_qid != nil do - Map.put(params, "max_id", max_qid) - else - params - end - - activities = ActivityPub.fetch_activities([user.ap_id | user.following], params) - - min_id = Enum.at(Enum.reverse(activities), 0).id - max_id = Enum.at(activities, 0).id - - collection = - Enum.map(activities, fn act -> - {:ok, data} = Transmogrifier.prepare_outgoing(act.data) - data - end) - - iri = "#{user.ap_id}/inbox" - - page = %{ - "id" => "#{iri}?max_id=#{max_id}", - "type" => "OrderedCollectionPage", - "partOf" => iri, - "orderedItems" => collection, - "next" => "#{iri}?max_id=#{min_id}" - } - - if max_qid == nil do - %{ - "id" => iri, - "type" => "OrderedCollection", - "first" => page - } - |> Map.merge(Utils.make_json_ld_header()) - else - page |> Map.merge(Utils.make_json_ld_header()) - end + page |> Map.merge(Utils.make_json_ld_header()) end def collection(collection, iri, page, show_items \\ true, total \\ nil) do -- cgit v1.2.3 From 1ddd403339655674ca634a876151c4346c87c515 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 13:20:48 +0000 Subject: Apply suggestion to lib/pleroma/web/activity_pub/activity_pub_controller.ex --- .../web/activity_pub/activity_pub_controller.ex | 33 +++++++++++----------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index c3e7edf57..aa1620009 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -234,22 +234,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) when page? in [true, "true"] do with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- User.ensure_keys_present(user), - activities <- - (if params["max_id"] do - ActivityPub.fetch_user_activities(user, nil, %{ - "max_id" => params["max_id"], - # This is a hack because postgres generates inefficient queries when filtering by 'Answer', - # poll votes will be hidden by the visibility filter in this case anyway - "include_poll_votes" => true, - "limit" => 10 - }) - else - ActivityPub.fetch_user_activities(user, nil, %{ - "limit" => 10, - "include_poll_votes" => true - }) - end) do + {:ok, user} <- User.ensure_keys_present(user) do + activities = + if params["max_id"] do + ActivityPub.fetch_user_activities(user, nil, %{ + "max_id" => params["max_id"], + # This is a hack because postgres generates inefficient queries when filtering by 'Answer', + # poll votes will be hidden by the visibility filter in this case anyway + "include_poll_votes" => true, + "limit" => 10 + }) + else + ActivityPub.fetch_user_activities(user, nil, %{ + "limit" => 10, + "include_poll_votes" => true + }) + end + conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) -- cgit v1.2.3 From c7d8ccd0c417aab59253a446ed0ffc973448536e Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 16:26:47 +0300 Subject: Remove useless with clause --- .../web/activity_pub/activity_pub_controller.ex | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index aa1620009..60abe1e1d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -348,23 +348,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do %{"nickname" => nickname, "page" => page?} = params ) when page? in [true, "true"] do - with activities <- - (if params["max_id"] do - ActivityPub.fetch_activities([user.ap_id | user.following], %{ - "max_id" => params["max_id"], - "limit" => 10 - }) - else - ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10}) - end) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("activity_collection_page.json", %{ - activities: activities, - iri: "#{user.ap_id}/inbox" - }) - end + activities = + if params["max_id"] do + ActivityPub.fetch_activities([user.ap_id | user.following], %{ + "max_id" => params["max_id"], + "limit" => 10 + }) + else + ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10}) + end + + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection_page.json", %{ + activities: activities, + iri: "#{user.ap_id}/inbox" + }) end def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ -- cgit v1.2.3 From f2880d7d29836b16ae6825fbda85c21496fc42b5 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 16:36:46 +0300 Subject: Credo considered harmful --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 60abe1e1d..8112f6642 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -239,8 +239,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do if params["max_id"] do ActivityPub.fetch_user_activities(user, nil, %{ "max_id" => params["max_id"], - # This is a hack because postgres generates inefficient queries when filtering by 'Answer', - # poll votes will be hidden by the visibility filter in this case anyway + # This is a hack because postgres generates inefficient queries when filtering by + # 'Answer', poll votes will be hidden by the visibility filter in this case anyway "include_poll_votes" => true, "limit" => 10 }) -- cgit v1.2.3 From f92d7d52c20e951d31f0dedc16bde3aeb6687374 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 13:38:45 +0000 Subject: Apply suggestion to lib/pleroma/web/activity_pub/views/user_view.ex --- lib/pleroma/web/activity_pub/views/user_view.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 5dbb5992f..4e37be5db 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -238,15 +238,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do } end - page = %{ + %{ "id" => "#{iri}?max_id=#{max_id}&page=true", "type" => "OrderedCollectionPage", "partOf" => iri, "orderedItems" => collection, "next" => "#{iri}?max_id=#{min_id}&page=true" } - - page |> Map.merge(Utils.make_json_ld_header()) + |> Map.merge(Utils.make_json_ld_header()) end def collection(collection, iri, page, show_items \\ true, total \\ nil) do -- cgit v1.2.3 From 5fb72170a72e61f0b8035fba63b7bbdff7acde05 Mon Sep 17 00:00:00 2001 From: Hakaba Hitoyo Date: Thu, 26 Sep 2019 02:57:41 +0000 Subject: Revert "add _discoverable_ keyword into ActivityPub @context" This reverts commit 3aef4bdf8f37efd1055a84c5fca12ec4559a17f5. --- lib/pleroma/user/info.ex | 8 ++++++-- lib/pleroma/web/activity_pub/activity_pub.ex | 4 +++- lib/pleroma/web/activity_pub/views/user_view.ex | 3 ++- .../web/mastodon_api/controllers/mastodon_api_controller.ex | 3 ++- lib/pleroma/web/mastodon_api/views/account_view.ex | 6 +++++- 5 files changed, 18 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 99745f496..1d0f0c7f4 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -54,6 +54,7 @@ defmodule Pleroma.User.Info do field(:pleroma_settings_store, :map, default: %{}) field(:fields, {:array, :map}, default: nil) field(:raw_fields, {:array, :map}, default: []) + field(:discoverable, :boolean, default: false) field(:notification_settings, :map, default: %{ @@ -277,7 +278,8 @@ defmodule Pleroma.User.Info do :hide_follows_count, :follower_count, :fields, - :following_count + :following_count, + :discoverable ]) |> validate_fields(true) end @@ -295,6 +297,7 @@ defmodule Pleroma.User.Info do :hide_follows, :fields, :hide_followers, + :discoverable, :hide_followers_count, :hide_follows_count ]) @@ -318,7 +321,8 @@ defmodule Pleroma.User.Info do :skip_thread_containment, :fields, :raw_fields, - :pleroma_settings_store + :pleroma_settings_store, + :discoverable ]) |> validate_fields() end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index ff29efd43..8d0a57623 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1001,6 +1001,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do locked = data["manuallyApprovesFollowers"] || false data = Transmogrifier.maybe_fix_user_object(data) + discoverable = data["discoverable"] || false user_data = %{ ap_id: data["id"], @@ -1009,7 +1010,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do source_data: data, banner: banner, fields: fields, - locked: locked + locked: locked, + discoverable: discoverable }, avatar: avatar, name: data["name"], diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 4e37be5db..993307287 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -106,7 +106,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do }, "endpoints" => endpoints, "attachment" => fields, - "tag" => (user.info.source_data["tag"] || []) ++ emoji_tags + "tag" => (user.info.source_data["tag"] || []) ++ emoji_tags, + "discoverable" => user.info.discoverable } |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user)) diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index bb81b061e..239cfac9f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -153,7 +153,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do :hide_follows, :hide_favorites, :show_role, - :skip_thread_containment + :skip_thread_containment, + :discoverable ] |> Enum.reduce(%{}, fn key, acc -> add_if_present(acc, params, to_string(key), key, fn value -> diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 195dd124b..a23aeea9b 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -116,6 +116,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for])) relationship = render("relationship.json", %{user: opts[:for], target: user}) + discoverable = user.info.discoverable + %{ id: to_string(user.id), username: username_from_nickname(user.nickname), @@ -139,7 +141,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do note: HTML.strip_tags((user.bio || "") |> String.replace("
", "\n")), sensitive: false, fields: raw_fields, - pleroma: %{} + pleroma: %{ + discoverable: discoverable + } }, # Pleroma extension -- cgit v1.2.3 From eed774d058bfac2d36fd79faa915394a97baa6db Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 24 Sep 2019 16:10:54 +0700 Subject: Add CommonAPI.ActivityDraft --- lib/pleroma/web/common_api/activity_draft.ex | 222 +++++++++++++++++++++ lib/pleroma/web/common_api/common_api.ex | 103 ++-------- lib/pleroma/web/common_api/utils.ex | 126 ++++++------ lib/pleroma/web/controller_helper.ex | 2 +- .../controllers/mastodon_api_controller.ex | 51 +++-- 5 files changed, 325 insertions(+), 179 deletions(-) create mode 100644 lib/pleroma/web/common_api/activity_draft.ex (limited to 'lib') diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex new file mode 100644 index 000000000..b4480bd18 --- /dev/null +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -0,0 +1,222 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.CommonAPI.ActivityDraft do + alias Pleroma.Activity + alias Pleroma.Conversation.Participation + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.CommonAPI.Utils + + import Pleroma.Web.Gettext + + defstruct valid?: true, + errors: [], + user: nil, + params: %{}, + status: nil, + summary: nil, + full_payload: nil, + attachments: [], + in_reply_to: nil, + in_reply_to_conversation: nil, + visibility: nil, + expires_at: nil, + poll: nil, + emoji: %{}, + content_html: nil, + mentions: [], + tags: [], + to: [], + cc: [], + context: nil, + sensitive: false, + object: nil, + preview?: false, + changes: %{} + + def create(user, params) do + %__MODULE__{user: user} + |> put_params(params) + |> status() + |> summary() + |> attachments() + |> full_payload() + |> in_reply_to() + |> in_reply_to_conversation() + |> visibility() + |> expires_at() + |> poll() + |> content() + |> to_and_cc() + |> context() + |> sensitive() + |> object() + |> preview?() + |> changes() + |> validate() + end + + defp put_params(draft, params) do + params = Map.put_new(params, "in_reply_to_status_id", params["in_reply_to_id"]) + %__MODULE__{draft | params: params} + end + + defp status(%{params: %{"status" => status}} = draft) do + %__MODULE__{draft | status: String.trim(status)} + end + + defp summary(%{params: params} = draft) do + %__MODULE__{draft | summary: Map.get(params, "spoiler_text", "")} + end + + defp full_payload(%{status: status, summary: summary} = draft) do + full_payload = String.trim(status <> summary) + + case Utils.validate_character_limit(full_payload, draft.attachments) do + :ok -> %__MODULE__{draft | full_payload: full_payload} + {:error, message} -> add_error(draft, message) + end + end + + defp attachments(%{params: params} = draft) do + attachments = Utils.attachments_from_ids(params) + %__MODULE__{draft | attachments: attachments} + end + + defp in_reply_to(draft) do + case Map.get(draft.params, "in_reply_to_status_id") do + "" -> draft + nil -> draft + id -> %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)} + end + end + + defp in_reply_to_conversation(draft) do + in_reply_to_conversation = Participation.get(draft.params["in_reply_to_conversation_id"]) + %__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation} + end + + defp visibility(%{params: params} = draft) do + case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do + {visibility, "direct"} when visibility != "direct" -> + add_error(draft, dgettext("errors", "The message visibility must be direct")) + + {visibility, _} -> + %__MODULE__{draft | visibility: visibility} + end + end + + defp expires_at(draft) do + case CommonAPI.check_expiry_date(draft.params["expires_in"]) do + {:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at} + {:error, message} -> add_error(draft, message) + end + end + + defp poll(draft) do + case Utils.make_poll_data(draft.params) do + {:ok, {poll, poll_emoji}} -> + %__MODULE__{draft | poll: poll, emoji: Map.merge(draft.emoji, poll_emoji)} + + {:error, message} -> + add_error(draft, message) + end + end + + defp content(draft) do + {content_html, mentions, tags} = + Utils.make_content_html( + draft.status, + draft.attachments, + draft.params, + draft.visibility + ) + + %__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags} + end + + defp to_and_cc(%{valid?: false} = draft), do: draft + + defp to_and_cc(draft) do + addressed_users = + draft.mentions + |> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end) + |> Utils.get_addressed_users(draft.params["to"]) + + {to, cc} = + Utils.get_to_and_cc( + draft.user, + addressed_users, + draft.in_reply_to, + draft.visibility, + draft.in_reply_to_conversation + ) + + %__MODULE__{draft | to: to, cc: cc} + end + + defp context(draft) do + context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation) + %__MODULE__{draft | context: context} + end + + defp sensitive(draft) do + sensitive = draft.params["sensitive"] || Enum.member?(draft.tags, {"#nsfw", "nsfw"}) + %__MODULE__{draft | sensitive: sensitive} + end + + defp object(%{valid?: false} = draft), do: draft + + defp object(draft) do + emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji) + + object = + Utils.make_note_data( + draft.user.ap_id, + draft.to, + draft.context, + draft.content_html, + draft.attachments, + draft.in_reply_to, + draft.tags, + draft.summary, + draft.cc, + draft.sensitive, + draft.poll + ) + |> Map.put("emoji", emoji) + + %__MODULE__{draft | object: object} + end + + defp preview?(draft) do + preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false + %__MODULE__{draft | preview?: preview?} + end + + defp changes(%{valid?: false} = draft), do: draft + + defp changes(draft) do + direct? = draft.visibility == "direct" + + changes = + %{ + to: draft.to, + actor: draft.user, + context: draft.context, + object: draft.object, + additional: %{"cc" => draft.cc, "directMessage" => direct?} + } + |> Utils.maybe_add_list_data(draft.user, draft.visibility) + + %__MODULE__{draft | changes: changes} + end + + defp add_error(draft, message) do + %__MODULE__{draft | valid?: false, errors: [message | draft.errors]} + end + + defp validate(%{valid?: true} = draft), do: {:ok, draft} + defp validate(%{errors: [message | _]}), do: {:error, message} +end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 4a74dc16f..d34bb7285 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity alias Pleroma.ActivityExpiration alias Pleroma.Conversation.Participation - alias Pleroma.Emoji alias Pleroma.Object alias Pleroma.ThreadMute alias Pleroma.User @@ -173,9 +172,7 @@ defmodule Pleroma.Web.CommonAPI do end) end - def get_visibility(_, _, %Participation{}) do - {"direct", "direct"} - end + def get_visibility(_, _, %Participation{}), do: {"direct", "direct"} def get_visibility(%{"visibility" => visibility}, in_reply_to, _) when visibility in ~w{public unlisted private direct}, @@ -201,9 +198,9 @@ defmodule Pleroma.Web.CommonAPI do end end - defp check_expiry_date({:ok, nil} = res), do: res + def check_expiry_date({:ok, nil} = res), do: res - defp check_expiry_date({:ok, in_seconds}) do + def check_expiry_date({:ok, in_seconds}) do expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds) if ActivityExpiration.expires_late_enough?(expiry) do @@ -213,97 +210,27 @@ defmodule Pleroma.Web.CommonAPI do end end - defp check_expiry_date(expiry_str) do + def check_expiry_date(expiry_str) do Ecto.Type.cast(:integer, expiry_str) |> check_expiry_date() end - def post(user, %{"status" => status} = data) do - limit = Pleroma.Config.get([:instance, :limit]) - - with status <- String.trim(status), - attachments <- attachments_from_ids(data), - in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]), - in_reply_to_conversation <- Participation.get(data["in_reply_to_conversation_id"]), - {visibility, in_reply_to_visibility} <- - get_visibility(data, in_reply_to, in_reply_to_conversation), - {_, false} <- - {:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"}, - {content_html, mentions, tags} <- - make_content_html( - status, - attachments, - data, - visibility - ), - mentioned_users <- for({_, mentioned_user} <- mentions, do: mentioned_user.ap_id), - addressed_users <- get_addressed_users(mentioned_users, data["to"]), - {poll, poll_emoji} <- make_poll_data(data), - {to, cc} <- - get_to_and_cc(user, addressed_users, in_reply_to, visibility, in_reply_to_conversation), - context <- make_context(in_reply_to, in_reply_to_conversation), - cw <- data["spoiler_text"] || "", - sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}), - {:ok, expires_at} <- check_expiry_date(data["expires_in"]), - full_payload <- String.trim(status <> cw), - :ok <- validate_character_limit(full_payload, attachments, limit), - object <- - make_note_data( - user.ap_id, - to, - context, - content_html, - attachments, - in_reply_to, - tags, - cw, - cc, - sensitive, - poll - ), - object <- put_emoji(object, full_payload, poll_emoji) do - preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false - direct? = visibility == "direct" - - result = - %{ - to: to, - actor: user, - context: context, - object: object, - additional: %{"cc" => cc, "directMessage" => direct?} - } - |> maybe_add_list_data(user, visibility) - |> ActivityPub.create(preview?) - - if expires_at do - with {:ok, activity} <- result do - {:ok, _} = ActivityExpiration.create(activity, expires_at) - end - end - - result - else - {:private_to_public, true} -> - {:error, dgettext("errors", "The message visibility must be direct")} - - {:error, _} = e -> - e - - e -> - {:error, e} + def post(user, %{"status" => _} = data) do + with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do + draft.changes + |> ActivityPub.create(draft.preview?) + |> maybe_create_activity_expiration(draft.expires_at) end end - # parse and put emoji to object data - defp put_emoji(map, text, emojis) do - Map.put( - map, - "emoji", - Map.merge(Emoji.Formatter.get_emoji_map(text), emojis) - ) + defp maybe_create_activity_expiration({:ok, activity}, %NaiveDateTime{} = expires_at) do + with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do + {:ok, activity} + end end + defp maybe_create_activity_expiration(result, _), do: result + # Updates the emojis for a user based on their profile def update(user) do emoji = emoji_from_profile(user) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 52fbc162b..8093a56a6 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do import Pleroma.Web.Gettext + import Pleroma.Web.ControllerHelper, only: [truthy_param?: 1] alias Calendar.Strftime alias Pleroma.Activity @@ -41,14 +42,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do end end - def get_replied_to_activity(""), do: nil - - def get_replied_to_activity(id) when not is_nil(id) do - Activity.get_by_id(id) - end - - def get_replied_to_activity(_), do: nil - def attachments_from_ids(%{"media_ids" => ids, "descriptions" => desc} = _) do attachments_from_ids_descs(ids, desc) end @@ -159,70 +152,74 @@ defmodule Pleroma.Web.CommonAPI.Utils do def maybe_add_list_data(activity_params, _, _), do: activity_params + def make_poll_data(%{"poll" => %{"expires_in" => expires_in}} = data) + when is_binary(expires_in) do + # In some cases mastofe sends out strings instead of integers + data + |> put_in(["poll", "expires_in"], String.to_integer(expires_in)) + |> make_poll_data() + end + def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data) when is_list(options) do - %{max_expiration: max_expiration, min_expiration: min_expiration} = - limits = Pleroma.Config.get([:instance, :poll_limits]) - - # XXX: There is probably a cleaner way of doing this - try do - # In some cases mastofe sends out strings instead of integers - expires_in = if is_binary(expires_in), do: String.to_integer(expires_in), else: expires_in - - if Enum.count(options) > limits.max_options do - raise ArgumentError, message: "Poll can't contain more than #{limits.max_options} options" - end + limits = Pleroma.Config.get([:instance, :poll_limits]) - {poll, emoji} = + with :ok <- validate_poll_expiration(expires_in, limits), + :ok <- validate_poll_options_amount(options, limits), + :ok <- validate_poll_options_length(options, limits) do + {option_notes, emoji} = Enum.map_reduce(options, %{}, fn option, emoji -> - if String.length(option) > limits.max_option_chars do - raise ArgumentError, - message: - "Poll options cannot be longer than #{limits.max_option_chars} characters each" - end - - {%{ - "name" => option, - "type" => "Note", - "replies" => %{"type" => "Collection", "totalItems" => 0} - }, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))} - end) - - case expires_in do - expires_in when expires_in > max_expiration -> - raise ArgumentError, message: "Expiration date is too far in the future" - - expires_in when expires_in < min_expiration -> - raise ArgumentError, message: "Expiration date is too soon" + note = %{ + "name" => option, + "type" => "Note", + "replies" => %{"type" => "Collection", "totalItems" => 0} + } - _ -> - :noop - end + {note, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))} + end) end_time = NaiveDateTime.utc_now() |> NaiveDateTime.add(expires_in) |> NaiveDateTime.to_iso8601() - poll = - if Pleroma.Web.ControllerHelper.truthy_param?(data["poll"]["multiple"]) do - %{"type" => "Question", "anyOf" => poll, "closed" => end_time} - else - %{"type" => "Question", "oneOf" => poll, "closed" => end_time} - end + key = if truthy_param?(data["poll"]["multiple"]), do: "anyOf", else: "oneOf" + poll = %{"type" => "Question", key => option_notes, "closed" => end_time} - {poll, emoji} - rescue - e in ArgumentError -> e.message + {:ok, {poll, emoji}} end end def make_poll_data(%{"poll" => poll}) when is_map(poll) do - "Invalid poll" + {:error, "Invalid poll"} end def make_poll_data(_data) do - {%{}, %{}} + {:ok, {%{}, %{}}} + end + + defp validate_poll_options_amount(options, %{max_options: max_options}) do + if Enum.count(options) > max_options do + {:error, "Poll can't contain more than #{max_options} options"} + else + :ok + end + end + + defp validate_poll_options_length(options, %{max_option_chars: max_option_chars}) do + if Enum.any?(options, &(String.length(&1) > max_option_chars)) do + {:error, "Poll options cannot be longer than #{max_option_chars} characters each"} + else + :ok + end + end + + defp validate_poll_expiration(expires_in, %{min_expiration: min, max_expiration: max}) do + cond do + expires_in > max -> {:error, "Expiration date is too far in the future"} + expires_in < min -> {:error, "Expiration date is too soon"} + true -> :ok + end end def make_content_html( @@ -347,25 +344,25 @@ defmodule Pleroma.Web.CommonAPI.Utils do attachments, in_reply_to, tags, - cw \\ nil, + summary \\ nil, cc \\ [], sensitive \\ false, - merge \\ %{} + extra_params \\ %{} ) do %{ "type" => "Note", "to" => to, "cc" => cc, "content" => content_html, - "summary" => cw, - "sensitive" => !Enum.member?(["false", "False", "0", false], sensitive), + "summary" => summary, + "sensitive" => truthy_param?(sensitive), "context" => context, "attachment" => attachments, "actor" => actor, "tag" => Keyword.values(tags) |> Enum.uniq() } |> add_in_reply_to(in_reply_to) - |> Map.merge(merge) + |> Map.merge(extra_params) end defp add_in_reply_to(object, nil), do: object @@ -571,15 +568,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do } end - def validate_character_limit(full_payload, attachments, limit) do + def validate_character_limit("" = _full_payload, [] = _attachments) do + {:error, dgettext("errors", "Cannot post an empty status without attachments")} + end + + def validate_character_limit(full_payload, _attachments) do + limit = Pleroma.Config.get([:instance, :limit]) length = String.length(full_payload) if length < limit do - if length > 0 or Enum.count(attachments) > 0 do - :ok - else - {:error, dgettext("errors", "Cannot post an empty status without attachments")} - end + :ok else {:error, dgettext("errors", "The status is over the character limit")} end diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index b53a01955..e90bf842e 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.ControllerHelper do use Pleroma.Web, :controller # As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html - @falsy_param_values [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"] + @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"] def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil def truthy_param?(value), do: value not in @falsy_param_values diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 1e88ff7fe..28d0e58f3 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -575,14 +575,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do - params = - params - |> Map.put("in_reply_to_status_id", params["in_reply_to_id"]) - - scheduled_at = params["scheduled_at"] - - if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do + def post_status( + %{assigns: %{user: user}} = conn, + %{"status" => _, "scheduled_at" => scheduled_at} = params + ) do + if ScheduledActivity.far_enough?(scheduled_at) do with {:ok, scheduled_activity} <- ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do conn @@ -590,24 +587,26 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> render("show.json", %{scheduled_activity: scheduled_activity}) end else - params = Map.drop(params, ["scheduled_at"]) - - case CommonAPI.post(user, params) do - {:error, message} -> - conn - |> put_status(:unprocessable_entity) - |> json(%{error: message}) - - {:ok, activity} -> - conn - |> put_view(StatusView) - |> try_render("status.json", %{ - activity: activity, - for: user, - as: :activity, - with_direct_conversation_id: true - }) - end + post_status(conn, Map.drop(params, ["scheduled_at"])) + end + end + + def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do + case CommonAPI.post(user, params) do + {:ok, activity} -> + conn + |> put_view(StatusView) + |> try_render("status.json", %{ + activity: activity, + for: user, + as: :activity, + with_direct_conversation_id: true + }) + + {:error, message} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: message}) end end -- cgit v1.2.3 From de3e90e536ea0759b024155aee307cc340db3cf1 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 23 Sep 2019 18:52:41 +0700 Subject: Add ActivityDraft.with_valid/2 --- lib/pleroma/web/common_api/activity_draft.ex | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index b4480bd18..aa7c8c381 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -40,20 +40,20 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> put_params(params) |> status() |> summary() - |> attachments() |> full_payload() - |> in_reply_to() - |> in_reply_to_conversation() - |> visibility() |> expires_at() |> poll() + |> with_valid(&in_reply_to/1) + |> with_valid(&attachments/1) + |> with_valid(&in_reply_to_conversation/1) + |> with_valid(&visibility/1) |> content() - |> to_and_cc() - |> context() + |> with_valid(&to_and_cc/1) + |> with_valid(&context/1) |> sensitive() - |> object() + |> with_valid(&object/1) |> preview?() - |> changes() + |> with_valid(&changes/1) |> validate() end @@ -136,8 +136,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do %__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags} end - defp to_and_cc(%{valid?: false} = draft), do: draft - defp to_and_cc(draft) do addressed_users = draft.mentions @@ -166,8 +164,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do %__MODULE__{draft | sensitive: sensitive} end - defp object(%{valid?: false} = draft), do: draft - defp object(draft) do emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji) @@ -195,8 +191,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do %__MODULE__{draft | preview?: preview?} end - defp changes(%{valid?: false} = draft), do: draft - defp changes(draft) do direct? = draft.visibility == "direct" @@ -213,6 +207,9 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do %__MODULE__{draft | changes: changes} end + defp with_valid(%{valid?: true} = draft, func), do: func.(draft) + defp with_valid(draft, _func), do: draft + defp add_error(draft, message) do %__MODULE__{draft | valid?: false, errors: [message | draft.errors]} end -- cgit v1.2.3 From c57ad0a4020fe88521b83d471fb8b71d637fddf1 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 24 Sep 2019 15:56:20 +0700 Subject: Cleanup CommonAPI --- lib/pleroma/web/common_api/common_api.ex | 159 ++++++++++++++----------------- lib/pleroma/web/common_api/utils.ex | 12 ++- 2 files changed, 79 insertions(+), 92 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index d34bb7285..a00e4b0d8 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -17,14 +17,11 @@ defmodule Pleroma.Web.CommonAPI do import Pleroma.Web.CommonAPI.Utils def follow(follower, followed) do + timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout]) + with {:ok, follower} <- User.maybe_direct_follow(follower, followed), {:ok, activity} <- ActivityPub.follow(follower, followed), - {:ok, follower, followed} <- - User.wait_and_refresh( - Pleroma.Config.get([:activitypub, :follow_handshake_timeout]), - follower, - followed - ) do + {:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do {:ok, follower, followed, activity} end end @@ -75,8 +72,7 @@ defmodule Pleroma.Web.CommonAPI do {:ok, delete} <- ActivityPub.delete(object) do {:ok, delete} else - _ -> - {:error, dgettext("errors", "Could not delete")} + _ -> {:error, dgettext("errors", "Could not delete")} end end @@ -86,18 +82,16 @@ defmodule Pleroma.Web.CommonAPI do nil <- Utils.get_existing_announce(user.ap_id, object) do ActivityPub.announce(user, object) else - _ -> - {:error, dgettext("errors", "Could not repeat")} + _ -> {:error, dgettext("errors", "Could not repeat")} end end def unrepeat(id_or_ap_id, user) do - with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - object <- Object.normalize(activity) do + with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id) do + object = Object.normalize(activity) ActivityPub.unannounce(user, object) else - _ -> - {:error, dgettext("errors", "Could not unrepeat")} + _ -> {:error, dgettext("errors", "Could not unrepeat")} end end @@ -107,30 +101,23 @@ defmodule Pleroma.Web.CommonAPI do nil <- Utils.get_existing_like(user.ap_id, object) do ActivityPub.like(user, object) else - _ -> - {:error, dgettext("errors", "Could not favorite")} + _ -> {:error, dgettext("errors", "Could not favorite")} end end def unfavorite(id_or_ap_id, user) do - with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - object <- Object.normalize(activity) do + with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id) do + object = Object.normalize(activity) ActivityPub.unlike(user, object) else - _ -> - {:error, dgettext("errors", "Could not unfavorite")} + _ -> {:error, dgettext("errors", "Could not unfavorite")} end end - def vote(user, object, choices) do - with "Question" <- object.data["type"], - {:author, false} <- {:author, object.data["actor"] == user.ap_id}, - {:existing_votes, []} <- {:existing_votes, Utils.get_existing_votes(user.ap_id, object)}, - {options, max_count} <- get_options_and_max_count(object), - option_count <- Enum.count(options), - {:choice_check, {choices, true}} <- - {:choice_check, normalize_and_validate_choice_indices(choices, option_count)}, - {:count_check, true} <- {:count_check, Enum.count(choices) <= max_count} do + def vote(user, %{data: %{"type" => "Question"}} = object, choices) do + with :ok <- validate_not_author(object, user), + :ok <- validate_existing_votes(user, object), + {:ok, options, choices} <- normalize_and_validate_choices(choices, object) do answer_activities = Enum.map(choices, fn index -> answer_data = make_answer_data(user, object, Enum.at(options, index)["name"]) @@ -149,27 +136,37 @@ defmodule Pleroma.Web.CommonAPI do object = Object.get_cached_by_ap_id(object.data["id"]) {:ok, answer_activities, object} - else - {:author, _} -> {:error, dgettext("errors", "Poll's author can't vote")} - {:existing_votes, _} -> {:error, dgettext("errors", "Already voted")} - {:choice_check, {_, false}} -> {:error, dgettext("errors", "Invalid indices")} - {:count_check, false} -> {:error, dgettext("errors", "Too many choices")} end end - defp get_options_and_max_count(object) do - if Map.has_key?(object.data, "anyOf") do - {object.data["anyOf"], Enum.count(object.data["anyOf"])} + defp validate_not_author(%{data: %{"actor" => ap_id}}, %{ap_id: ap_id}), + do: {:error, dgettext("errors", "Poll's author can't vote")} + + defp validate_not_author(_, _), do: :ok + + defp validate_existing_votes(%{ap_id: ap_id}, object) do + if Utils.get_existing_votes(ap_id, object) == [] do + :ok else - {object.data["oneOf"], 1} + {:error, dgettext("errors", "Already voted")} end end - defp normalize_and_validate_choice_indices(choices, count) do - Enum.map_reduce(choices, true, fn index, valid -> - index = if is_binary(index), do: String.to_integer(index), else: index - {index, if(valid, do: index < count, else: valid)} - end) + defp get_options_and_max_count(%{data: %{"anyOf" => any_of}}), do: {any_of, Enum.count(any_of)} + defp get_options_and_max_count(%{data: %{"oneOf" => one_of}}), do: {one_of, 1} + + defp normalize_and_validate_choices(choices, object) do + choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end) + {options, max_count} = get_options_and_max_count(object) + count = Enum.count(options) + + with {_, true} <- {:valid_choice, Enum.all?(choices, &(&1 < count))}, + {_, true} <- {:count_check, Enum.count(choices) <= max_count} do + {:ok, options, choices} + else + {:valid_choice, _} -> {:error, dgettext("errors", "Invalid indices")} + {:count_check, _} -> {:error, dgettext("errors", "Too many choices")} + end end def get_visibility(_, _, %Participation{}), do: {"direct", "direct"} @@ -194,7 +191,7 @@ defmodule Pleroma.Web.CommonAPI do def get_replied_to_visibility(activity) do with %Object{} = object <- Object.normalize(activity) do - Pleroma.Web.ActivityPub.Visibility.get_visibility(object) + Visibility.get_visibility(object) end end @@ -234,13 +231,12 @@ defmodule Pleroma.Web.CommonAPI do # Updates the emojis for a user based on their profile def update(user) do emoji = emoji_from_profile(user) - source_data = user.info |> Map.get(:source_data, {}) |> Map.put("tag", emoji) + source_data = user.info |> Map.get(:source_data, %{}) |> Map.put("tag", emoji) user = - with {:ok, user} <- User.update_info(user, &User.Info.set_source_data(&1, source_data)) do - user - else - _e -> user + case User.update_info(user, &User.Info.set_source_data(&1, source_data)) do + {:ok, user} -> user + _ -> user end ActivityPub.update(%{ @@ -255,14 +251,8 @@ defmodule Pleroma.Web.CommonAPI do def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do with %Activity{ actor: ^user_ap_id, - data: %{ - "type" => "Create" - }, - object: %Object{ - data: %{ - "type" => "Note" - } - } + data: %{"type" => "Create"}, + object: %Object{data: %{"type" => "Note"}} } = activity <- get_by_id_or_ap_id(id_or_ap_id), true <- Visibility.is_public?(activity), {:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do @@ -299,51 +289,46 @@ defmodule Pleroma.Web.CommonAPI do def thread_muted?(%{id: nil} = _user, _activity), do: false def thread_muted?(user, activity) do - with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do - false - else - _ -> true - end + ThreadMute.check_muted(user.id, activity.data["context"]) != [] end - def report(user, data) do - with {:account_id, %{"account_id" => account_id}} <- {:account_id, data}, - {:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)}, + def report(user, %{"account_id" => account_id} = data) do + with {:ok, account} <- get_reported_account(account_id), {:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]), - {:ok, statuses} <- get_report_statuses(account, data), - {:ok, activity} <- - ActivityPub.flag(%{ - context: Utils.generate_context_id(), - actor: user, - account: account, - statuses: statuses, - content: content_html, - forward: data["forward"] || false - }) do - {:ok, activity} - else - {:error, err} -> {:error, err} - {:account_id, %{}} -> {:error, dgettext("errors", "Valid `account_id` required")} - {:account, nil} -> {:error, dgettext("errors", "Account not found")} + {:ok, statuses} <- get_report_statuses(account, data) do + ActivityPub.flag(%{ + context: Utils.generate_context_id(), + actor: user, + account: account, + statuses: statuses, + content: content_html, + forward: data["forward"] || false + }) + end + end + + def report(_user, _params), do: {:error, dgettext("errors", "Valid `account_id` required")} + + defp get_reported_account(account_id) do + case User.get_cached_by_id(account_id) do + %User{} = account -> {:ok, account} + _ -> {:error, dgettext("errors", "Account not found")} end end def update_report_state(activity_id, state) do - with %Activity{} = activity <- Activity.get_by_id(activity_id), - {:ok, activity} <- Utils.update_report_state(activity, state) do - {:ok, activity} + with %Activity{} = activity <- Activity.get_by_id(activity_id) do + Utils.update_report_state(activity, state) else nil -> {:error, :not_found} - {:error, reason} -> {:error, reason} _ -> {:error, dgettext("errors", "Could not update state")} end end def update_activity_scope(activity_id, opts \\ %{}) do with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), - {:ok, activity} <- toggle_sensitive(activity, opts), - {:ok, activity} <- set_visibility(activity, opts) do - {:ok, activity} + {:ok, activity} <- toggle_sensitive(activity, opts) do + set_visibility(activity, opts) else nil -> {:error, :not_found} {:error, reason} -> {:error, reason} diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 8093a56a6..88a5f434a 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -231,7 +231,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do no_attachment_links = data |> Map.get("no_attachment_links", Config.get([:instance, :no_attachment_links])) - |> Kernel.in([true, "true"]) + |> truthy_param?() content_type = get_content_type(data["content_type"]) @@ -431,12 +431,14 @@ defmodule Pleroma.Web.CommonAPI.Utils do end end - def emoji_from_profile(%{info: _info} = user) do - (Emoji.Formatter.get_emoji(user.bio) ++ Emoji.Formatter.get_emoji(user.name)) - |> Enum.map(fn {shortcode, %Emoji{file: url}} -> + def emoji_from_profile(%User{bio: bio, name: name}) do + [bio, name] + |> Enum.map(&Emoji.Formatter.get_emoji/1) + |> Enum.concat() + |> Enum.map(fn {shortcode, %Emoji{file: path}} -> %{ "type" => "Emoji", - "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"}, + "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{path}"}, "name" => ":#{shortcode}:" } end) -- cgit v1.2.3 From 3572cf29b7374947ebfbb42a20d370a75f5f0a40 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 26 Sep 2019 10:53:42 +0700 Subject: Extract timeline actions from `MastodonAPIController` into `TimelineController` --- lib/pleroma/web/controller_helper.ex | 2 +- .../controllers/mastodon_api_controller.ex | 125 +------------------ .../controllers/timeline_controller.ex | 136 +++++++++++++++++++++ lib/pleroma/web/router.ex | 10 +- 4 files changed, 144 insertions(+), 129 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index b53a01955..e90bf842e 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.ControllerHelper do use Pleroma.Web, :controller # As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html - @falsy_param_values [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"] + @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"] def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil def truthy_param?(value), do: value not in @falsy_param_values diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 1e88ff7fe..74a8b5055 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller import Pleroma.Web.ControllerHelper, - only: [json_response: 3, add_link_headers: 2, add_link_headers: 3] + only: [json_response: 3, add_link_headers: 2, truthy_param?: 1] alias Ecto.Changeset alias Pleroma.Activity @@ -44,7 +44,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TwitterAPI - alias Pleroma.Web.ControllerHelper import Ecto.Query require Logger @@ -156,7 +155,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do ] |> Enum.reduce(%{}, fn key, acc -> add_if_present(acc, params, to_string(key), key, fn value -> - {:ok, ControllerHelper.truthy_param?(value)} + {:ok, truthy_param?(value)} end) end) |> add_if_present(params, "default_scope", :default_scope) @@ -344,43 +343,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, mastodon_emoji) end - def home_timeline(%{assigns: %{user: user}} = conn, params) do - params = - params - |> Map.put("type", ["Create", "Announce"]) - |> Map.put("blocking_user", user) - |> Map.put("muting_user", user) - |> Map.put("user", user) - - activities = - [user.ap_id | user.following] - |> ActivityPub.fetch_activities(params) - |> Enum.reverse() - - conn - |> add_link_headers(activities) - |> put_view(StatusView) - |> render("index.json", %{activities: activities, for: user, as: :activity}) - end - - def public_timeline(%{assigns: %{user: user}} = conn, params) do - local_only = params["local"] in [true, "True", "true", "1"] - - activities = - params - |> Map.put("type", ["Create", "Announce"]) - |> Map.put("local_only", local_only) - |> Map.put("blocking_user", user) - |> Map.put("muting_user", user) - |> ActivityPub.fetch_public_activities() - |> Enum.reverse() - - conn - |> add_link_headers(activities, %{"local" => local_only}) - |> put_view(StatusView) - |> render("index.json", %{activities: activities, for: user, as: :activity}) - end - def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do params = @@ -400,25 +362,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def dm_timeline(%{assigns: %{user: user}} = conn, params) do - params = - params - |> Map.put("type", "Create") - |> Map.put("blocking_user", user) - |> Map.put("user", user) - |> Map.put(:visibility, "direct") - - activities = - [user.ap_id] - |> ActivityPub.fetch_activities_query(params) - |> Pagination.fetch_paginated(params) - - conn - |> add_link_headers(activities) - |> put_view(StatusView) - |> render("index.json", %{activities: activities, for: user, as: :activity}) - end - def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do limit = 100 @@ -822,45 +765,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do - local_only = params["local"] in [true, "True", "true", "1"] - - tags = - [params["tag"], params["any"]] - |> List.flatten() - |> Enum.uniq() - |> Enum.filter(& &1) - |> Enum.map(&String.downcase(&1)) - - tag_all = - params["all"] || - [] - |> Enum.map(&String.downcase(&1)) - - tag_reject = - params["none"] || - [] - |> Enum.map(&String.downcase(&1)) - - activities = - params - |> Map.put("type", "Create") - |> Map.put("local_only", local_only) - |> Map.put("blocking_user", user) - |> Map.put("muting_user", user) - |> Map.put("user", user) - |> Map.put("tag", tags) - |> Map.put("tag_all", tag_all) - |> Map.put("tag_reject", tag_reject) - |> ActivityPub.fetch_public_activities() - |> Enum.reverse() - - conn - |> add_link_headers(activities, %{"local" => local_only}) - |> put_view(StatusView) - |> render("index.json", %{activities: activities, for: user, as: :activity}) - end - def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do with %User{} = user <- User.get_cached_by_id(id), followers <- MastodonAPI.get_followers(user, params) do @@ -1173,31 +1077,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, res) end - def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do - with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do - params = - params - |> Map.put("type", "Create") - |> Map.put("blocking_user", user) - |> Map.put("user", user) - |> Map.put("muting_user", user) - - # we must filter the following list for the user to avoid leaking statuses the user - # does not actually have permission to see (for more info, peruse security issue #270). - activities = - following - |> Enum.filter(fn x -> x in user.following end) - |> ActivityPub.fetch_activities_bounded(following, params) - |> Enum.reverse() - - conn - |> put_view(StatusView) - |> render("index.json", %{activities: activities, for: user, as: :activity}) - else - _e -> render_error(conn, :forbidden, "Error.") - end - end - def index(%{assigns: %{user: user}} = conn, _params) do token = get_session(conn, :oauth_token) diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex new file mode 100644 index 000000000..bb8b0eb32 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -0,0 +1,136 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.TimelineController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, + only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1] + + alias Pleroma.Pagination + alias Pleroma.Web.ActivityPub.ActivityPub + + plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) + + # GET /api/v1/timelines/home + def home(%{assigns: %{user: user}} = conn, params) do + params = + params + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + + recipients = [user.ap_id | user.following] + + activities = + recipients + |> ActivityPub.fetch_activities(params) + |> Enum.reverse() + + conn + |> add_link_headers(activities) + |> render("index.json", activities: activities, for: user, as: :activity) + end + + # GET /api/v1/timelines/direct + def direct(%{assigns: %{user: user}} = conn, params) do + params = + params + |> Map.put("type", "Create") + |> Map.put("blocking_user", user) + |> Map.put("user", user) + |> Map.put(:visibility, "direct") + + activities = + [user.ap_id] + |> ActivityPub.fetch_activities_query(params) + |> Pagination.fetch_paginated(params) + + conn + |> add_link_headers(activities) + |> render("index.json", activities: activities, for: user, as: :activity) + end + + # GET /api/v1/timelines/public + def public(%{assigns: %{user: user}} = conn, params) do + local_only = truthy_param?(params["local"]) + + activities = + params + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("local_only", local_only) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> ActivityPub.fetch_public_activities() + |> Enum.reverse() + + conn + |> add_link_headers(activities, %{"local" => local_only}) + |> render("index.json", activities: activities, for: user, as: :activity) + end + + # GET /api/v1/timelines/tag/:tag + def hashtag(%{assigns: %{user: user}} = conn, params) do + local_only = truthy_param?(params["local"]) + + tags = + [params["tag"], params["any"]] + |> List.flatten() + |> Enum.uniq() + |> Enum.filter(& &1) + |> Enum.map(&String.downcase(&1)) + + tag_all = + params + |> Map.get("all", []) + |> Enum.map(&String.downcase(&1)) + + tag_reject = + params + |> Map.get("none", []) + |> Enum.map(&String.downcase(&1)) + + activities = + params + |> Map.put("type", "Create") + |> Map.put("local_only", local_only) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + |> Map.put("tag", tags) + |> Map.put("tag_all", tag_all) + |> Map.put("tag_reject", tag_reject) + |> ActivityPub.fetch_public_activities() + |> Enum.reverse() + + conn + |> add_link_headers(activities, %{"local" => local_only}) + |> render("index.json", activities: activities, for: user, as: :activity) + end + + # GET /api/v1/timelines/list/:list_id + def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do + with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do + params = + params + |> Map.put("type", "Create") + |> Map.put("blocking_user", user) + |> Map.put("user", user) + |> Map.put("muting_user", user) + + # we must filter the following list for the user to avoid leaking statuses the user + # does not actually have permission to see (for more info, peruse security issue #270). + activities = + following + |> Enum.filter(fn x -> x in user.following end) + |> ActivityPub.fetch_activities_bounded(following, params) + |> Enum.reverse() + + render(conn, "index.json", activities: activities, for: user, as: :activity) + else + _e -> render_error(conn, :forbidden, "Error.") + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 316c895ee..2575481ff 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -319,8 +319,8 @@ defmodule Pleroma.Web.Router do get("/blocks", MastodonAPIController, :blocks) get("/mutes", MastodonAPIController, :mutes) - get("/timelines/home", MastodonAPIController, :home_timeline) - get("/timelines/direct", MastodonAPIController, :dm_timeline) + get("/timelines/home", TimelineController, :home) + get("/timelines/direct", TimelineController, :direct) get("/favourites", MastodonAPIController, :favourites) get("/bookmarks", MastodonAPIController, :bookmarks) @@ -466,9 +466,9 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_read_or_public) - get("/timelines/public", MastodonAPIController, :public_timeline) - get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline) - get("/timelines/list/:list_id", MastodonAPIController, :list_timeline) + get("/timelines/public", TimelineController, :public) + get("/timelines/tag/:tag", TimelineController, :hashtag) + get("/timelines/list/:list_id", TimelineController, :list) get("/statuses", MastodonAPIController, :get_statuses) get("/statuses/:id", MastodonAPIController, :get_status) -- cgit v1.2.3 From 73ae38ca04df02656bfb239ceba4ffe64879e927 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 26 Sep 2019 21:08:04 +0300 Subject: add deprecated tag --- lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 5e1977b8e..8f6b3456a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -1474,6 +1474,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + @doc false + @deprecated "https://github.com/tootsuite/mastodon/pull/11213" def status_card(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id(id), true <- Visibility.visible_for_user?(activity, user) do -- cgit v1.2.3 From 98d1347a4ea1c296d2f07b9467addc56ef2dc676 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 9 Sep 2019 21:49:02 +0700 Subject: Extract status actions from `MastodonAPIController` into `StatusController` --- lib/pleroma/bbs/handler.ex | 2 +- lib/pleroma/web/admin_api/admin_api_controller.ex | 4 +- .../controllers/mastodon_api_controller.ex | 264 ------------------ .../mastodon_api/controllers/status_controller.ex | 294 +++++++++++++++++++++ .../web/mastodon_api/views/conversation_view.ex | 2 +- .../web/mastodon_api/views/notification_view.ex | 6 +- lib/pleroma/web/mastodon_api/views/status_view.ex | 16 +- lib/pleroma/web/router.ex | 38 +-- lib/pleroma/web/views/streamer_view.ex | 4 +- 9 files changed, 328 insertions(+), 302 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/status_controller.ex (limited to 'lib') diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex index 0a381f592..fa838a4e4 100644 --- a/lib/pleroma/bbs/handler.ex +++ b/lib/pleroma/bbs/handler.ex @@ -42,7 +42,7 @@ defmodule Pleroma.BBS.Handler do end def puts_activity(activity) do - status = Pleroma.Web.MastodonAPI.StatusView.render("status.json", %{activity: activity}) + status = Pleroma.Web.MastodonAPI.StatusView.render("show.json", %{activity: activity}) IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})") IO.puts(HtmlSanitizeEx.strip_tags(status.content)) IO.puts("") diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 90aef99f7..21da8a7ff 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -513,7 +513,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do conn |> put_view(StatusView) - |> render("status.json", %{activity: activity}) + |> render("show.json", %{activity: activity}) else true -> {:param_cast, nil} @@ -537,7 +537,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do conn |> put_view(StatusView) - |> render("status.json", %{activity: activity}) + |> render("show.json", %{activity: activity}) end end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index e4ae63231..82bba43e5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -51,28 +51,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do @rate_limited_relations_actions ~w(follow unfollow)a - @rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status - post_status delete_status)a - - plug( - RateLimiter, - {:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]} - when action in ~w(reblog_status unreblog_status)a - ) - - plug( - RateLimiter, - {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]} - when action in ~w(fav_status unfav_status)a - ) - plug( RateLimiter, {:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions ) plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions) - plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions) plug(RateLimiter, :app_account_creation when action == :account_register) plug(RateLimiter, :search when action in [:search, :search2, :account_search]) plug(RateLimiter, :password_reset when action == :password_reset) @@ -362,63 +346,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do - limit = 100 - - activities = - ids - |> Enum.take(limit) - |> Activity.all_by_ids_with_object() - |> Enum.filter(&Visibility.visible_for_user?(&1, user)) - - conn - |> put_view(StatusView) - |> render("index.json", activities: activities, for: user, as: :activity) - end - - def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id_with_object(id), - true <- Visibility.visible_for_user?(activity, user) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user}) - end - end - - def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id), - activities <- - ActivityPub.fetch_activities_for_context(activity.data["context"], %{ - "blocking_user" => user, - "user" => user, - "exclude_id" => activity.id - }), - grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do - result = %{ - ancestors: - StatusView.render( - "index.json", - for: user, - activities: grouped_activities[true] || [], - as: :activity - ) - |> Enum.reverse(), - # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart - descendants: - StatusView.render( - "index.json", - for: user, - activities: grouped_activities[false] || [], - as: :activity - ) - |> Enum.reverse() - # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart - } - - json(conn, result) - end - end - def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), @@ -518,143 +445,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def post_status( - %{assigns: %{user: user}} = conn, - %{"status" => _, "scheduled_at" => scheduled_at} = params - ) do - if ScheduledActivity.far_enough?(scheduled_at) do - with {:ok, scheduled_activity} <- - ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do - conn - |> put_view(ScheduledActivityView) - |> render("show.json", %{scheduled_activity: scheduled_activity}) - end - else - post_status(conn, Map.drop(params, ["scheduled_at"])) - end - end - - def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do - case CommonAPI.post(user, params) do - {:ok, activity} -> - conn - |> put_view(StatusView) - |> try_render("status.json", %{ - activity: activity, - for: user, - as: :activity, - with_direct_conversation_id: true - }) - - {:error, message} -> - conn - |> put_status(:unprocessable_entity) - |> json(%{error: message}) - end - end - - def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do - json(conn, %{}) - else - _e -> render_error(conn, :forbidden, "Can't delete this post") - end - end - - def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user), - %Activity{} = announce <- Activity.normalize(announce.data) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: announce, for: user, as: :activity}) - end - end - - def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) - end - end - - def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) - end - end - - def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) - end - end - - def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) - end - end - - def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) - end - end - - def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id_with_object(id), - %User{} = user <- User.get_cached_by_nickname(user.nickname), - true <- Visibility.visible_for_user?(activity, user), - {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) - end - end - - def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id_with_object(id), - %User{} = user <- User.get_cached_by_nickname(user.nickname), - true <- Visibility.visible_for_user?(activity, user), - {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) - end - end - - def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do - activity = Activity.get_by_id(id) - - with {:ok, activity} <- CommonAPI.add_mute(user, activity) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) - end - end - - def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do - activity = Activity.get_by_id(id) - - with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) - end - end - def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do id = List.wrap(id) q = from(u in User, where: u.id in ^id) @@ -726,44 +516,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> json(mascot) end - def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id_with_object(id), - {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, - %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do - q = from(u in User, where: u.ap_id in ^likes) - - users = - Repo.all(q) - |> Enum.filter(&(not User.blocks?(user, &1))) - - conn - |> put_view(AccountView) - |> render("accounts.json", %{for: user, users: users, as: :user}) - else - {:visible, false} -> {:error, :not_found} - _ -> json(conn, []) - end - end - - def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id_with_object(id), - {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, - %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do - q = from(u in User, where: u.ap_id in ^announces) - - users = - Repo.all(q) - |> Enum.filter(&(not User.blocks?(user, &1))) - - conn - |> put_view(AccountView) - |> render("accounts.json", %{for: user, users: users, as: :user}) - else - {:visible, false} -> {:error, :not_found} - _ -> json(conn, []) - end - end - def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do with %User{} = user <- User.get_cached_by_id(id), followers <- MastodonAPI.get_followers(user, params) do @@ -1394,22 +1146,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do - with %Activity{} = activity <- Activity.get_by_id(status_id), - true <- Visibility.visible_for_user?(activity, user) do - data = - StatusView.render( - "card.json", - Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - ) - - json(conn, data) - else - _e -> - %{} - end - end - def reports(%{assigns: %{user: user}} = conn, params) do case CommonAPI.report(user, params) do {:ok, activity} -> diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex new file mode 100644 index 000000000..89869bda0 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -0,0 +1,294 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.StatusController do + use Pleroma.Web, :controller + + import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3] + + require Ecto.Query + + alias Pleroma.Activity + alias Pleroma.Bookmark + alias Pleroma.Object + alias Pleroma.Plugs.RateLimiter + alias Pleroma.Repo + alias Pleroma.ScheduledActivity + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI.ScheduledActivityView + alias Pleroma.Web.MastodonAPI.StatusView + + @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a + + plug( + RateLimiter, + {:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]} + when action in ~w(reblog unreblog)a + ) + + plug( + RateLimiter, + {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]} + when action in ~w(favourite unfavourite)a + ) + + plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions) + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @doc """ + GET `/api/v1/statuses?ids[]=1&ids[]=2` + + `ids` query param is required + """ + def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do + limit = 100 + + activities = + ids + |> Enum.take(limit) + |> Activity.all_by_ids_with_object() + |> Enum.filter(&Visibility.visible_for_user?(&1, user)) + + render(conn, "index.json", activities: activities, for: user, as: :activity) + end + + @doc """ + POST /api/v1/statuses + + Creates a scheduled status when `scheduled_at` param is present and it's far enough + """ + def create( + %{assigns: %{user: user}} = conn, + %{"status" => _, "scheduled_at" => scheduled_at} = params + ) do + params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"]) + + if ScheduledActivity.far_enough?(scheduled_at) do + with {:ok, scheduled_activity} <- + ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do + conn + |> put_view(ScheduledActivityView) + |> render("show.json", scheduled_activity: scheduled_activity) + end + else + create(conn, Map.drop(params, ["scheduled_at"])) + end + end + + @doc """ + POST /api/v1/statuses + + Creates a regular status + """ + def create(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do + params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"]) + + with {:ok, activity} <- CommonAPI.post(user, params) do + try_render(conn, "show.json", + activity: activity, + for: user, + as: :activity, + with_direct_conversation_id: true + ) + else + {:error, message} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: message}) + end + end + + @doc "GET /api/v1/statuses/:id" + def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id_with_object(id), + true <- Visibility.visible_for_user?(activity, user) do + try_render(conn, "show.json", activity: activity, for: user) + end + end + + @doc "DELETE /api/v1/statuses/:id" + def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do + json(conn, %{}) + else + _e -> render_error(conn, :forbidden, "Can't delete this post") + end + end + + @doc "POST /api/v1/statuses/:id/reblog" + def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user), + %Activity{} = announce <- Activity.normalize(announce.data) do + try_render(conn, "show.json", %{activity: announce, for: user, as: :activity}) + end + end + + @doc "POST /api/v1/statuses/:id/unreblog" + def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do + try_render(conn, "show.json", %{activity: activity, for: user, as: :activity}) + end + end + + @doc "POST /api/v1/statuses/:id/favourite" + def favourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do + try_render(conn, "show.json", activity: activity, for: user, as: :activity) + end + end + + @doc "POST /api/v1/statuses/:id/unfavourite" + def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do + try_render(conn, "show.json", activity: activity, for: user, as: :activity) + end + end + + @doc "POST /api/v1/statuses/:id/pin" + def pin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do + try_render(conn, "show.json", activity: activity, for: user, as: :activity) + end + end + + @doc "POST /api/v1/statuses/:id/unpin" + def unpin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do + try_render(conn, "show.json", activity: activity, for: user, as: :activity) + end + end + + @doc "POST /api/v1/statuses/:id/bookmark" + def bookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id_with_object(id), + %User{} = user <- User.get_cached_by_nickname(user.nickname), + true <- Visibility.visible_for_user?(activity, user), + {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do + try_render(conn, "show.json", activity: activity, for: user, as: :activity) + end + end + + @doc "POST /api/v1/statuses/:id/unbookmark" + def unbookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id_with_object(id), + %User{} = user <- User.get_cached_by_nickname(user.nickname), + true <- Visibility.visible_for_user?(activity, user), + {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do + try_render(conn, "show.json", activity: activity, for: user, as: :activity) + end + end + + @doc "POST /api/v1/statuses/:id/mute" + def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id(id), + {:ok, activity} <- CommonAPI.add_mute(user, activity) do + try_render(conn, "show.json", activity: activity, for: user, as: :activity) + end + end + + @doc "POST /api/v1/statuses/:id/unmute" + def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id(id), + {:ok, activity} <- CommonAPI.remove_mute(user, activity) do + try_render(conn, "show.json", activity: activity, for: user, as: :activity) + end + end + + @doc "GET /api/v1/statuses/:id/card" + def card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do + with %Activity{} = activity <- Activity.get_by_id(status_id), + true <- Visibility.visible_for_user?(activity, user) do + data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + render(conn, "card.json", data) + else + _ -> render_error(conn, :not_found, "Record not found") + end + end + + @doc "GET /api/v1/statuses/:id/favourited_by" + def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id_with_object(id), + {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, + %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do + users = + User + |> Ecto.Query.where([u], u.ap_id in ^likes) + |> Repo.all() + |> Enum.filter(&(not User.blocks?(user, &1))) + + conn + |> put_view(AccountView) + |> render("accounts.json", for: user, users: users, as: :user) + else + {:visible, false} -> {:error, :not_found} + _ -> json(conn, []) + end + end + + @doc "GET /api/v1/statuses/:id/reblogged_by" + def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id_with_object(id), + {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, + %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do + users = + User + |> Ecto.Query.where([u], u.ap_id in ^announces) + |> Repo.all() + |> Enum.filter(&(not User.blocks?(user, &1))) + + conn + |> put_view(AccountView) + |> render("accounts.json", for: user, users: users, as: :user) + else + {:visible, false} -> {:error, :not_found} + _ -> json(conn, []) + end + end + + @doc "GET /api/v1/statuses/:id/context" + def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id(id) do + activities = + ActivityPub.fetch_activities_for_context(activity.data["context"], %{ + "blocking_user" => user, + "user" => user, + "exclude_id" => activity.id + }) + + # TODO: Move to view + grouped_activities = Enum.group_by(activities, fn %{id: id} -> id < activity.id end) + + result = %{ + ancestors: + StatusView.render( + "index.json", + for: user, + activities: grouped_activities[true] || [], + as: :activity + ) + |> Enum.reverse(), + # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart + descendants: + StatusView.render( + "index.json", + for: user, + activities: grouped_activities[false] || [], + as: :activity + ) + |> Enum.reverse() + # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart + } + + json(conn, result) + end + end +end diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 40acc07b3..4aeb79d81 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -24,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do activity = Activity.get_by_id_with_object(last_activity_id) - last_status = StatusView.render("status.json", %{activity: activity, for: user}) + last_status = StatusView.render("show.json", %{activity: activity, for: user}) # Conversations return all users except the current user. users = diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index ec8eadcaa..05110a192 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -39,19 +39,19 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do "mention" -> response |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: activity, for: user}) + status: StatusView.render("show.json", %{activity: activity, for: user}) }) "favourite" -> response |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + status: StatusView.render("show.json", %{activity: parent_activity, for: user}) }) "reblog" -> response |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + status: StatusView.render("show.json", %{activity: parent_activity, for: user}) }) "follow" -> diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index ef796cddd..59bef30f2 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -73,17 +73,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do def render("index.json", opts) do replied_to_activities = get_replied_to_activities(opts.activities) + opts = Map.put(opts, :replied_to_activities, replied_to_activities) - opts.activities - |> safe_render_many( - StatusView, - "status.json", - Map.put(opts, :replied_to_activities, replied_to_activities) - ) + safe_render_many(opts.activities, StatusView, "show.json", opts) end def render( - "status.json", + "show.json", %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts ) do user = get_user(activity.data["actor"]) @@ -96,7 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do |> Activity.with_set_thread_muted_field(opts[:for]) |> Repo.one() - reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity)) + reblogged = render("show.json", Map.put(opts, :activity, reblogged_activity)) favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || []) @@ -144,7 +140,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end - def render("status.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do + def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do object = Object.normalize(activity) user = get_user(activity.data["actor"]) @@ -303,7 +299,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end - def render("status.json", _) do + def render("show.json", _) do nil end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 2575481ff..7a20b6d75 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -355,19 +355,19 @@ defmodule Pleroma.Web.Router do patch("/accounts/update_credentials", MastodonAPIController, :update_credentials) - post("/statuses", MastodonAPIController, :post_status) - delete("/statuses/:id", MastodonAPIController, :delete_status) - - post("/statuses/:id/reblog", MastodonAPIController, :reblog_status) - post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status) - post("/statuses/:id/favourite", MastodonAPIController, :fav_status) - post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status) - post("/statuses/:id/pin", MastodonAPIController, :pin_status) - post("/statuses/:id/unpin", MastodonAPIController, :unpin_status) - post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status) - post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status) - post("/statuses/:id/mute", MastodonAPIController, :mute_conversation) - post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation) + post("/statuses", StatusController, :create) + delete("/statuses/:id", StatusController, :delete) + + post("/statuses/:id/reblog", StatusController, :reblog) + post("/statuses/:id/unreblog", StatusController, :unreblog) + post("/statuses/:id/favourite", StatusController, :favourite) + post("/statuses/:id/unfavourite", StatusController, :unfavourite) + post("/statuses/:id/pin", StatusController, :pin) + post("/statuses/:id/unpin", StatusController, :unpin) + post("/statuses/:id/bookmark", StatusController, :bookmark) + post("/statuses/:id/unbookmark", StatusController, :unbookmark) + post("/statuses/:id/mute", StatusController, :mute_conversation) + post("/statuses/:id/unmute", StatusController, :unmute_conversation) put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status) delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status) @@ -448,10 +448,10 @@ defmodule Pleroma.Web.Router do get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials) get("/custom_emojis", MastodonAPIController, :custom_emojis) - get("/statuses/:id/card", MastodonAPIController, :status_card) + get("/statuses/:id/card", StatusController, :card) - get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by) - get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by) + get("/statuses/:id/favourited_by", StatusController, :favourited_by) + get("/statuses/:id/reblogged_by", StatusController, :reblogged_by) get("/trends", MastodonAPIController, :empty_array) @@ -470,9 +470,9 @@ defmodule Pleroma.Web.Router do get("/timelines/tag/:tag", TimelineController, :hashtag) get("/timelines/list/:list_id", TimelineController, :list) - get("/statuses", MastodonAPIController, :get_statuses) - get("/statuses/:id", MastodonAPIController, :get_status) - get("/statuses/:id/context", MastodonAPIController, :get_context) + get("/statuses", StatusController, :index) + get("/statuses/:id", StatusController, :show) + get("/statuses/:id/context", StatusController, :context) get("/polls/:id", MastodonAPIController, :get_poll) diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index b13030fa0..a9f14d09a 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Web.StreamerView do event: "update", payload: Pleroma.Web.MastodonAPI.StatusView.render( - "status.json", + "show.json", activity: activity, for: user ) @@ -43,7 +43,7 @@ defmodule Pleroma.Web.StreamerView do event: "update", payload: Pleroma.Web.MastodonAPI.StatusView.render( - "status.json", + "show.json", activity: activity ) |> Jason.encode!() -- cgit v1.2.3 From 5ea5c58a85b7be56370b151ce8977982d47fde8c Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 26 Sep 2019 14:28:35 +0700 Subject: Move view logic from StatusController.context to StatusView and add a test --- .../mastodon_api/controllers/status_controller.ex | 27 +--------------------- lib/pleroma/web/mastodon_api/views/status_view.ex | 14 +++++++++++ 2 files changed, 15 insertions(+), 26 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 89869bda0..f7da10289 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -21,7 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.ScheduledActivityView - alias Pleroma.Web.MastodonAPI.StatusView @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a @@ -264,31 +263,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do "exclude_id" => activity.id }) - # TODO: Move to view - grouped_activities = Enum.group_by(activities, fn %{id: id} -> id < activity.id end) - - result = %{ - ancestors: - StatusView.render( - "index.json", - for: user, - activities: grouped_activities[true] || [], - as: :activity - ) - |> Enum.reverse(), - # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart - descendants: - StatusView.render( - "index.json", - for: user, - activities: grouped_activities[false] || [], - as: :activity - ) - |> Enum.reverse() - # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart - } - - json(conn, result) + render(conn, "context.json", activity: activity, activities: activities, user: user) end 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 59bef30f2..715d40766 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -439,6 +439,20 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end + def render("context.json", %{activity: activity, activities: activities, user: user}) do + %{ancestors: ancestors, descendants: descendants} = + activities + |> Enum.reverse() + |> Enum.group_by(fn %{id: id} -> if id < activity.id, do: :ancestors, else: :descendants end) + |> Map.put_new(:ancestors, []) + |> Map.put_new(:descendants, []) + + %{ + ancestors: render("index.json", for: user, activities: ancestors, as: :activity), + descendants: render("index.json", for: user, activities: descendants, as: :activity) + } + end + def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do object = Object.normalize(activity) -- cgit v1.2.3 From 14294243a294d764b449e1eae19c4cd87b9d4d82 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 27 Sep 2019 04:22:40 +0000 Subject: mastodon api: implement follow_requests_count --- lib/pleroma/web/mastodon_api/views/account_view.ex | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index a23aeea9b..8cf9e9d5c 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -166,6 +166,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do |> maybe_put_settings_store(user, opts[:for], opts) |> maybe_put_chat_token(user, opts[:for], opts) |> maybe_put_activation_status(user, opts[:for]) + |> maybe_put_follow_requests_count(user, opts[:for]) end defp username_from_nickname(string) when is_binary(string) do @@ -174,6 +175,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do defp username_from_nickname(_), do: nil + defp maybe_put_follow_requests_count( + data, + %User{id: user_id} = user, + %User{id: user_id} + ) do + count = + User.get_follow_requests(user) + |> length() + + data + |> Kernel.put_in([:follow_requests_count], count) + end + + defp maybe_put_follow_requests_count(data, _, _), do: data + defp maybe_put_settings( data, %User{id: user_id} = user, -- cgit v1.2.3 From 6c7c35dbe11c3871eea1a1c5745befdc2068e526 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 27 Sep 2019 11:55:47 +0700 Subject: Fix SubscriptionNotificationView --- lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex index 0eccbcbb9..fc41a7389 100644 --- a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex +++ b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex @@ -36,19 +36,19 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do "mention" -> response |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: activity, for: user}) + status: StatusView.render("show.json", %{activity: activity, for: user}) }) "favourite" -> response |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + status: StatusView.render("show.json", %{activity: parent_activity, for: user}) }) "reblog" -> response |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + status: StatusView.render("show.json", %{activity: parent_activity, for: user}) }) "follow" -> -- cgit v1.2.3 From 621377f378c7cfde88f87356e9fd65ed6d9f6d50 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 27 Sep 2019 13:06:25 +0700 Subject: Extract filter actions from `MastodonAPIController` to `FilterController` --- .../mastodon_api/controllers/filter_controller.ex | 72 ++++++++++++++++++++++ .../controllers/mastodon_api_controller.ex | 61 ------------------ .../views/subscription_notification_view.ex | 6 +- lib/pleroma/web/router.ex | 10 +-- 4 files changed, 80 insertions(+), 69 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/filter_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex new file mode 100644 index 000000000..19041304e --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -0,0 +1,72 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FilterController do + use Pleroma.Web, :controller + + alias Pleroma.Filter + + @doc "GET /api/v1/filters" + def index(%{assigns: %{user: user}} = conn, _) do + filters = Filter.get_filters(user) + + render(conn, "filters.json", filters: filters) + end + + @doc "POST /api/v1/filters" + def create( + %{assigns: %{user: user}} = conn, + %{"phrase" => phrase, "context" => context} = params + ) do + query = %Filter{ + user_id: user.id, + phrase: phrase, + context: context, + hide: Map.get(params, "irreversible", false), + whole_word: Map.get(params, "boolean", true) + # expires_at + } + + {:ok, response} = Filter.create(query) + + render(conn, "filter.json", filter: response) + end + + @doc "GET /api/v1/filters/:id" + def show(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do + filter = Filter.get(filter_id, user) + + render(conn, "filter.json", filter: filter) + end + + @doc "PUT /api/v1/filters/:id" + def update( + %{assigns: %{user: user}} = conn, + %{"phrase" => phrase, "context" => context, "id" => filter_id} = params + ) do + query = %Filter{ + user_id: user.id, + filter_id: filter_id, + phrase: phrase, + context: context, + hide: Map.get(params, "irreversible", nil), + whole_word: Map.get(params, "boolean", true) + # expires_at + } + + {:ok, response} = Filter.update(query) + render(conn, "filter.json", filter: response) + end + + @doc "DELETE /api/v1/filters/:id" + def delete(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do + query = %Filter{ + user_id: user.id, + filter_id: filter_id + } + + {:ok, _} = Filter.delete(query) + json(conn, %{}) + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 82bba43e5..9e2382483 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -14,7 +14,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Config alias Pleroma.Conversation.Participation alias Pleroma.Emoji - alias Pleroma.Filter alias Pleroma.HTTP alias Pleroma.Object alias Pleroma.Pagination @@ -30,7 +29,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AppView alias Pleroma.Web.MastodonAPI.ConversationView - alias Pleroma.Web.MastodonAPI.FilterView alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonView @@ -1040,65 +1038,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, %{}) end - def get_filters(%{assigns: %{user: user}} = conn, _) do - filters = Filter.get_filters(user) - res = FilterView.render("filters.json", filters: filters) - json(conn, res) - end - - def create_filter( - %{assigns: %{user: user}} = conn, - %{"phrase" => phrase, "context" => context} = params - ) do - query = %Filter{ - user_id: user.id, - phrase: phrase, - context: context, - hide: Map.get(params, "irreversible", false), - whole_word: Map.get(params, "boolean", true) - # expires_at - } - - {:ok, response} = Filter.create(query) - res = FilterView.render("filter.json", filter: response) - json(conn, res) - end - - def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do - filter = Filter.get(filter_id, user) - res = FilterView.render("filter.json", filter: filter) - json(conn, res) - end - - def update_filter( - %{assigns: %{user: user}} = conn, - %{"phrase" => phrase, "context" => context, "id" => filter_id} = params - ) do - query = %Filter{ - user_id: user.id, - filter_id: filter_id, - phrase: phrase, - context: context, - hide: Map.get(params, "irreversible", nil), - whole_word: Map.get(params, "boolean", true) - # expires_at - } - - {:ok, response} = Filter.update(query) - res = FilterView.render("filter.json", filter: response) - json(conn, res) - end - - def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do - query = %Filter{ - user_id: user.id, - filter_id: filter_id - } - - {:ok, _} = Filter.delete(query) - json(conn, %{}) - end - def suggestions(%{assigns: %{user: user}} = conn, _) do suggestions = Config.get(:suggestions) diff --git a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex index 0eccbcbb9..fc41a7389 100644 --- a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex +++ b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex @@ -36,19 +36,19 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do "mention" -> response |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: activity, for: user}) + status: StatusView.render("show.json", %{activity: activity, for: user}) }) "favourite" -> response |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + status: StatusView.render("show.json", %{activity: parent_activity, for: user}) }) "reblog" -> response |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + status: StatusView.render("show.json", %{activity: parent_activity, for: user}) }) "follow" -> diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8bf55631e..30ebf435c 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -348,7 +348,7 @@ defmodule Pleroma.Web.Router do get("/domain_blocks", MastodonAPIController, :domain_blocks) - get("/filters", MastodonAPIController, :get_filters) + get("/filters", FilterController, :index) get("/suggestions", MastodonAPIController, :suggestions) @@ -392,10 +392,10 @@ defmodule Pleroma.Web.Router do post("/lists/:id/accounts", ListController, :add_to_list) delete("/lists/:id/accounts", ListController, :remove_from_list) - post("/filters", MastodonAPIController, :create_filter) - get("/filters/:id", MastodonAPIController, :get_filter) - put("/filters/:id", MastodonAPIController, :update_filter) - delete("/filters/:id", MastodonAPIController, :delete_filter) + post("/filters", FilterController, :create) + get("/filters/:id", FilterController, :show) + put("/filters/:id", FilterController, :update) + delete("/filters/:id", FilterController, :delete) patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar) patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner) -- cgit v1.2.3 From 0a5b106ddd333f2dec2b62badeca98e6091ba805 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 27 Sep 2019 13:35:45 +0700 Subject: Extract scheduled statuses actions from `MastodonAPIController` to `ScheduledActivityController` --- .../controllers/mastodon_api_controller.ex | 51 ---------------------- .../controllers/scheduled_activity_controller.ex | 51 ++++++++++++++++++++++ lib/pleroma/web/router.ex | 8 ++-- 3 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 82bba43e5..1f6211917 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -20,7 +20,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Pagination alias Pleroma.Plugs.RateLimiter alias Pleroma.Repo - alias Pleroma.ScheduledActivity alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web @@ -35,7 +34,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonView alias Pleroma.Web.MastodonAPI.ReportView - alias Pleroma.Web.MastodonAPI.ScheduledActivityView alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MediaProxy alias Pleroma.Web.OAuth.App @@ -396,55 +394,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do - with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do - conn - |> add_link_headers(scheduled_activities) - |> put_view(ScheduledActivityView) - |> render("index.json", %{scheduled_activities: scheduled_activities}) - end - end - - def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do - with %ScheduledActivity{} = scheduled_activity <- - ScheduledActivity.get(user, scheduled_activity_id) do - conn - |> put_view(ScheduledActivityView) - |> render("show.json", %{scheduled_activity: scheduled_activity}) - else - _ -> {:error, :not_found} - end - end - - def update_scheduled_status( - %{assigns: %{user: user}} = conn, - %{"id" => scheduled_activity_id} = params - ) do - with %ScheduledActivity{} = scheduled_activity <- - ScheduledActivity.get(user, scheduled_activity_id), - {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do - conn - |> put_view(ScheduledActivityView) - |> render("show.json", %{scheduled_activity: scheduled_activity}) - else - nil -> {:error, :not_found} - error -> error - end - end - - def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do - with %ScheduledActivity{} = scheduled_activity <- - ScheduledActivity.get(user, scheduled_activity_id), - {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do - conn - |> put_view(ScheduledActivityView) - |> render("show.json", %{scheduled_activity: scheduled_activity}) - else - nil -> {:error, :not_found} - error -> error - end - end - def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do id = List.wrap(id) q = from(u in User, where: u.id in ^id) diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex new file mode 100644 index 000000000..0a56b10b6 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.ScheduledActivity + alias Pleroma.Web.MastodonAPI.MastodonAPI + + plug(:assign_scheduled_activity when action != :index) + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @doc "GET /api/v1/scheduled_statuses" + def index(%{assigns: %{user: user}} = conn, params) do + with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do + conn + |> add_link_headers(scheduled_activities) + |> render("index.json", scheduled_activities: scheduled_activities) + end + end + + @doc "GET /api/v1/scheduled_statuses/:id" + def show(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do + render(conn, "show.json", scheduled_activity: scheduled_activity) + end + + @doc "PUT /api/v1/scheduled_statuses/:id" + def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do + with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do + render(conn, "show.json", scheduled_activity: scheduled_activity) + end + end + + @doc "DELETE /api/v1/scheduled_statuses/:id" + def delete(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do + with {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do + render(conn, "show.json", scheduled_activity: scheduled_activity) + end + end + + defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do + case ScheduledActivity.get(user, id) do + %ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity) + nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8bf55631e..e12e6d313 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -339,8 +339,8 @@ defmodule Pleroma.Web.Router do post("/notifications/dismiss", NotificationController, :dismiss) delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) - get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses) - get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status) + get("/scheduled_statuses", ScheduledActivityController, :index) + get("/scheduled_statuses/:id", ScheduledActivityController, :show) get("/lists", ListController, :index) get("/lists/:id", ListController, :show) @@ -377,8 +377,8 @@ defmodule Pleroma.Web.Router do post("/statuses/:id/mute", StatusController, :mute_conversation) post("/statuses/:id/unmute", StatusController, :unmute_conversation) - put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status) - delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status) + put("/scheduled_statuses/:id", ScheduledActivityController, :update) + delete("/scheduled_statuses/:id", ScheduledActivityController, :delete) post("/polls/:id/votes", MastodonAPIController, :poll_vote) -- cgit v1.2.3 From 8d315301193a39575291db5c5d9fabe492261664 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 27 Sep 2019 11:46:20 +0700 Subject: Cleanup ScheduledActivityView --- .../mastodon_api/views/scheduled_activity_view.ex | 23 +++++++--------------- 1 file changed, 7 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex index 0aae15ab9..fc042a276 100644 --- a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex +++ b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex @@ -7,11 +7,10 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do alias Pleroma.ScheduledActivity alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MastodonAPI.ScheduledActivityView alias Pleroma.Web.MastodonAPI.StatusView def render("index.json", %{scheduled_activities: scheduled_activities}) do - render_many(scheduled_activities, ScheduledActivityView, "show.json") + render_many(scheduled_activities, __MODULE__, "show.json") end def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do @@ -24,12 +23,8 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do end defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do - try do - attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment) - Map.put(data, :media_attachments, attachments) - rescue - _ -> data - end + attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment) + Map.put(data, :media_attachments, attachments) end defp with_media_attachments(data, _), do: data @@ -45,13 +40,9 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do in_reply_to_id: params["in_reply_to_id"] } - data = - if media_ids = params["media_ids"] do - Map.put(data, :media_ids, media_ids) - else - data - end - - data + case params["media_ids"] do + nil -> data + media_ids -> Map.put(data, :media_ids, media_ids) + end end end -- cgit v1.2.3 From 99c5a35890f470c30661e900380bcd7c4b75d6d0 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 27 Sep 2019 14:25:17 +0700 Subject: Extract follow requests actions from `MastodonAPIController` to `FollowRequestController` --- .../controllers/follow_request_controller.ex | 49 ++++++++++++++++++++++ .../controllers/mastodon_api_controller.ex | 36 ---------------- lib/pleroma/web/router.ex | 6 +-- 3 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex new file mode 100644 index 000000000..267014b97 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -0,0 +1,49 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FollowRequestController do + use Pleroma.Web, :controller + + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) + plug(:assign_follower when action != :index) + + action_fallback(:errors) + + @doc "GET /api/v1/follow_requests" + def index(%{assigns: %{user: followed}} = conn, _params) do + follow_requests = User.get_follow_requests(followed) + + render(conn, "accounts.json", for: followed, users: follow_requests, as: :user) + end + + @doc "POST /api/v1/follow_requests/:id/authorize" + def authorize(%{assigns: %{user: followed, follower: follower}} = conn, _params) do + with {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do + render(conn, "relationship.json", user: followed, target: follower) + end + end + + @doc "POST /api/v1/follow_requests/:id/reject" + def reject(%{assigns: %{user: followed, follower: follower}} = conn, _params) do + with {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do + render(conn, "relationship.json", user: followed, target: follower) + end + end + + defp assign_follower(%{params: %{"id" => id}} = conn, _) do + case User.get_cached_by_id(id) do + %User{} = follower -> assign(conn, :follower, follower) + nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() + end + end + + defp errors(conn, {:error, message}) do + conn + |> put_status(:forbidden) + |> json(%{error: message}) + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 82bba43e5..0ee9f034a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -550,42 +550,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def follow_requests(%{assigns: %{user: followed}} = conn, _params) do - follow_requests = User.get_follow_requests(followed) - - conn - |> put_view(AccountView) - |> render("accounts.json", %{for: followed, users: follow_requests, as: :user}) - end - - def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do - with %User{} = follower <- User.get_cached_by_id(id), - {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: followed, target: follower}) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - - def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do - with %User{} = follower <- User.get_cached_by_id(id), - {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: followed, target: follower}) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)}, {_, true} <- {:followed, follower.id != followed.id}, diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8bf55631e..72d3827a5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -323,7 +323,7 @@ defmodule Pleroma.Web.Router do get("/accounts/:id/lists", MastodonAPIController, :account_lists) get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array) - get("/follow_requests", MastodonAPIController, :follow_requests) + get("/follow_requests", FollowRequestController, :index) get("/blocks", MastodonAPIController, :blocks) get("/mutes", MastodonAPIController, :mutes) @@ -419,8 +419,8 @@ defmodule Pleroma.Web.Router do post("/accounts/:id/mute", MastodonAPIController, :mute) post("/accounts/:id/unmute", MastodonAPIController, :unmute) - post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request) - post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request) + post("/follow_requests/:id/authorize", FollowRequestController, :authorize) + post("/follow_requests/:id/reject", FollowRequestController, :reject) post("/domain_blocks", MastodonAPIController, :block_domain) delete("/domain_blocks", MastodonAPIController, :unblock_domain) -- cgit v1.2.3 From 408750b94e1374c815c80a1060892231f40b54fb Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 27 Sep 2019 14:28:05 +0700 Subject: Extract domain blocks actions from `MastodonAPIController` to `DomainBlockController` --- .../controllers/domain_block_controller.ex | 26 ++++++++++++++++++++++ .../controllers/mastodon_api_controller.ex | 14 ------------ lib/pleroma/web/router.ex | 6 ++--- 3 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex new file mode 100644 index 000000000..03db6c9b8 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.DomainBlockController do + use Pleroma.Web, :controller + + alias Pleroma.User + + @doc "GET /api/v1/domain_blocks" + def index(%{assigns: %{user: %{info: info}}} = conn, _) do + json(conn, Map.get(info, :domain_blocks, [])) + end + + @doc "POST /api/v1/domain_blocks" + def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do + User.block_domain(blocker, domain) + json(conn, %{}) + end + + @doc "DELETE /api/v1/domain_blocks" + def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do + User.unblock_domain(blocker, domain) + json(conn, %{}) + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 82bba43e5..e96bf6fd9 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -715,20 +715,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do - json(conn, info.domain_blocks || []) - end - - def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do - User.block_domain(blocker, domain) - json(conn, %{}) - end - - def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do - User.unblock_domain(blocker, domain) - json(conn, %{}) - end - def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %User{} = subscription_target <- User.get_cached_by_id(id), {:ok, subscription_target} = User.subscribe(user, subscription_target) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8bf55631e..d370f30db 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -346,7 +346,7 @@ defmodule Pleroma.Web.Router do get("/lists/:id", ListController, :show) get("/lists/:id/accounts", ListController, :list_accounts) - get("/domain_blocks", MastodonAPIController, :domain_blocks) + get("/domain_blocks", DomainBlockController, :index) get("/filters", MastodonAPIController, :get_filters) @@ -422,8 +422,8 @@ defmodule Pleroma.Web.Router do post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request) post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request) - post("/domain_blocks", MastodonAPIController, :block_domain) - delete("/domain_blocks", MastodonAPIController, :unblock_domain) + post("/domain_blocks", DomainBlockController, :create) + delete("/domain_blocks", DomainBlockController, :delete) post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe) post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe) -- cgit v1.2.3 From f9380289eb251c818e87e8f0ad0a41fc8bdd90aa Mon Sep 17 00:00:00 2001 From: minibikini Date: Fri, 27 Sep 2019 21:59:23 +0000 Subject: Add `remote_ip` plug --- lib/pleroma/plugs/remote_ip.ex | 54 ++++++++++++++++++++++++++++++++++++++++++ lib/pleroma/web/endpoint.ex | 5 +--- 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 lib/pleroma/plugs/remote_ip.ex (limited to 'lib') diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex new file mode 100644 index 000000000..fdedc27ee --- /dev/null +++ b/lib/pleroma/plugs/remote_ip.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.RemoteIp do + @moduledoc """ + This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. + """ + + @behaviour Plug + + @headers ~w[ + forwarded + x-forwarded-for + x-client-ip + x-real-ip + ] + + # https://en.wikipedia.org/wiki/Localhost + # https://en.wikipedia.org/wiki/Private_network + @reserved ~w[ + 127.0.0.0/8 + ::1/128 + fc00::/7 + 10.0.0.0/8 + 172.16.0.0/12 + 192.168.0.0/16 + ] + + def init(_), do: nil + + def call(conn, _) do + config = Pleroma.Config.get(__MODULE__, []) + + if Keyword.get(config, :enabled, false) do + RemoteIp.call(conn, remote_ip_opts(config)) + else + conn + end + end + + defp remote_ip_opts(config) do + headers = config |> Keyword.get(:headers, @headers) |> MapSet.new() + reserved = Keyword.get(config, :reserved, @reserved) + + proxies = + config + |> Keyword.get(:proxies, []) + |> Enum.concat(reserved) + |> Enum.map(&InetCidr.parse/1) + + {headers, proxies} + end +end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index eb805e853..2212e93f4 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -97,10 +97,7 @@ defmodule Pleroma.Web.Endpoint do extra: extra ) - # Note: the plug and its configuration is compile-time this can't be upstreamed yet - if proxies = Pleroma.Config.get([__MODULE__, :reverse_proxies]) do - plug(RemoteIp, proxies: proxies) - end + plug(Pleroma.Plugs.RemoteIp) defmodule Instrumenter do use Prometheus.PhoenixInstrumenter -- cgit v1.2.3 From 374f83d29b5793d75a3a6be7c18cf52cfed42b64 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 28 Sep 2019 01:56:20 +0300 Subject: Fix not being able to post empty statuses with attachments Attachment field was filled in after the empty status check --- lib/pleroma/web/common_api/activity_draft.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index aa7c8c381..f7da81b34 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -40,11 +40,11 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> put_params(params) |> status() |> summary() + |> with_valid(&attachments/1) |> full_payload() |> expires_at() |> poll() |> with_valid(&in_reply_to/1) - |> with_valid(&attachments/1) |> with_valid(&in_reply_to_conversation/1) |> with_valid(&visibility/1) |> content() -- cgit v1.2.3 From 9202904da9b48eb2a3884b8e89ea879e01d44b9a Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sat, 28 Sep 2019 01:21:28 +0200 Subject: status_controller.ex: Posting media status without content defined --- lib/pleroma/web/mastodon_api/controllers/status_controller.ex | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index f7da10289..ae3d51575 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -103,6 +103,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end end + def create(%{assigns: %{user: _user}} = conn, %{"media_ids" => _} = params) do + create(conn, Map.put(params, "status", "")) + end + @doc "GET /api/v1/statuses/:id" def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), -- cgit v1.2.3 From e9d1aa75d5dc0859b692e891f6e65949208a5f0f Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sun, 29 Sep 2019 18:43:27 +0300 Subject: Revert subscription refactoring. As discussed in pleroma-meta#2 This reverts commit eb9aa7aa1095de150d036839c78c402019efb4b1, reversing changes made to c4fbb56984d8f86df948cfd9b0f7c081d688c365. --- lib/pleroma/notification.ex | 1 + lib/pleroma/subscription_notification.ex | 260 --------------------- lib/pleroma/web/activity_pub/activity_pub.ex | 2 - .../subscription_notification_controller.ex | 71 ------ lib/pleroma/web/pleroma_api/pleroma_api.ex | 40 ---- .../views/subscription_notification_view.ex | 61 ----- lib/pleroma/web/push/impl.ex | 3 +- lib/pleroma/web/router.ex | 8 - 8 files changed, 2 insertions(+), 444 deletions(-) delete mode 100644 lib/pleroma/subscription_notification.ex delete mode 100644 lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex delete mode 100644 lib/pleroma/web/pleroma_api/pleroma_api.ex delete mode 100644 lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index d19924289..d94ae5971 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -230,6 +230,7 @@ defmodule Pleroma.Notification do [] |> Utils.maybe_notify_to_recipients(activity) |> Utils.maybe_notify_mentioned_recipients(activity) + |> Utils.maybe_notify_subscribers(activity) |> Enum.uniq() User.get_users_from_set(recipients, local_only) diff --git a/lib/pleroma/subscription_notification.ex b/lib/pleroma/subscription_notification.ex deleted file mode 100644 index 1349d988c..000000000 --- a/lib/pleroma/subscription_notification.ex +++ /dev/null @@ -1,260 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.SubscriptionNotification do - use Ecto.Schema - - alias Pleroma.Activity - alias Pleroma.Object - alias Pleroma.Pagination - alias Pleroma.Repo - alias Pleroma.SubscriptionNotification - alias Pleroma.User - alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.Push - alias Pleroma.Web.Streamer - - import Ecto.Query - import Ecto.Changeset - - @type t :: %__MODULE__{} - - schema "subscription_notifications" do - belongs_to(:user, User, type: FlakeId.Ecto.CompatType) - belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) - - timestamps() - end - - def changeset(%SubscriptionNotification{} = notification, attrs) do - cast(notification, attrs, []) - end - - def for_user_query(user, opts \\ []) do - query = - SubscriptionNotification - |> where(user_id: ^user.id) - |> where( - [n, a], - fragment( - "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')", - a.actor - ) - ) - |> join(:inner, [n], activity in assoc(n, :activity)) - |> join(:left, [n, a], object in Object, - on: - fragment( - "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)", - object.data, - a.data - ) - ) - |> preload([n, a, o], activity: {a, object: o}) - - if opts[:with_muted] do - query - else - query - |> where([n, a], a.actor not in ^user.info.muted_notifications) - |> where([n, a], a.actor not in ^user.info.blocks) - |> where( - [n, a], - fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks - ) - |> join(:left, [n, a], tm in Pleroma.ThreadMute, - on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data) - ) - |> where([n, a, o, tm], is_nil(tm.user_id)) - end - end - - def for_user(user, opts \\ %{}) do - user - |> for_user_query(opts) - |> Pagination.fetch_paginated(opts) - end - - @doc """ - Returns notifications for user received since given date. - - ## Examples - - iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33]) - [%Pleroma.SubscriptionNotification{}, %Pleroma.SubscriptionNotification{}] - - iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33]) - [] - """ - @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()] - def for_user_since(user, date) do - user - |> for_user_query() - |> where([n], n.updated_at > ^date) - |> Repo.all() - end - - def clear_up_to(%{id: user_id} = _user, id) do - from( - n in SubscriptionNotification, - where: n.user_id == ^user_id, - where: n.id <= ^id - ) - |> Repo.delete_all([]) - end - - def get(%{id: user_id} = _user, id) do - query = - from( - n in SubscriptionNotification, - where: n.id == ^id, - join: activity in assoc(n, :activity), - preload: [activity: activity] - ) - - case Repo.one(query) do - %{user_id: ^user_id} = notification -> - {:ok, notification} - - _ -> - {:error, "Cannot get notification"} - end - end - - def clear(user) do - from(n in SubscriptionNotification, where: n.user_id == ^user.id) - |> Repo.delete_all() - end - - def destroy_multiple(%{id: user_id} = _user, ids) do - from(n in SubscriptionNotification, - where: n.id in ^ids, - where: n.user_id == ^user_id - ) - |> Repo.delete_all() - end - - def dismiss(%{id: user_id} = _user, id) do - case Repo.get(SubscriptionNotification, id) do - %{user_id: ^user_id} = notification -> - Repo.delete(notification) - - _ -> - {:error, "Cannot dismiss notification"} - end - end - - def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do - case Object.normalize(activity) do - %{data: %{"type" => "Answer"}} -> - {:ok, []} - - _ -> - users = get_notified_from_activity(activity) - notifications = Enum.map(users, fn user -> create_notification(activity, user) end) - {:ok, notifications} - end - end - - def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity) - when type in ["Like", "Announce", "Follow"] do - notifications = - activity - |> get_notified_from_activity() - |> Enum.map(&create_notification(activity, &1)) - - {:ok, notifications} - end - - def create_notifications(_), do: {:ok, []} - - # TODO move to sql, too. - def create_notification(%Activity{} = activity, %User{} = user) do - unless skip?(activity, user) do - notification = %SubscriptionNotification{user_id: user.id, activity: activity} - {:ok, notification} = Repo.insert(notification) - Streamer.stream("user", notification) - Streamer.stream("user:subscription_notification", notification) - Push.send(notification) - notification - end - end - - def get_notified_from_activity(activity, local_only \\ true) - - def get_notified_from_activity( - %Activity{data: %{"to" => _, "type" => type} = _data} = activity, - local_only - ) - when type in ["Create", "Like", "Announce", "Follow"] do - [] - |> Utils.maybe_notify_subscribers(activity) - |> Enum.uniq() - |> User.get_users_from_set(local_only) - end - - def get_notified_from_activity(_, _local_only), do: [] - - @spec skip?(Activity.t(), User.t()) :: boolean() - def skip?(activity, user) do - [ - :self, - :followers, - :follows, - :non_followers, - :non_follows, - :recently_followed - ] - |> Enum.any?(&skip?(&1, activity, user)) - end - - @spec skip?(atom(), Activity.t(), User.t()) :: boolean() - def skip?(:self, activity, user) do - activity.data["actor"] == user.ap_id - end - - def skip?( - :followers, - %{data: %{"actor" => actor}}, - %{info: %{notification_settings: %{"followers" => false}}} = user - ) do - actor - |> User.get_cached_by_ap_id() - |> User.following?(user) - end - - def skip?( - :non_followers, - activity, - %{info: %{notification_settings: %{"non_followers" => false}}} = user - ) do - actor = activity.data["actor"] - follower = User.get_cached_by_ap_id(actor) - !User.following?(follower, user) - end - - def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do - actor = activity.data["actor"] - followed = User.get_cached_by_ap_id(actor) - User.following?(user, followed) - end - - def skip?( - :non_follows, - activity, - %{info: %{notification_settings: %{"non_follows" => false}}} = user - ) do - actor = activity.data["actor"] - followed = User.get_cached_by_ap_id(actor) - !User.following?(user, followed) - end - - def skip?(:recently_followed, %{data: %{"type" => "Follow", "actor" => actor}}, user) do - user - |> SubscriptionNotification.for_user() - |> Enum.any?(&match?(%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}}, &1)) - end - - def skip?(_, _, _), do: false -end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 7e83e27e5..8d0a57623 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Object.Fetcher alias Pleroma.Pagination alias Pleroma.Repo - alias Pleroma.SubscriptionNotification alias Pleroma.Upload alias Pleroma.User alias Pleroma.Web.ActivityPub.MRF @@ -152,7 +151,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id}) Notification.create_notifications(activity) - SubscriptionNotification.create_notifications(activity) participations = activity diff --git a/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex deleted file mode 100644 index 37c2222de..000000000 --- a/lib/pleroma/web/pleroma_api/controllers/subscription_notification_controller.ex +++ /dev/null @@ -1,71 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do - use Pleroma.Web, :controller - - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] - - alias Pleroma.Activity - alias Pleroma.SubscriptionNotification - alias Pleroma.User - alias Pleroma.Web.PleromaAPI.PleromaAPI - - def index(%{assigns: %{user: user}} = conn, params) do - notifications = - user - |> PleromaAPI.get_subscription_notifications(params) - |> Enum.map(&build_notification_data/1) - - conn - |> add_link_headers(notifications) - |> render("index.json", %{notifications: notifications, for: user}) - end - - def show(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do - with {:ok, notification} <- SubscriptionNotification.get(user, id) do - render(conn, "show.json", %{ - subscription_notification: build_notification_data(notification), - for: user - }) - else - {:error, reason} -> - conn - |> put_status(:forbidden) - |> json(%{"error" => reason}) - end - end - - def clear(%{assigns: %{user: user}} = conn, _params) do - SubscriptionNotification.clear(user) - json(conn, %{}) - end - - def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do - with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do - json(conn, %{}) - else - {:error, reason} -> - conn - |> put_status(:forbidden) - |> json(%{"error" => reason}) - end - end - - def destroy_multiple( - %{assigns: %{user: user}} = conn, - %{"ids" => ids} = _params - ) do - SubscriptionNotification.destroy_multiple(user, ids) - json(conn, %{}) - end - - defp build_notification_data(%{activity: %{data: data}} = notification) do - %{ - notification: notification, - actor: User.get_cached_by_ap_id(data["actor"]), - parent_activity: Activity.get_create_by_object_ap_id(data["object"]) - } - end -end diff --git a/lib/pleroma/web/pleroma_api/pleroma_api.ex b/lib/pleroma/web/pleroma_api/pleroma_api.ex deleted file mode 100644 index 480964845..000000000 --- a/lib/pleroma/web/pleroma_api/pleroma_api.ex +++ /dev/null @@ -1,40 +0,0 @@ -defmodule Pleroma.Web.PleromaAPI.PleromaAPI do - import Ecto.Query - import Ecto.Changeset - - alias Pleroma.Activity - alias Pleroma.Pagination - alias Pleroma.SubscriptionNotification - - def get_subscription_notifications(user, params \\ %{}) do - options = cast_params(params) - - user - |> SubscriptionNotification.for_user_query(options) - |> restrict(:exclude_types, options) - |> Pagination.fetch_paginated(params) - end - - defp cast_params(params) do - param_types = %{ - exclude_types: {:array, :string}, - reblogs: :boolean, - with_muted: :boolean - } - - changeset = cast({%{}, param_types}, params, Map.keys(param_types)) - changeset.changes - end - - defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do - ap_types = - mastodon_types - |> Enum.map(&Activity.from_mastodon_notification_type/1) - |> Enum.filter(& &1) - - query - |> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data)) - end - - defp restrict(query, _, _), do: query -end diff --git a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex deleted file mode 100644 index fc41a7389..000000000 --- a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex +++ /dev/null @@ -1,61 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do - use Pleroma.Web, :view - - alias Pleroma.Activity - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MastodonAPI.AccountView - alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.PleromaAPI.SubscriptionNotificationView - - def render("index.json", %{notifications: notifications, for: user}) do - safe_render_many(notifications, SubscriptionNotificationView, "show.json", %{for: user}) - end - - def render("show.json", %{ - subscription_notification: %{ - notification: %{activity: activity} = notification, - actor: actor, - parent_activity: parent_activity - }, - for: user - }) do - mastodon_type = Activity.mastodon_notification_type(activity) - - response = %{ - id: to_string(notification.id), - type: mastodon_type, - created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at), - account: AccountView.render("account.json", %{user: actor, for: user}) - } - - case mastodon_type do - "mention" -> - response - |> Map.merge(%{ - status: StatusView.render("show.json", %{activity: activity, for: user}) - }) - - "favourite" -> - response - |> Map.merge(%{ - status: StatusView.render("show.json", %{activity: parent_activity, for: user}) - }) - - "reblog" -> - response - |> Map.merge(%{ - status: StatusView.render("show.json", %{activity: parent_activity, for: user}) - }) - - "follow" -> - response - - _ -> - nil - end - end -end diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 7ea5607fa..35d3ff07c 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.Push.Impl do alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo - alias Pleroma.SubscriptionNotification alias Pleroma.User alias Pleroma.Web.Metadata.Utils alias Pleroma.Web.Push.Subscription @@ -20,7 +19,7 @@ defmodule Pleroma.Web.Push.Impl do @types ["Create", "Follow", "Announce", "Like"] @doc "Performs sending notifications for user subscriptions" - @spec perform(Notification.t() | SubscriptionNotification.t()) :: list(any) | :error + @spec perform(Notification.t()) :: list(any) | :error def perform( %{ activity: %{data: %{"type" => activity_type}, id: activity_id} = activity, diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index a025474e2..805bef16f 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -293,14 +293,6 @@ defmodule Pleroma.Web.Router do pipe_through(:oauth_read) get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses) get("/conversations/:id", PleromaAPIController, :conversation) - - scope "/subscription_notifications" do - post("/clear", SubscriptionNotificationController, :clear) - post("/dismiss", SubscriptionNotificationController, :dismiss) - delete("/destroy_multiple", SubscriptionNotificationController, :destroy_multiple) - get("/", SubscriptionNotificationController, :index) - get("/:id", SubscriptionNotificationController, :show) - end end scope [] do -- cgit v1.2.3 From d4d88b3361ea57d763c5093470b7ebaee6bcf11c Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 30 Sep 2019 16:52:07 +0700 Subject: Extract conversation actions from `MastodonAPIController` to ConversationController --- .../controllers/conversation_controller.ex | 32 ++++++++++++++++++++++ .../controllers/mastodon_api_controller.ex | 27 ------------------ .../web/mastodon_api/views/conversation_view.ex | 21 +++++--------- lib/pleroma/web/router.ex | 4 +-- 4 files changed, 41 insertions(+), 43 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex new file mode 100644 index 000000000..ea1e36a12 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ConversationController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.Conversation.Participation + alias Pleroma.Repo + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @doc "GET /api/v1/conversations" + def index(%{assigns: %{user: user}} = conn, params) do + participations = Participation.for_user_with_last_activity_id(user, params) + + conn + |> add_link_headers(participations) + |> render("participations.json", participations: participations, for: user) + end + + @doc "POST /api/v1/conversations/:id/read" + def read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do + with %Participation{} = participation <- + Repo.get_by(Participation, id: participation_id, user_id: user.id), + {:ok, participation} <- Participation.mark_as_read(participation) do + render(conn, "participation.json", participation: participation, for: user) + end + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 0878f7ba6..650fb74cd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -12,7 +12,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Activity alias Pleroma.Bookmark alias Pleroma.Config - alias Pleroma.Conversation.Participation alias Pleroma.Emoji alias Pleroma.HTTP alias Pleroma.Object @@ -27,7 +26,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AppView - alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonView @@ -1003,31 +1001,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do render_error(conn, :forbidden, "Invalid credentials") end - def conversations(%{assigns: %{user: user}} = conn, params) do - participations = Participation.for_user_with_last_activity_id(user, params) - - conversations = - Enum.map(participations, fn participation -> - ConversationView.render("participation.json", %{participation: participation, for: user}) - end) - - conn - |> add_link_headers(participations) - |> json(conversations) - end - - def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do - with %Participation{} = participation <- - Repo.get_by(Participation, id: participation_id, user_id: user.id), - {:ok, participation} <- Participation.mark_as_read(participation) do - participation_view = - ConversationView.render("participation.json", %{participation: participation, for: user}) - - conn - |> json(participation_view) - end - end - def password_reset(conn, params) do nickname_or_email = params["email"] || params["nickname"] diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 4aeb79d81..2c5767dd8 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -11,6 +11,10 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.StatusView + def render("participations.json", %{participations: participations, for: user}) do + render_many(participations, __MODULE__, "participation.json", as: :participation, for: user) + end + def render("participation.json", %{participation: participation, for: user}) do participation = Repo.preload(participation, conversation: [], recipients: []) @@ -23,25 +27,14 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do end activity = Activity.get_by_id_with_object(last_activity_id) - - last_status = StatusView.render("show.json", %{activity: activity, for: user}) - # Conversations return all users except the current user. - users = - participation.recipients - |> Enum.reject(&(&1.id == user.id)) - - accounts = - AccountView.render("accounts.json", %{ - users: users, - as: :user - }) + users = Enum.reject(participation.recipients, &(&1.id == user.id)) %{ id: participation.id |> to_string(), - accounts: accounts, + accounts: render(AccountView, "accounts.json", users: users, as: :user), unread: !participation.read, - last_status: last_status + last_status: render(StatusView, "show.json", activity: activity, for: user) } end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 805bef16f..5dafa3693 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -344,8 +344,8 @@ defmodule Pleroma.Web.Router do get("/suggestions", MastodonAPIController, :suggestions) - get("/conversations", MastodonAPIController, :conversations) - post("/conversations/:id/read", MastodonAPIController, :conversation_read) + get("/conversations", ConversationController, :index) + post("/conversations/:id/read", ConversationController, :read) get("/endorsements", MastodonAPIController, :empty_array) end -- cgit v1.2.3 From b7877e9b1c61e42d60bb65deef0cec7f1103dd89 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 27 Sep 2019 11:40:40 +0000 Subject: mastodon api: implement rendering of listen activities --- lib/pleroma/web/mastodon_api/views/status_view.ex | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 2321d0de2..cf024a83c 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -368,6 +368,23 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end + def render("listen.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do + object = Object.normalize(activity) + + user = get_user(activity.data["actor"]) + created_at = Utils.to_masto_date(activity.data["published"]) + + %{ + id: activity.id, + account: AccountView.render("account.json", %{user: user, for: opts[:for]}), + created_at: created_at, + title: object.data["title"] |> HTML.strip_tags(), + artist: object.data["artist"] |> HTML.strip_tags(), + album: object.data["album"] |> HTML.strip_tags(), + length: object.data["length"] + } + end + def render("poll.json", %{object: object} = opts) do {multiple, options} = case object.data do -- cgit v1.2.3 From 1f9de2a8cdc1913b26afab1f914aea526db608d8 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 27 Sep 2019 12:22:35 +0000 Subject: activitypub: implement IR-level considerations for Listen activities --- lib/pleroma/web/activity_pub/activity_pub.ex | 20 ++++++++++++++++++++ lib/pleroma/web/activity_pub/utils.ex | 17 ++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 8d0a57623..425073541 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -248,6 +248,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + def listen(%{to: to, actor: actor, context: context, object: object} = params) do + additional = params[:additional] || %{} + # only accept false as false value + local = !(params[:local] == false) + published = params[:published] + + with listen_data <- + make_listen_data( + %{to: to, actor: actor, published: published, context: context, object: object}, + additional + ), + {:ok, activity} <- insert(listen_data, local), + :ok <- maybe_federate(activity) do + {:ok, activity} + else + {:error, message} -> + {:error, message} + end + end + def accept(%{to: to, actor: actor, object: object} = params) do # only accept false as false value local = !(params[:local] == false) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 30628a793..2ba182f4e 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do require Logger require Pleroma.Constants - @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"] + @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer", "Audio"] @supported_report_states ~w(open closed resolved) @valid_visibilities ~w(public unlisted private direct) @@ -581,6 +581,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Map.merge(additional) end + #### Listen-related helpers + def make_listen_data(params, additional) do + published = params.published || make_date() + + %{ + "type" => "Listen", + "to" => params.to |> Enum.uniq(), + "actor" => params.actor.ap_id, + "object" => params.object, + "published" => published, + "context" => params.context + } + |> Map.merge(additional) + end + #### Flag-related helpers @spec make_flag_data(map(), map()) :: map() def make_flag_data(%{actor: actor, context: context, content: content} = params, additional) do -- cgit v1.2.3 From 172c74a77baf5b8910987e19c620158d0497d16a Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 27 Sep 2019 12:40:31 +0000 Subject: activitypub: transmogrifier: implement support for Listen activities --- lib/pleroma/web/activity_pub/transmogrifier.ex | 33 +++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index dad2fead8..63877248a 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -430,6 +430,36 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end + def handle_incoming( + %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data, + options + ) do + actor = Containment.get_actor(data) + + data = + Map.put(data, "actor", actor) + |> fix_addressing + + with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do + options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) + object = fix_object(object, options) + + params = %{ + to: data["to"], + object: object, + actor: user, + context: nil, + local: false, + published: data["published"], + additional: Map.take(data, ["cc", "id"]) + } + + ActivityPub.listen(params) + else + _e -> :error + end + end + def handle_incoming( %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data, _options @@ -765,7 +795,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # internal -> Mastodon # """ - def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do + def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data) + when activity_type in ["Create", "Listen"] do object = object_id |> Object.normalize() -- cgit v1.2.3 From 2c82d8603bb4c3f7281023752dc78aa31a814ab6 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sat, 28 Sep 2019 00:24:32 +0000 Subject: common api: implement scrobbling --- lib/pleroma/web/common_api/common_api.ex | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index a00e4b0d8..a040a6ce2 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -212,6 +212,24 @@ defmodule Pleroma.Web.CommonAPI do |> check_expiry_date() end + def listen(user, %{"title" => _} = data) do + with visibility <- data["visibility"] || "public", + {to, cc} <- get_to_and_cc(user, [], nil, visibility, nil), + listen_data <- + Map.take(data, ["album", "artist", "title", "length"]) + |> Map.put("type", "Audio"), + {:ok, activity} <- + ActivityPub.listen(%{ + actor: user, + to: to, + object: listen_data, + context: Utils.generate_context_id(), + additional: %{cc: cc} + }) do + {:ok, activity} + end + end + def post(user, %{"status" => _} = data) do with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do draft.changes -- cgit v1.2.3 From 7cad6ea67a47df2776a15dd69b9e408c517800e6 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sat, 28 Sep 2019 02:12:12 +0000 Subject: pleroma api: hook up scrobbler controller --- lib/pleroma/web/activity_pub/activity_pub.ex | 17 +++++++++ lib/pleroma/web/mastodon_api/views/status_view.ex | 4 +++ .../controllers/pleroma_api_controller.ex | 42 +++++++++++++++++++++- lib/pleroma/web/router.ex | 11 ++++++ 4 files changed, 73 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 425073541..95f994c17 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -608,6 +608,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_thread_visibility(query, _, _), do: query + def fetch_user_abstract_activities(user, reading_user, params \\ %{}) do + params = + params + |> Map.put("user", reading_user) + |> Map.put("actor_id", user.ap_id) + |> Map.put("whole_db", true) + + recipients = + user_activities_recipients(%{ + "godmode" => params["godmode"], + "reading_user" => reading_user + }) + + fetch_activities(recipients, params) + |> Enum.reverse() + end + def fetch_user_activities(user, reading_user, params \\ %{}) do params = params diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index cf024a83c..d398f7853 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -385,6 +385,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end + def render("listens.json", opts) do + safe_render_many(opts.activities, StatusView, "listen.json", opts) + end + def render("poll.json", %{object: object} = opts) do {multiple, options} = case object.data do diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index d17ccf84d..1b0ed1f40 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -5,11 +5,13 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2] alias Pleroma.Conversation.Participation alias Pleroma.Notification + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.StatusView @@ -86,4 +88,42 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do |> render("index.json", %{notifications: notifications, for: user}) end end + + def update_now_playing(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do + params = + if !params["length"] do + params + else + params + |> Map.put("length", fetch_integer_param(params, "length")) + end + + with {:ok, activity} <- CommonAPI.listen(user, params) do + conn + |> put_view(StatusView) + |> render("listen.json", %{activity: activity, for: user}) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + end + end + + def user_now_playing(%{assigns: %{user: reading_user}} = conn, params) do + with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do + params = Map.put(params, "type", ["Listen"]) + + activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params) + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("listens.json", %{ + activities: activities, + for: reading_user, + as: :activity + }) + end + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 805bef16f..bd5f02af1 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -300,6 +300,17 @@ defmodule Pleroma.Web.Router do patch("/conversations/:id", PleromaAPIController, :update_conversation) post("/notifications/read", PleromaAPIController, :read_notification) end + + scope [] do + pipe_through(:oauth_write) + post("/now-playing", PleromaAPIController, :update_now_playing) + end + end + + scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do + pipe_through([:api, :oauth_read_or_public]) + + get("/accounts/:id/now-playing", PleromaAPIController, :user_now_playing) end scope "/api/v1", Pleroma.Web.MastodonAPI do -- cgit v1.2.3 From 71eff09e564ae3eeaf02acecbb8d89b7d4e2e511 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sat, 28 Sep 2019 12:12:35 +0000 Subject: common api: make sure the generated IR is actually federatable --- lib/pleroma/web/common_api/common_api.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index a040a6ce2..b02c47059 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -217,14 +217,16 @@ defmodule Pleroma.Web.CommonAPI do {to, cc} <- get_to_and_cc(user, [], nil, visibility, nil), listen_data <- Map.take(data, ["album", "artist", "title", "length"]) - |> Map.put("type", "Audio"), + |> Map.put("type", "Audio") + |> Map.put("to", to) + |> Map.put("cc", cc), {:ok, activity} <- ActivityPub.listen(%{ actor: user, to: to, object: listen_data, context: Utils.generate_context_id(), - additional: %{cc: cc} + additional: %{"cc" => cc} }) do {:ok, activity} end -- cgit v1.2.3 From 84712c35f9b316b0891edfa791aeb5e358613bd2 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sat, 28 Sep 2019 12:28:39 +0000 Subject: activitypub: object view: include child object for Listen activities --- lib/pleroma/web/activity_pub/views/object_view.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex index 0d63f0707..88c55acdd 100644 --- a/lib/pleroma/web/activity_pub/views/object_view.ex +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -15,7 +15,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do Map.merge(base, additional) end - def render("object.json", %{object: %Activity{data: %{"type" => "Create"}} = activity}) do + def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} = activity}) + when activity_type in ["Create", "Listen"] do base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() object = Object.normalize(activity) -- cgit v1.2.3 From 8b34b221cbec366e0a605b9e64dafceb76ed3fd3 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sat, 28 Sep 2019 12:29:00 +0000 Subject: common api: add some missing IR bits for listen activities' children --- lib/pleroma/web/common_api/common_api.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index b02c47059..2ec017ff8 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -219,7 +219,8 @@ defmodule Pleroma.Web.CommonAPI do Map.take(data, ["album", "artist", "title", "length"]) |> Map.put("type", "Audio") |> Map.put("to", to) - |> Map.put("cc", cc), + |> Map.put("cc", cc) + |> Map.put("actor", user.ap_id), {:ok, activity} <- ActivityPub.listen(%{ actor: user, -- cgit v1.2.3 From a6e1469767cd716eccf1106e3704130a4fc909b8 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 29 Sep 2019 00:18:06 +0000 Subject: router: change scrobble timeline route from now-playing to scrobbles --- lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex | 2 +- lib/pleroma/web/router.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 1b0ed1f40..6010732db 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -110,7 +110,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do end end - def user_now_playing(%{assigns: %{user: reading_user}} = conn, params) do + def user_scrobbles(%{assigns: %{user: reading_user}} = conn, params) do with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do params = Map.put(params, "type", ["Listen"]) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index bd5f02af1..8966e8cc0 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -310,7 +310,7 @@ defmodule Pleroma.Web.Router do scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do pipe_through([:api, :oauth_read_or_public]) - get("/accounts/:id/now-playing", PleromaAPIController, :user_now_playing) + get("/accounts/:id/scrobbles", PleromaAPIController, :user_scrobbles) end scope "/api/v1", Pleroma.Web.MastodonAPI do -- cgit v1.2.3 From e653edd182338fa8f4396341cea26cd5568f0107 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 29 Sep 2019 00:25:42 +0000 Subject: split scrobble functions into their own controller --- .../controllers/pleroma_api_controller.ex | 42 +---------------- .../pleroma_api/controllers/scrobble_controller.ex | 52 ++++++++++++++++++++++ lib/pleroma/web/router.ex | 4 +- 3 files changed, 55 insertions(+), 43 deletions(-) create mode 100644 lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 6010732db..d17ccf84d 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -5,13 +5,11 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2] + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] alias Pleroma.Conversation.Participation alias Pleroma.Notification - alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.StatusView @@ -88,42 +86,4 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do |> render("index.json", %{notifications: notifications, for: user}) end end - - def update_now_playing(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do - params = - if !params["length"] do - params - else - params - |> Map.put("length", fetch_integer_param(params, "length")) - end - - with {:ok, activity} <- CommonAPI.listen(user, params) do - conn - |> put_view(StatusView) - |> render("listen.json", %{activity: activity, for: user}) - else - {:error, message} -> - conn - |> put_status(:bad_request) - |> json(%{"error" => message}) - end - end - - def user_scrobbles(%{assigns: %{user: reading_user}} = conn, params) do - with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do - params = Map.put(params, "type", ["Listen"]) - - activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params) - - conn - |> add_link_headers(activities) - |> put_view(StatusView) - |> render("listens.json", %{ - activities: activities, - for: reading_user, - as: :activity - }) - end - end end diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex new file mode 100644 index 000000000..ac6cd8edd --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex @@ -0,0 +1,52 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.ScrobbleController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2] + + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.StatusView + + def update_now_playing(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do + params = + if !params["length"] do + params + else + params + |> Map.put("length", fetch_integer_param(params, "length")) + end + + with {:ok, activity} <- CommonAPI.listen(user, params) do + conn + |> put_view(StatusView) + |> render("listen.json", %{activity: activity, for: user}) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + end + end + + def user_scrobbles(%{assigns: %{user: reading_user}} = conn, params) do + with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do + params = Map.put(params, "type", ["Listen"]) + + activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params) + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("listens.json", %{ + activities: activities, + for: reading_user, + as: :activity + }) + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8966e8cc0..8e3a72656 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -303,14 +303,14 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_write) - post("/now-playing", PleromaAPIController, :update_now_playing) + post("/now-playing", ScrobbleController, :update_now_playing) end end scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do pipe_through([:api, :oauth_read_or_public]) - get("/accounts/:id/scrobbles", PleromaAPIController, :user_scrobbles) + get("/accounts/:id/scrobbles", ScrobbleController, :user_scrobbles) end scope "/api/v1", Pleroma.Web.MastodonAPI do -- cgit v1.2.3 From 1d7cbdaf7b2f3ff6576959ed26885d7545f31a14 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sun, 29 Sep 2019 02:18:34 +0000 Subject: change new scrobble endpoint --- lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex | 2 +- lib/pleroma/web/router.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex index ac6cd8edd..0fb978c5d 100644 --- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.StatusView - def update_now_playing(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do + def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do params = if !params["length"] do params diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8e3a72656..bf32cff1e 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -303,7 +303,7 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_write) - post("/now-playing", ScrobbleController, :update_now_playing) + post("/scrobble", ScrobbleController, :new_scrobble) end end -- cgit v1.2.3