fix for scheduled post with poll

This commit is contained in:
Alexander Strizhakov 2021-02-01 18:22:26 +03:00
parent 08a2cb750d
commit 0dc68c157f
No known key found for this signature in database
GPG key ID: 022896A53AEF1381
6 changed files with 119 additions and 61 deletions

View file

@ -65,6 +65,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Fixed last_status.account being not filled with account data. - Mastodon API: Fixed last_status.account being not filled with account data.
- Mastodon API: Fix not being able to add or remove multiple users at once in lists. - Mastodon API: Fix not being able to add or remove multiple users at once in lists.
- Mastodon API: Fixed own_votes being not returned with poll data. - Mastodon API: Fixed own_votes being not returned with poll data.
- Mastodon API: Fixed creation of scheduled posts with polls.
</details> </details>
## Unreleased (Patch) ## Unreleased (Patch)

View file

@ -413,34 +413,7 @@ defp create_request do
items: %Schema{type: :string}, items: %Schema{type: :string},
description: "Array of Attachment ids to be attached as media." description: "Array of Attachment ids to be attached as media."
}, },
poll: %Schema{ poll: poll_params(),
nullable: true,
type: :object,
required: [:options],
properties: %{
options: %Schema{
type: :array,
items: %Schema{type: :string},
description: "Array of possible answers. Must be provided with `poll[expires_in]`."
},
expires_in: %Schema{
type: :integer,
nullable: true,
description:
"Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
},
multiple: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Allow multiple choices?"
},
hide_totals: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Hide vote counts until the poll ends?"
}
}
},
in_reply_to_id: %Schema{ in_reply_to_id: %Schema{
nullable: true, nullable: true,
allOf: [FlakeID], allOf: [FlakeID],
@ -522,6 +495,37 @@ defp create_request do
} }
end end
def poll_params do
%Schema{
nullable: true,
type: :object,
required: [:options, :expires_in],
properties: %{
options: %Schema{
type: :array,
items: %Schema{type: :string},
description: "Array of possible answers. Must be provided with `poll[expires_in]`."
},
expires_in: %Schema{
type: :integer,
nullable: true,
description:
"Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
},
multiple: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Allow multiple choices?"
},
hide_totals: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Hide vote counts until the poll ends?"
}
}
}
end
def id_param do def id_param do
Operation.parameter(:id, :path, FlakeID, "Status ID", Operation.parameter(:id, :path, FlakeID, "Status ID",
example: "9umDrYheeY451cQnEe", example: "9umDrYheeY451cQnEe",

View file

@ -5,8 +5,8 @@
defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do
alias OpenApiSpex.Schema alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Attachment alias Pleroma.Web.ApiSpec.Schemas.Attachment
alias Pleroma.Web.ApiSpec.Schemas.Poll
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
alias Pleroma.Web.ApiSpec.StatusOperation
require OpenApiSpex require OpenApiSpex
@ -29,7 +29,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do
spoiler_text: %Schema{type: :string, nullable: true}, spoiler_text: %Schema{type: :string, nullable: true},
visibility: %Schema{allOf: [VisibilityScope], nullable: true}, visibility: %Schema{allOf: [VisibilityScope], nullable: true},
scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true}, scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true},
poll: %Schema{allOf: [Poll], nullable: true}, poll: StatusOperation.poll_params(),
in_reply_to_id: %Schema{type: :string, nullable: true} in_reply_to_id: %Schema{type: :string, nullable: true}
} }
} }

View file

