Merge branch 'scheduled-activities' into 'develop'
Scheduled activities See merge request pleroma/pleroma!989
This commit is contained in:
commit
b177e1e7f3
|
@ -361,7 +361,8 @@
|
||||||
config :pleroma_job_queue, :queues,
|
config :pleroma_job_queue, :queues,
|
||||||
federator_incoming: 50,
|
federator_incoming: 50,
|
||||||
federator_outgoing: 50,
|
federator_outgoing: 50,
|
||||||
mailer: 10
|
mailer: 10,
|
||||||
|
scheduled_activities: 10
|
||||||
|
|
||||||
config :pleroma, :fetch_initial_posts,
|
config :pleroma, :fetch_initial_posts,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
@ -392,6 +393,11 @@
|
||||||
|
|
||||||
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
|
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.ScheduledActivity,
|
||||||
|
daily_user_limit: 25,
|
||||||
|
total_user_limit: 300,
|
||||||
|
enabled: true
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -50,6 +50,11 @@
|
||||||
|
|
||||||
config :pleroma_job_queue, disabled: true
|
config :pleroma_job_queue, disabled: true
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.ScheduledActivity,
|
||||||
|
daily_user_limit: 2,
|
||||||
|
total_user_limit: 3,
|
||||||
|
enabled: false
|
||||||
|
|
||||||
try do
|
try do
|
||||||
import_config "test.secret.exs"
|
import_config "test.secret.exs"
|
||||||
rescue
|
rescue
|
||||||
|
|
|
@ -218,14 +218,14 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
|
||||||
- `port`
|
- `port`
|
||||||
* `url` - a list containing the configuration for generating urls, accepts
|
* `url` - a list containing the configuration for generating urls, accepts
|
||||||
- `host` - the host without the scheme and a post (e.g `example.com`, not `https://example.com:2020`)
|
- `host` - the host without the scheme and a post (e.g `example.com`, not `https://example.com:2020`)
|
||||||
- `scheme` - e.g `http`, `https`
|
- `scheme` - e.g `http`, `https`
|
||||||
- `port`
|
- `port`
|
||||||
- `path`
|
- `path`
|
||||||
|
|
||||||
|
|
||||||
**Important note**: if you modify anything inside these lists, default `config.exs` values will be overwritten, which may result in breakage, to make sure this does not happen please copy the default value for the list from `config.exs` and modify/add only what you need
|
**Important note**: if you modify anything inside these lists, default `config.exs` values will be overwritten, which may result in breakage, to make sure this does not happen please copy the default value for the list from `config.exs` and modify/add only what you need
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```elixir
|
```elixir
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
url: [host: "example.com", port: 2020, scheme: "https"],
|
url: [host: "example.com", port: 2020, scheme: "https"],
|
||||||
|
@ -317,6 +317,7 @@ Pleroma has the following queues:
|
||||||
* `federator_outgoing` - Outgoing federation
|
* `federator_outgoing` - Outgoing federation
|
||||||
* `federator_incoming` - Incoming federation
|
* `federator_incoming` - Incoming federation
|
||||||
* `mailer` - Email sender, see [`Pleroma.Mailer`](#pleroma-mailer)
|
* `mailer` - Email sender, see [`Pleroma.Mailer`](#pleroma-mailer)
|
||||||
|
* `scheduled_activities` - Scheduled activities, see [`Pleroma.ScheduledActivities`](#pleromascheduledactivity)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -412,3 +413,9 @@ Pleroma account will be created with the same name as the LDAP user name.
|
||||||
|
|
||||||
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
|
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
|
||||||
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
|
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
|
||||||
|
|
||||||
|
## Pleroma.ScheduledActivity
|
||||||
|
|
||||||
|
* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)
|
||||||
|
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
|
||||||
|
* `enabled`: whether scheduled activities are sent to the job queue to be executed
|
||||||
|
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Activity do
|
||||||
field(:data, :map)
|
field(:data, :map)
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
field(:actor, :string)
|
field(:actor, :string)
|
||||||
field(:recipients, {:array, :string})
|
field(:recipients, {:array, :string}, default: [])
|
||||||
has_many(:notifications, Notification, on_delete: :delete_all)
|
has_many(:notifications, Notification, on_delete: :delete_all)
|
||||||
|
|
||||||
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
||||||
|
|
|
@ -104,7 +104,8 @@ def start(_type, _args) do
|
||||||
],
|
],
|
||||||
id: :cachex_idem
|
id: :cachex_idem
|
||||||
),
|
),
|
||||||
worker(Pleroma.FlakeId, [])
|
worker(Pleroma.FlakeId, []),
|
||||||
|
worker(Pleroma.ScheduledActivityWorker, [])
|
||||||
] ++
|
] ++
|
||||||
hackney_pool_children() ++
|
hackney_pool_children() ++
|
||||||
[
|
[
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivity do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@min_offset :timer.minutes(5)
|
||||||
|
|
||||||
|
schema "scheduled_activities" do
|
||||||
|
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||||
|
field(:scheduled_at, :naive_datetime)
|
||||||
|
field(:params, :map)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||||
|
scheduled_activity
|
||||||
|
|> cast(attrs, [:scheduled_at, :params])
|
||||||
|
|> validate_required([:scheduled_at, :params])
|
||||||
|
|> validate_scheduled_at()
|
||||||
|
|> with_media_attachments()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(
|
||||||
|
%{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
|
||||||
|
)
|
||||||
|
when is_list(media_ids) do
|
||||||
|
media_attachments = Utils.attachments_from_ids(%{"media_ids" => media_ids})
|
||||||
|
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("media_attachments", media_attachments)
|
||||||
|
|> Map.put("media_ids", media_ids)
|
||||||
|
|
||||||
|
put_change(changeset, :params, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(changeset), do: changeset
|
||||||
|
|
||||||
|
def update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||||
|
scheduled_activity
|
||||||
|
|> cast(attrs, [:scheduled_at])
|
||||||
|
|> validate_required([:scheduled_at])
|
||||||
|
|> validate_scheduled_at()
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_scheduled_at(changeset) do
|
||||||
|
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
|
||||||
|
cond do
|
||||||
|
not far_enough?(scheduled_at) ->
|
||||||
|
[scheduled_at: "must be at least 5 minutes from now"]
|
||||||
|
|
||||||
|
exceeds_daily_user_limit?(changeset.data.user_id, scheduled_at) ->
|
||||||
|
[scheduled_at: "daily limit exceeded"]
|
||||||
|
|
||||||
|
exceeds_total_user_limit?(changeset.data.user_id) ->
|
||||||
|
[scheduled_at: "total limit exceeded"]
|
||||||
|
|
||||||
|
true ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def exceeds_daily_user_limit?(user_id, scheduled_at) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user_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
|
||||||
|
|
||||||
|
def exceeds_total_user_limit?(user_id) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user_id)
|
||||||
|
|> select([sa], count(sa.id))
|
||||||
|
|> Repo.one()
|
||||||
|
|> Kernel.>=(Config.get([ScheduledActivity, :total_user_limit]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def far_enough?(scheduled_at) when is_binary(scheduled_at) do
|
||||||
|
with {:ok, scheduled_at} <- Ecto.Type.cast(:naive_datetime, scheduled_at) do
|
||||||
|
far_enough?(scheduled_at)
|
||||||
|
else
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def far_enough?(scheduled_at) do
|
||||||
|
now = NaiveDateTime.utc_now()
|
||||||
|
diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
|
||||||
|
diff > @min_offset
|
||||||
|
end
|
||||||
|
|
||||||
|
def new(%User{} = user, attrs) do
|
||||||
|
%ScheduledActivity{user_id: user.id}
|
||||||
|
|> changeset(attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(%User{} = user, attrs) do
|
||||||
|
user
|
||||||
|
|> new(attrs)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(%User{} = user, scheduled_activity_id) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user.id)
|
||||||
|
|> where(id: ^scheduled_activity_id)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||||
|
scheduled_activity
|
||||||
|
|> update_changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
|
@ -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
|
|
@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def get_followers(user, params \\ %{}) do
|
def get_followers(user, params \\ %{}) do
|
||||||
|
@ -28,6 +29,12 @@ def get_notifications(user, params \\ %{}) do
|
||||||
|> Pagination.fetch_paginated(params)
|
|> Pagination.fetch_paginated(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_scheduled_activities(user, params \\ %{}) do
|
||||||
|
user
|
||||||
|
|> ScheduledActivity.for_user_query()
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
end
|
||||||
|
|
||||||
defp cast_params(params) do
|
defp cast_params(params) do
|
||||||
param_types = %{
|
param_types = %{
|
||||||
exclude_types: {:array, :string}
|
exclude_types: {:array, :string}
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Ecto.Changeset
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Filter
|
alias Pleroma.Filter
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.Stats
|
alias Pleroma.Stats
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
|
@ -25,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Web.MastodonAPI.MastodonView
|
alias Pleroma.Web.MastodonAPI.MastodonView
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||||
alias Pleroma.Web.MastodonAPI.ReportView
|
alias Pleroma.Web.MastodonAPI.ReportView
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
alias Pleroma.Web.OAuth.App
|
alias Pleroma.Web.OAuth.App
|
||||||
|
@ -364,6 +367,55 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
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_statuses, 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 post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
|
def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
|
||||||
when length(media_ids) > 0 do
|
when length(media_ids) > 0 do
|
||||||
params =
|
params =
|
||||||
|
@ -384,12 +436,27 @@ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||||
_ -> Ecto.UUID.generate()
|
_ -> Ecto.UUID.generate()
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, activity} =
|
scheduled_at = params["scheduled_at"]
|
||||||
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
|
|
||||||
|
|
||||||
conn
|
if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
|
||||||
|> put_view(StatusView)
|
with {:ok, scheduled_activity} <-
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :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"])
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
|
||||||
|
CommonAPI.post(user, params)
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
@ -1406,6 +1473,23 @@ def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
||||||
|
|
||||||
# fallback action
|
# 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)
|
||||||
|
|> json(%{error: "Record not found"})
|
||||||
|
end
|
||||||
|
|
||||||
def errors(conn, _) do
|
def errors(conn, _) do
|
||||||
conn
|
conn
|
||||||
|> put_status(500)
|
|> put_status(500)
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
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")
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do
|
||||||
|
%{
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
defp status_params(params) do
|
||||||
|
data = %{
|
||||||
|
text: params["status"],
|
||||||
|
sensitive: params["sensitive"],
|
||||||
|
spoiler_text: params["spoiler_text"],
|
||||||
|
visibility: params["visibility"],
|
||||||
|
scheduled_at: params["scheduled_at"],
|
||||||
|
poll: params["poll"],
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
|
@ -244,6 +244,9 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/notifications", MastodonAPIController, :notifications)
|
get("/notifications", MastodonAPIController, :notifications)
|
||||||
get("/notifications/:id", MastodonAPIController, :get_notification)
|
get("/notifications/:id", MastodonAPIController, :get_notification)
|
||||||
|
|
||||||
|
get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
|
||||||
|
get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
|
||||||
|
|
||||||
get("/lists", MastodonAPIController, :get_lists)
|
get("/lists", MastodonAPIController, :get_lists)
|
||||||
get("/lists/:id", MastodonAPIController, :get_list)
|
get("/lists/:id", MastodonAPIController, :get_list)
|
||||||
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
|
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
|
||||||
|
@ -278,6 +281,9 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
|
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
|
||||||
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
|
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
|
||||||
|
|
||||||
|
put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
|
||||||
|
delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
|
||||||
|
|
||||||
post("/media", MastodonAPIController, :upload)
|
post("/media", MastodonAPIController, :upload)
|
||||||
put("/media/:id", MastodonAPIController, :update_media)
|
put("/media/:id", MastodonAPIController, :update_media)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateScheduledActivities do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:scheduled_activities) do
|
||||||
|
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||||
|
add(:scheduled_at, :naive_datetime, null: false)
|
||||||
|
add(:params, :map, null: false)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create(index(:scheduled_activities, [:scheduled_at]))
|
||||||
|
create(index(:scheduled_activities, [:user_id]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivityTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.DataCase
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
setup context do
|
||||||
|
DataCase.ensure_local_uploader(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "creation" do
|
||||||
|
test "when daily user limit is exceeded" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: today}
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:error, changeset} = ScheduledActivity.create(user, attrs)
|
||||||
|
assert changeset.errors == [scheduled_at: {"daily limit exceeded", []}]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when total user limit is exceeded" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
tomorrow =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||||
|
{:error, changeset} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||||
|
assert changeset.errors == [scheduled_at: {"total limit exceeded", []}]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when scheduled_at is earlier than 5 minute from now" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(4), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: scheduled_at}
|
||||||
|
{:error, changeset} = ScheduledActivity.create(user, attrs)
|
||||||
|
assert changeset.errors == [scheduled_at: {"must be at least 5 minutes from now", []}]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivityWorkerTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "creates a status from the scheduled activity" do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user, params: %{status: "hi"})
|
||||||
|
Pleroma.ScheduledActivityWorker.perform(:execute, scheduled_activity.id)
|
||||||
|
|
||||||
|
refute Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||||
|
activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id))
|
||||||
|
assert activity.data["object"]["content"] == "hi"
|
||||||
|
end
|
||||||
|
end
|
|
@ -267,4 +267,12 @@ def notification_factory do
|
||||||
user: build(:user)
|
user: build(:user)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def scheduled_activity_factory do
|
||||||
|
%Pleroma.ScheduledActivity{
|
||||||
|
user: build(:user),
|
||||||
|
scheduled_at: NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(60), :millisecond),
|
||||||
|
params: build(:note) |> Map.from_struct() |> Map.get(:data)
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
@ -2407,4 +2408,214 @@ test "redirects to the getting-started page when referer is not present", %{conn
|
||||||
assert redirected_to(conn) == "/web/getting-started"
|
assert redirected_to(conn) == "/web/getting-started"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "scheduled activities" do
|
||||||
|
test "creates a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "scheduled",
|
||||||
|
"scheduled_at" => scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
|
||||||
|
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
|
||||||
|
assert [] == Repo.all(Activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "creates a scheduled activity with a media attachment", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"media_ids" => [to_string(upload.id)],
|
||||||
|
"status" => "scheduled",
|
||||||
|
"scheduled_at" => scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
|
||||||
|
assert %{"type" => "image"} = media_attachment
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
|
||||||
|
%{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "not scheduled",
|
||||||
|
"scheduled_at" => scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"content" => "not scheduled"} = json_response(conn, 200)
|
||||||
|
assert [] == Repo.all(ScheduledActivity)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when daily user limit is exceeded", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: today}
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
|
||||||
|
|
||||||
|
assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when total user limit is exceeded", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
tomorrow =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: today}
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
|
||||||
|
|
||||||
|
assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows scheduled activities", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|
||||||
|
# min_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
|
||||||
|
# since_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
|
||||||
|
|
||||||
|
# max_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
|
||||||
|
assert scheduled_activity_id == scheduled_activity.id |> to_string()
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/404")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
new_scheduled_at =
|
||||||
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
|
||||||
|
scheduled_at: new_scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
|
||||||
|
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deletes a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{} = json_response(res_conn, 200)
|
||||||
|
assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "A scheduled activity with a media attachment" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hi"})
|
||||||
|
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(10), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
attrs = %{
|
||||||
|
params: %{
|
||||||
|
"media_ids" => [upload.id],
|
||||||
|
"status" => "hi",
|
||||||
|
"sensitive" => true,
|
||||||
|
"spoiler_text" => "spoiler",
|
||||||
|
"visibility" => "unlisted",
|
||||||
|
"in_reply_to_id" => to_string(activity.id)
|
||||||
|
},
|
||||||
|
scheduled_at: scheduled_at
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, scheduled_activity} = ScheduledActivity.create(user, attrs)
|
||||||
|
result = ScheduledActivityView.render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
|
||||||
|
expected = %{
|
||||||
|
id: to_string(scheduled_activity.id),
|
||||||
|
media_attachments:
|
||||||
|
%{"media_ids" => [upload.id]}
|
||||||
|
|> Utils.attachments_from_ids()
|
||||||
|
|> Enum.map(&StatusView.render("attachment.json", %{attachment: &1})),
|
||||||
|
params: %{
|
||||||
|
in_reply_to_id: to_string(activity.id),
|
||||||
|
media_ids: [upload.id],
|
||||||
|
poll: nil,
|
||||||
|
scheduled_at: nil,
|
||||||
|
sensitive: true,
|
||||||
|
spoiler_text: "spoiler",
|
||||||
|
text: "hi",
|
||||||
|
visibility: "unlisted"
|
||||||
|
},
|
||||||
|
scheduled_at: Utils.to_masto_date(scheduled_activity.scheduled_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert expected == result
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue