Add scheduler for sending scheduled activities to the queue

This commit is contained in:
eugenijm 2019-04-03 18:55:04 +03:00
commit 2056efa714
13 changed files with 196 additions and 68 deletions

View file

@ -104,7 +104,8 @@ defmodule Pleroma.Application do
],
id: :cachex_idem
),
worker(Pleroma.FlakeId, [])
worker(Pleroma.FlakeId, []),
worker(Pleroma.ScheduledActivityWorker, [])
] ++
hackney_pool_children() ++
[

View file

@ -184,12 +184,4 @@ defmodule Pleroma.Object do
_ -> {:error, "Not found"}
end
end
def enforce_user_objects(user, object_ids) do
Object
|> where([o], fragment("?->>'actor' = ?", o.data, ^user.ap_id))
|> where([o], o.id in ^object_ids)
|> select([o], o.id)
|> Repo.all()
end
end

View file

@ -6,7 +6,6 @@ defmodule Pleroma.ScheduledActivity do
use Ecto.Schema
alias Pleroma.Config
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
@ -37,8 +36,6 @@ defmodule Pleroma.ScheduledActivity do
%{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
)
when is_list(media_ids) do
user = User.get_cached_by_id(changeset.data.user_id)
media_ids = Object.enforce_user_objects(user, media_ids) |> Enum.map(&to_string(&1))
media_attachments = Utils.attachments_from_ids(%{"media_ids" => media_ids})
params =
@ -79,8 +76,8 @@ defmodule Pleroma.ScheduledActivity do
def exceeds_daily_user_limit?(user_id, scheduled_at) do
ScheduledActivity
|> where(user_id: ^user_id)
|> where([s], type(s.scheduled_at, :date) == type(^scheduled_at, :date))
|> select([u], count(u.id))
|> where([sa], type(sa.scheduled_at, :date) == type(^scheduled_at, :date))
|> select([sa], count(sa.id))
|> Repo.one()
|> Kernel.>=(Config.get([ScheduledActivity, :daily_user_limit]))
end
@ -88,7 +85,7 @@ defmodule Pleroma.ScheduledActivity do
def exceeds_total_user_limit?(user_id) do
ScheduledActivity
|> where(user_id: ^user_id)
|> select([u], count(u.id))
|> select([sa], count(sa.id))
|> Repo.one()
|> Kernel.>=(Config.get([ScheduledActivity, :total_user_limit]))
end
@ -125,19 +122,40 @@ defmodule Pleroma.ScheduledActivity do
|> Repo.one()
end
def update(scheduled_activity, attrs) do
def update(%ScheduledActivity{} = scheduled_activity, attrs) do
scheduled_activity
|> update_changeset(attrs)
|> Repo.update()
end
def delete(scheduled_activity) do
def delete(%ScheduledActivity{} = scheduled_activity) do
scheduled_activity
|> Repo.delete()
end
def delete(id) when is_binary(id) or is_integer(id) do
ScheduledActivity
|> where(id: ^id)
|> select([sa], sa)
|> Repo.delete_all()
|> case do
{1, [scheduled_activity]} -> {:ok, scheduled_activity}
_ -> :error
end
end
def for_user_query(%User{} = user) do
ScheduledActivity
|> where(user_id: ^user.id)
end
def due_activities(offset \\ 0) do
naive_datetime =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(offset, :millisecond)
ScheduledActivity
|> where([sa], sa.scheduled_at < ^naive_datetime)
|> Repo.all()
end
end

View file

@ -0,0 +1,58 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# 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 ->
PleromaJobQueue.enqueue(:scheduled_activities, __MODULE__, [:execute, scheduled_activity.id])
end)
schedule_next()
{:noreply, state}
end
defp schedule_next do
Process.send_after(self(), :perform, @schedule_interval)
end
end

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Filter
@ -438,14 +439,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
scheduled_at = params["scheduled_at"]
if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
{:ok, scheduled_activity} =
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at})
end)
conn
|> put_view(ScheduledActivityView)
|> render("show.json", %{scheduled_activity: scheduled_activity})
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
params = Map.drop(params, ["scheduled_at"])
@ -1474,6 +1473,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
# fallback action
#
def errors(conn, {:error, %Changeset{} = changeset}) do
error_message =
changeset
|> Changeset.traverse_errors(fn {message, _opt} -> message end)
|> Enum.map_join(", ", fn {_k, v} -> v end)
conn
|> put_status(422)
|> json(%{error: error_message})
end
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)

View file

@ -16,16 +16,20 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do
%{
id: scheduled_activity.id |> to_string,
scheduled_at: scheduled_activity.scheduled_at |> CommonAPI.Utils.to_masto_date(),
id: to_string(scheduled_activity.id),
scheduled_at: CommonAPI.Utils.to_masto_date(scheduled_activity.scheduled_at),
params: status_params(scheduled_activity.params)
}
|> with_media_attachments(scheduled_activity)
end
defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do
attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment)
Map.put(data, :media_attachments, attachments)
try do
attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment)
Map.put(data, :media_attachments, attachments)
rescue
_ -> data
end
end
defp with_media_attachments(data, _), do: data