@ -9,38 +9,50 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do
use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities" use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities"
alias Pleroma.Config alias Pleroma.Repo
alias Pleroma.ScheduledActivity alias Pleroma.ScheduledActivity
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI
require Logger require Logger
@impl Oban.Worker @impl Oban.Worker
def perform(%Job{args: %{"activity_id" => activity_id}}) do def perform(%Job{args: %{"activity_id" => activity_id}}) do
if Config.get([ScheduledActivity, :enabled]) do with %ScheduledActivity{} = scheduled_activity <- find_scheduled_activity(activity_id),
case Pleroma.Repo.get(ScheduledActivity, activity_id) do %User{} = user <- find_user(scheduled_activity.user_id) do
%ScheduledActivity{} = scheduled_activity -> params = atomize_keys(scheduled_activity.params)
post_activity(scheduled_activity)
_ -> Repo.transaction(fn ->
Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}") {:ok, activity} = Pleroma.Web.CommonAPI.post(user, params)
end {:ok, _} = ScheduledActivity.delete(scheduled_activity)
end activity
end end)
defp post_activity(%ScheduledActivity{user_id: user_id, params: params} = scheduled_activity) do
params = Map.new(params, fn {key, value} -> {String.to_existing_atom(key), value} end)
with {:delete, {:ok, _}} <- {:delete, ScheduledActivity.delete(scheduled_activity)},
{:user, %User{} = user} <- {:user, User.get_cached_by_id(user_id)},
{:post, {:ok, _}} <- {:post, CommonAPI.post(user, params)} do
:ok
else else
error -> {:error, :scheduled_activity_not_found} = error ->
Logger.error( Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}")
"#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}" error
)
{:error, :user_not_found} = error ->
Logger.error("#{__MODULE__} Couldn't find user for scheduled activity: #{activity_id}")
error
end end
end end
defp find_scheduled_activity(id) do
with nil <- Repo.get(ScheduledActivity, id) do
{:error, :scheduled_activity_not_found}
end
end
defp find_user(id) do
with nil <- User.get_cached_by_id(id) do
{:error, :user_not_found}
end
end
defp atomize_keys(map) do
Map.new(map, fn
{key, value} when is_map(value) -> {String.to_existing_atom(key), atomize_keys(value)}
{key, value} -> {String.to_existing_atom(key), value}
end)
end
end end

View file

@ -515,7 +515,7 @@ test "posting a poll", %{conn: conn} do
end) end)
assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
refute response["poll"]["expred"] assert response["poll"]["expired"] == false
question = Object.get_by_id(response["poll"]["id"]) question = Object.get_by_id(response["poll"]["id"])
@ -591,6 +591,44 @@ test "maximum date limit is enforced", %{conn: conn} do
%{"error" => error} = json_response_and_validate_schema(conn, 422) %{"error" => error} = json_response_and_validate_schema(conn, 422)
assert error == "Expiration date is too far in the future" assert error == "Expiration date is too far in the future"
end end
test "scheduled poll", %{conn: conn} do
clear_config([ScheduledActivity, :enabled], true)
scheduled_at =
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
|> Kernel.<>("Z")
%{"id" => scheduled_id} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/statuses", %{
"status" => "very cool poll",
"poll" => %{
"options" => ~w(a b c),
"expires_in" => 420
},
"scheduled_at" => scheduled_at
})
|> json_response_and_validate_schema(200)
assert {:ok, %{id: activity_id}} =
perform_job(Pleroma.Workers.ScheduledActivityWorker, %{
activity_id: scheduled_id
})
assert Repo.all(Oban.Job) == []
object =
Activity
|> Repo.get(activity_id)
|> Object.normalize()
assert object.data["content"] == "very cool poll"
assert object.data["type"] == "Question"
assert length(object.data["oneOf"]) == 3
end
end end
test "get a status" do test "get a status" do

View file

@ -11,10 +11,9 @@ defmodule Pleroma.Workers.ScheduledActivityWorkerTest do
import Pleroma.Factory import Pleroma.Factory
import ExUnit.CaptureLog import ExUnit.CaptureLog
setup do: clear_config([ScheduledActivity, :enabled]) setup do: clear_config([ScheduledActivity, :enabled], true)
test "creates a status from the scheduled activity" do test "creates a status from the scheduled activity" do
clear_config([ScheduledActivity, :enabled], true)
user = insert(:user) user = insert(:user)
naive_datetime = naive_datetime =
@ -32,18 +31,22 @@ test "creates a status from the scheduled activity" do
params: %{status: "hi"} params: %{status: "hi"}
) )
{:ok, %{id: activity_id}} =
ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => scheduled_activity.id}}) ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => scheduled_activity.id}})
refute Repo.get(ScheduledActivity, scheduled_activity.id) refute Repo.get(ScheduledActivity, scheduled_activity.id)
activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id))
assert Pleroma.Object.normalize(activity, fetch: false).data["content"] == "hi" object =
Pleroma.Activity
|> Repo.get(activity_id)
|> Pleroma.Object.normalize()
assert object.data["content"] == "hi"
end end
test "adds log message if ScheduledActivity isn't find" do test "error message for non-existent scheduled activity" do
clear_config([ScheduledActivity, :enabled], true)
assert capture_log([level: :error], fn -> assert capture_log([level: :error], fn ->
ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => 42}}) ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => 42}})
end) =~ "Couldn't find scheduled activity" end) =~ "Couldn't find scheduled activity: 42"
end end
end end