Merge branch 'develop' into issue/1276-2
This commit is contained in:
commit
fb38b7339c
|
@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
||||||
- Mastodon API: Add support for filtering replies in public and home timelines
|
- Mastodon API: Add support for filtering replies in public and home timelines
|
||||||
- Admin API: endpoints for create/update/delete OAuth Apps.
|
- Admin API: endpoints for create/update/delete OAuth Apps.
|
||||||
|
- Admin API: endpoint for status view.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -653,6 +653,8 @@
|
||||||
profiles: %{local: false, remote: false},
|
profiles: %{local: false, remote: false},
|
||||||
activities: %{local: false, remote: false}
|
activities: %{local: false, remote: false}
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
|
||||||
|
|
||||||
# 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"
|
||||||
|
|
|
@ -2247,6 +2247,7 @@
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :active,
|
key: :active,
|
||||||
|
label: "Enabled",
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Globally enable or disable digest emails"
|
description: "Globally enable or disable digest emails"
|
||||||
},
|
},
|
||||||
|
@ -3194,5 +3195,19 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: Pleroma.Web.ApiSpec.CastAndValidate,
|
||||||
|
type: :group,
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :strict,
|
||||||
|
type: :boolean,
|
||||||
|
description:
|
||||||
|
"Enables strict input validation (useful in development, not recommended in production)",
|
||||||
|
suggestions: [false]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -52,6 +52,8 @@
|
||||||
hostname: "localhost",
|
hostname: "localhost",
|
||||||
pool_size: 10
|
pool_size: 10
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
|
||||||
|
|
||||||
if File.exists?("./config/dev.secret.exs") do
|
if File.exists?("./config/dev.secret.exs") do
|
||||||
import_config "dev.secret.exs"
|
import_config "dev.secret.exs"
|
||||||
else
|
else
|
||||||
|
|
|
@ -96,6 +96,8 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
|
config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
|
||||||
|
|
||||||
if File.exists?("./config/test.secret.exs") do
|
if File.exists?("./config/test.secret.exs") do
|
||||||
import_config "test.secret.exs"
|
import_config "test.secret.exs"
|
||||||
else
|
else
|
||||||
|
|
|
@ -755,6 +755,17 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- 400 Bad Request `"Invalid parameters"` when `status` is missing
|
- 400 Bad Request `"Invalid parameters"` when `status` is missing
|
||||||
- On success: `204`, empty response
|
- On success: `204`, empty response
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/statuses/:id`
|
||||||
|
|
||||||
|
### Show status by id
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `id`: required, status id
|
||||||
|
- Response:
|
||||||
|
- On failure:
|
||||||
|
- 404 Not Found `"Not Found"`
|
||||||
|
- On success: JSON, Mastodon Status entity
|
||||||
|
|
||||||
## `PUT /api/pleroma/admin/statuses/:id`
|
## `PUT /api/pleroma/admin/statuses/:id`
|
||||||
|
|
||||||
### Change the scope of an individual reported status
|
### Change the scope of an individual reported status
|
||||||
|
|
|
@ -925,3 +925,7 @@ Restrict access for unauthenticated users to timelines (public and federate), us
|
||||||
* `activities` - statuses
|
* `activities` - statuses
|
||||||
* `local`
|
* `local`
|
||||||
* `remote`
|
* `remote`
|
||||||
|
|
||||||
|
## Pleroma.Web.ApiSpec.CastAndValidate
|
||||||
|
|
||||||
|
* `:strict` a boolean, enables strict input validation (useful in development, not recommended in production). Defaults to `false`.
|
||||||
|
|
|
@ -113,7 +113,6 @@ defmodule Pleroma.User do
|
||||||
field(:is_admin, :boolean, default: false)
|
field(:is_admin, :boolean, default: false)
|
||||||
field(:show_role, :boolean, default: true)
|
field(:show_role, :boolean, default: true)
|
||||||
field(:settings, :map, default: nil)
|
field(:settings, :map, default: nil)
|
||||||
field(:magic_key, :string, default: nil)
|
|
||||||
field(:uri, Types.Uri, default: nil)
|
field(:uri, Types.Uri, default: nil)
|
||||||
field(:hide_followers_count, :boolean, default: false)
|
field(:hide_followers_count, :boolean, default: false)
|
||||||
field(:hide_follows_count, :boolean, default: false)
|
field(:hide_follows_count, :boolean, default: false)
|
||||||
|
@ -387,7 +386,6 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
:banner,
|
:banner,
|
||||||
:locked,
|
:locked,
|
||||||
:last_refreshed_at,
|
:last_refreshed_at,
|
||||||
:magic_key,
|
|
||||||
:uri,
|
:uri,
|
||||||
:follower_address,
|
:follower_address,
|
||||||
:following_address,
|
:following_address,
|
||||||
|
|
|
@ -1530,21 +1530,34 @@ def fetch_follow_information_for_user(user) do
|
||||||
defp normalize_counter(counter) when is_integer(counter), do: counter
|
defp normalize_counter(counter) when is_integer(counter), do: counter
|
||||||
defp normalize_counter(_), do: 0
|
defp normalize_counter(_), do: 0
|
||||||
|
|
||||||
defp maybe_update_follow_information(data) do
|
def maybe_update_follow_information(user_data) do
|
||||||
with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])},
|
with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])},
|
||||||
{:ok, info} <- fetch_follow_information_for_user(data) do
|
{_, true} <- {:user_type_check, user_data[:type] in ["Person", "Service"]},
|
||||||
info = Map.merge(data[:info] || %{}, info)
|
{_, true} <-
|
||||||
Map.put(data, :info, info)
|
{:collections_available,
|
||||||
|
!!(user_data[:following_address] && user_data[:follower_address])},
|
||||||
|
{:ok, info} <-
|
||||||
|
fetch_follow_information_for_user(user_data) do
|
||||||
|
info = Map.merge(user_data[:info] || %{}, info)
|
||||||
|
|
||||||
|
user_data
|
||||||
|
|> Map.put(:info, info)
|
||||||
else
|
else
|
||||||
|
{:user_type_check, false} ->
|
||||||
|
user_data
|
||||||
|
|
||||||
|
{:collections_available, false} ->
|
||||||
|
user_data
|
||||||
|
|
||||||
{:enabled, false} ->
|
{:enabled, false} ->
|
||||||
data
|
user_data
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.error(
|
Logger.error(
|
||||||
"Follower/Following counter update for #{data.ap_id} failed.\n" <> inspect(e)
|
"Follower/Following counter update for #{user_data.ap_id} failed.\n" <> inspect(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
data
|
user_data
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
|
@ -19,8 +20,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||||
field(:object, Types.ObjectID)
|
field(:object, Types.ObjectID)
|
||||||
field(:actor, Types.ObjectID)
|
field(:actor, Types.ObjectID)
|
||||||
field(:context, :string)
|
field(:context, :string)
|
||||||
field(:to, {:array, :string})
|
field(:to, {:array, :string}, default: [])
|
||||||
field(:cc, {:array, :string})
|
field(:cc, {:array, :string}, default: [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data) do
|
||||||
|
@ -31,7 +32,48 @@ def cast_and_validate(data) do
|
||||||
|
|
||||||
def cast_data(data) do
|
def cast_data(data) do
|
||||||
%__MODULE__{}
|
%__MODULE__{}
|
||||||
|> cast(data, [:id, :type, :object, :actor, :context, :to, :cc])
|
|> changeset(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
|> fix_after_cast()
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_after_cast(cng) do
|
||||||
|
cng
|
||||||
|
|> fix_recipients()
|
||||||
|
|> fix_context()
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_context(cng) do
|
||||||
|
object = get_field(cng, :object)
|
||||||
|
|
||||||
|
with nil <- get_field(cng, :context),
|
||||||
|
%Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do
|
||||||
|
cng
|
||||||
|
|> put_change(:context, context)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
cng
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_recipients(cng) do
|
||||||
|
to = get_field(cng, :to)
|
||||||
|
cc = get_field(cng, :cc)
|
||||||
|
object = get_field(cng, :object)
|
||||||
|
|
||||||
|
with {[], []} <- {to, cc},
|
||||||
|
%Object{data: %{"actor" => actor}} <- Object.get_cached_by_ap_id(object),
|
||||||
|
{:ok, actor} <- Types.ObjectID.cast(actor) do
|
||||||
|
cng
|
||||||
|
|> put_change(:to, [actor])
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
cng
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_data(data_cng) do
|
def validate_data(data_cng) do
|
||||||
|
|
|
@ -4,20 +4,33 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.Pipeline do
|
defmodule Pleroma.Web.ActivityPub.Pipeline do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.MRF
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
alias Pleroma.Web.ActivityPub.SideEffects
|
alias Pleroma.Web.ActivityPub.SideEffects
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
|
|
||||||
@spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()}
|
@spec common_pipeline(map(), keyword()) ::
|
||||||
|
{:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
|
||||||
def common_pipeline(object, meta) do
|
def common_pipeline(object, meta) do
|
||||||
|
case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
|
||||||
|
{:ok, value} ->
|
||||||
|
value
|
||||||
|
|
||||||
|
{:error, e} ->
|
||||||
|
{:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_common_pipeline(object, meta) do
|
||||||
with {_, {:ok, validated_object, meta}} <-
|
with {_, {:ok, validated_object, meta}} <-
|
||||||
{:validate_object, ObjectValidator.validate(object, meta)},
|
{:validate_object, ObjectValidator.validate(object, meta)},
|
||||||
{_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)},
|
{_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)},
|
||||||
{_, {:ok, %Activity{} = activity, meta}} <-
|
{_, {:ok, activity, meta}} <-
|
||||||
{:persist_object, ActivityPub.persist(mrfd_object, meta)},
|
{:persist_object, ActivityPub.persist(mrfd_object, meta)},
|
||||||
{_, {:ok, %Activity{} = activity, meta}} <-
|
{_, {:ok, activity, meta}} <-
|
||||||
{:execute_side_effects, SideEffects.handle(activity, meta)},
|
{:execute_side_effects, SideEffects.handle(activity, meta)},
|
||||||
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
|
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
|
||||||
{:ok, activity, meta}
|
{:ok, activity, meta}
|
||||||
|
@ -27,7 +40,9 @@ def common_pipeline(object, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_federate(activity, meta) do
|
defp maybe_federate(%Object{}, _), do: {:ok, :not_federated}
|
||||||
|
|
||||||
|
defp maybe_federate(%Activity{} = activity, meta) do
|
||||||
with {:ok, local} <- Keyword.fetch(meta, :local) do
|
with {:ok, local} <- Keyword.fetch(meta, :local) do
|
||||||
if local do
|
if local do
|
||||||
Federator.publish(activity)
|
Federator.publish(activity)
|
||||||
|
|
|
@ -15,17 +15,12 @@ def handle(object, meta \\ [])
|
||||||
# - Add like to object
|
# - Add like to object
|
||||||
# - Set up notification
|
# - Set up notification
|
||||||
def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||||
{:ok, result} =
|
liked_object = Object.get_by_ap_id(object.data["object"])
|
||||||
Pleroma.Repo.transaction(fn ->
|
Utils.add_like_to_object(object, liked_object)
|
||||||
liked_object = Object.get_by_ap_id(object.data["object"])
|
|
||||||
Utils.add_like_to_object(object, liked_object)
|
|
||||||
|
|
||||||
Notification.create_notifications(object)
|
Notification.create_notifications(object)
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end)
|
|
||||||
|
|
||||||
result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
|
|
|
@ -15,7 +15,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
|
||||||
alias Pleroma.Web.ActivityPub.Pipeline
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
@ -658,16 +657,9 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(%{"type" => "Like"} = data, _options) do
|
def handle_incoming(%{"type" => "Like"} = data, _options) do
|
||||||
with {_, {:ok, cast_data_sym}} <-
|
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||||
{:casting_data,
|
{:ok, activity, _meta} <-
|
||||||
data |> LikeValidator.cast_data() |> Ecto.Changeset.apply_action(:insert)},
|
Pipeline.common_pipeline(data, local: false) do
|
||||||
cast_data = ObjectValidator.stringify_keys(Map.from_struct(cast_data_sym)),
|
|
||||||
:ok <- ObjectValidator.fetch_actor_and_object(cast_data),
|
|
||||||
{_, {:ok, cast_data}} <- {:ensure_context_presence, ensure_context_presence(cast_data)},
|
|
||||||
{_, {:ok, cast_data}} <-
|
|
||||||
{:ensure_recipients_presence, ensure_recipients_presence(cast_data)},
|
|
||||||
{_, {:ok, activity, _meta}} <-
|
|
||||||
{:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
e -> {:error, e}
|
e -> {:error, e}
|
||||||
|
@ -1296,45 +1288,4 @@ def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do
|
||||||
def maybe_fix_user_url(data), do: data
|
def maybe_fix_user_url(data), do: data
|
||||||
|
|
||||||
def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
|
def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
|
||||||
|
|
||||||
defp ensure_context_presence(%{"context" => context} = data) when is_binary(context),
|
|
||||||
do: {:ok, data}
|
|
||||||
|
|
||||||
defp ensure_context_presence(%{"object" => object} = data) when is_binary(object) do
|
|
||||||
with %{data: %{"context" => context}} when is_binary(context) <- Object.normalize(object) do
|
|
||||||
{:ok, Map.put(data, "context", context)}
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
{:error, :no_context}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp ensure_context_presence(_) do
|
|
||||||
{:error, :no_context}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp ensure_recipients_presence(%{"to" => [_ | _], "cc" => [_ | _]} = data),
|
|
||||||
do: {:ok, data}
|
|
||||||
|
|
||||||
defp ensure_recipients_presence(%{"object" => object} = data) do
|
|
||||||
case Object.normalize(object) do
|
|
||||||
%{data: %{"actor" => actor}} ->
|
|
||||||
data =
|
|
||||||
data
|
|
||||||
|> Map.put("to", [actor])
|
|
||||||
|> Map.put("cc", data["cc"] || [])
|
|
||||||
|
|
||||||
{:ok, data}
|
|
||||||
|
|
||||||
nil ->
|
|
||||||
{:error, :no_object}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:error, :no_actor}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp ensure_recipients_presence(_) do
|
|
||||||
{:error, :no_object}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -93,7 +93,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read:statuses"], admin: true}
|
%{scopes: ["read:statuses"], admin: true}
|
||||||
when action in [:list_statuses, :list_user_statuses, :list_instance_statuses]
|
when action in [:list_statuses, :list_user_statuses, :list_instance_statuses, :status_show]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -837,6 +837,16 @@ def list_statuses(%{assigns: %{user: _admin}} = conn, params) do
|
||||||
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def status_show(conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(id) do
|
||||||
|
conn
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> render("show.json", %{activity: activity})
|
||||||
|
else
|
||||||
|
_ -> errors(conn, {:error, :not_found})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
|
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
|
||||||
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
|
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
|
||||||
{:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
|
{:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2019-2020 Moxley Stratton, Mike Buhot <https://github.com/open-api-spex/open_api_spex>, MPL-2.0
|
||||||
|
# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.CastAndValidate do
|
||||||
|
@moduledoc """
|
||||||
|
This plug is based on [`OpenApiSpex.Plug.CastAndValidate`]
|
||||||
|
(https://github.com/open-api-spex/open_api_spex/blob/master/lib/open_api_spex/plug/cast_and_validate.ex).
|
||||||
|
The main difference is ignoring unexpected query params instead of throwing
|
||||||
|
an error and a config option (`[Pleroma.Web.ApiSpec.CastAndValidate, :strict]`)
|
||||||
|
to disable this behavior. Also, the default rendering error module
|
||||||
|
is `Pleroma.Web.ApiSpec.RenderError`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@behaviour Plug
|
||||||
|
|
||||||
|
alias Plug.Conn
|
||||||
|
|
||||||
|
@impl Plug
|
||||||
|
def init(opts) do
|
||||||
|
opts
|
||||||
|
|> Map.new()
|
||||||
|
|> Map.put_new(:render_error, Pleroma.Web.ApiSpec.RenderError)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Plug
|
||||||
|
def call(%{private: %{open_api_spex: private_data}} = conn, %{
|
||||||
|
operation_id: operation_id,
|
||||||
|
render_error: render_error
|
||||||
|
}) do
|
||||||
|
spec = private_data.spec
|
||||||
|
operation = private_data.operation_lookup[operation_id]
|
||||||
|
|
||||||
|
content_type =
|
||||||
|
case Conn.get_req_header(conn, "content-type") do
|
||||||
|
[header_value | _] ->
|
||||||
|
header_value
|
||||||
|
|> String.split(";")
|
||||||
|
|> List.first()
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
private_data = Map.put(private_data, :operation_id, operation_id)
|
||||||
|
conn = Conn.put_private(conn, :open_api_spex, private_data)
|
||||||
|
|
||||||
|
case cast_and_validate(spec, operation, conn, content_type, strict?()) do
|
||||||
|
{:ok, conn} ->
|
||||||
|
conn
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
opts = render_error.init(reason)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> render_error.call(opts)
|
||||||
|
|> Plug.Conn.halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(
|
||||||
|
%{
|
||||||
|
private: %{
|
||||||
|
phoenix_controller: controller,
|
||||||
|
phoenix_action: action,
|
||||||
|
open_api_spex: private_data
|
||||||
|
}
|
||||||
|
} = conn,
|
||||||
|
opts
|
||||||
|
) do
|
||||||
|
operation =
|
||||||
|
case private_data.operation_lookup[{controller, action}] do
|
||||||
|
nil ->
|
||||||
|
operation_id = controller.open_api_operation(action).operationId
|
||||||
|
operation = private_data.operation_lookup[operation_id]
|
||||||
|
|
||||||
|
operation_lookup =
|
||||||
|
private_data.operation_lookup
|
||||||
|
|> Map.put({controller, action}, operation)
|
||||||
|
|
||||||
|
OpenApiSpex.Plug.Cache.adapter().put(
|
||||||
|
private_data.spec_module,
|
||||||
|
{private_data.spec, operation_lookup}
|
||||||
|
)
|
||||||
|
|
||||||
|
operation
|
||||||
|
|
||||||
|
operation ->
|
||||||
|
operation
|
||||||
|
end
|
||||||
|
|
||||||
|
if operation.operationId do
|
||||||
|
call(conn, Map.put(opts, :operation_id, operation.operationId))
|
||||||
|
else
|
||||||
|
raise "operationId was not found in action API spec"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, opts), do: OpenApiSpex.Plug.CastAndValidate.call(conn, opts)
|
||||||
|
|
||||||
|
defp cast_and_validate(spec, operation, conn, content_type, true = _strict) do
|
||||||
|
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cast_and_validate(spec, operation, conn, content_type, false = _strict) do
|
||||||
|
case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
|
||||||
|
{:ok, conn} ->
|
||||||
|
{:ok, conn}
|
||||||
|
|
||||||
|
# Remove unexpected query params and cast/validate again
|
||||||
|
{:error, errors} ->
|
||||||
|
query_params =
|
||||||
|
Enum.reduce(errors, conn.query_params, fn
|
||||||
|
%{reason: :unexpected_field, name: name, path: [name]}, params ->
|
||||||
|
Map.delete(params, name)
|
||||||
|
|
||||||
|
%{reason: :invalid_enum, name: nil, path: path, value: value}, params ->
|
||||||
|
path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()
|
||||||
|
update_in(params, path, &List.delete(&1, value))
|
||||||
|
|
||||||
|
_, params ->
|
||||||
|
params
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn = %Conn{conn | query_params: query_params}
|
||||||
|
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp list_items_to_string(list) do
|
||||||
|
Enum.map(list, fn
|
||||||
|
i when is_atom(i) -> to_string(i)
|
||||||
|
i -> i
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strict?, do: Pleroma.Config.get([__MODULE__, :strict], false)
|
||||||
|
end
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Scheduled Statuses"],
|
||||||
|
summary: "View scheduled statuses",
|
||||||
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
|
parameters: pagination_params(),
|
||||||
|
operationId: "ScheduledActivity.index",
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Array of ScheduledStatus", "application/json", %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: ScheduledStatus
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Scheduled Statuses"],
|
||||||
|
summary: "View a single scheduled status",
|
||||||
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
|
parameters: [id_param()],
|
||||||
|
operationId: "ScheduledActivity.show",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus),
|
||||||
|
404 => Operation.response("Error", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Scheduled Statuses"],
|
||||||
|
summary: "Schedule a status",
|
||||||
|
operationId: "ScheduledActivity.update",
|
||||||
|
security: [%{"oAuth" => ["write:statuses"]}],
|
||||||
|
parameters: [id_param()],
|
||||||
|
requestBody:
|
||||||
|
request_body("Parameters", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
scheduled_at: %Schema{
|
||||||
|
type: :string,
|
||||||
|
format: :"date-time",
|
||||||
|
description:
|
||||||
|
"ISO 8601 Datetime at which the status will be published. Must be at least 5 minutes into the future."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus),
|
||||||
|
404 => Operation.response("Error", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Scheduled Statuses"],
|
||||||
|
summary: "Cancel a scheduled status",
|
||||||
|
security: [%{"oAuth" => ["write:statuses"]}],
|
||||||
|
parameters: [id_param()],
|
||||||
|
operationId: "ScheduledActivity.delete",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Empty object", "application/json", %Schema{type: :object}),
|
||||||
|
404 => Operation.response("Error", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp id_param do
|
||||||
|
Operation.parameter(:id, :path, FlakeID, "Poll ID",
|
||||||
|
example: "123",
|
||||||
|
required: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,6 +17,9 @@ def init(opts), do: opts
|
||||||
def call(conn, errors) do
|
def call(conn, errors) do
|
||||||
errors =
|
errors =
|
||||||
Enum.map(errors, fn
|
Enum.map(errors, fn
|
||||||
|
%{name: nil, reason: :invalid_enum} = err ->
|
||||||
|
%OpenApiSpex.Cast.Error{err | name: err.value}
|
||||||
|
|
||||||
%{name: nil} = err ->
|
%{name: nil} = err ->
|
||||||
%OpenApiSpex.Cast.Error{err | name: List.last(err.path)}
|
%OpenApiSpex.Cast.Error{err | name: List.last(err.path)}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.Schemas.Attachment do
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "Attachment",
|
||||||
|
description: "Represents a file or media attachment that can be added to a status.",
|
||||||
|
type: :object,
|
||||||
|
requried: [:id, :url, :preview_url],
|
||||||
|
properties: %{
|
||||||
|
id: %Schema{type: :string},
|
||||||
|
url: %Schema{
|
||||||
|
type: :string,
|
||||||
|
format: :uri,
|
||||||
|
description: "The location of the original full-size attachment"
|
||||||
|
},
|
||||||
|
remote_url: %Schema{
|
||||||
|
type: :string,
|
||||||
|
format: :uri,
|
||||||
|
description:
|
||||||
|
"The location of the full-size original attachment on the remote website. String (URL), or null if the attachment is local",
|
||||||
|
nullable: true
|
||||||
|
},
|
||||||
|
preview_url: %Schema{
|
||||||
|
type: :string,
|
||||||
|
format: :uri,
|
||||||
|
description: "The location of a scaled-down preview of the attachment"
|
||||||
|
},
|
||||||
|
text_url: %Schema{
|
||||||
|
type: :string,
|
||||||
|
format: :uri,
|
||||||
|
description: "A shorter URL for the attachment"
|
||||||
|
},
|
||||||
|
description: %Schema{
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description:
|
||||||
|
"Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load"
|
||||||
|
},
|
||||||
|
type: %Schema{
|
||||||
|
type: :string,
|
||||||
|
enum: ["image", "video", "audio", "unknown"],
|
||||||
|
description: "The type of the attachment"
|
||||||
|
},
|
||||||
|
pleroma: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
mime_type: %Schema{type: :string, description: "mime type of the attachment"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
id: "1638338801",
|
||||||
|
type: "image",
|
||||||
|
url: "someurl",
|
||||||
|
remote_url: "someurl",
|
||||||
|
preview_url: "someurl",
|
||||||
|
text_url: "someurl",
|
||||||
|
description: nil,
|
||||||
|
pleroma: %{mime_type: "image/png"}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.Attachment
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.Poll
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
|
||||||
|
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "ScheduledStatus",
|
||||||
|
description: "Represents a status that will be published at a future scheduled date.",
|
||||||
|
type: :object,
|
||||||
|
required: [:id, :scheduled_at, :params],
|
||||||
|
properties: %{
|
||||||
|
id: %Schema{type: :string},
|
||||||
|
scheduled_at: %Schema{type: :string, format: :"date-time"},
|
||||||
|
media_attachments: %Schema{type: :array, items: Attachment},
|
||||||
|
params: %Schema{
|
||||||
|
type: :object,
|
||||||
|
required: [:text, :visibility],
|
||||||
|
properties: %{
|
||||||
|
text: %Schema{type: :string, nullable: true},
|
||||||
|
media_ids: %Schema{type: :array, nullable: true, items: %Schema{type: :string}},
|
||||||
|
sensitive: %Schema{type: :boolean, nullable: true},
|
||||||
|
spoiler_text: %Schema{type: :string, nullable: true},
|
||||||
|
visibility: %Schema{type: VisibilityScope, nullable: true},
|
||||||
|
scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true},
|
||||||
|
poll: %Schema{type: Poll, nullable: true},
|
||||||
|
in_reply_to_id: %Schema{type: :string, nullable: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
id: "3221",
|
||||||
|
scheduled_at: "2019-12-05T12:33:01.000Z",
|
||||||
|
params: %{
|
||||||
|
text: "test content",
|
||||||
|
media_ids: nil,
|
||||||
|
sensitive: nil,
|
||||||
|
spoiler_text: nil,
|
||||||
|
visibility: nil,
|
||||||
|
scheduled_at: nil,
|
||||||
|
poll: nil,
|
||||||
|
idempotency: nil,
|
||||||
|
in_reply_to_id: nil
|
||||||
|
},
|
||||||
|
media_attachments: [Attachment.schema().example]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
alias OpenApiSpex.Schema
|
alias OpenApiSpex.Schema
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.Attachment
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Emoji
|
alias Pleroma.Web.ApiSpec.Schemas.Emoji
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Poll
|
alias Pleroma.Web.ApiSpec.Schemas.Poll
|
||||||
|
@ -50,22 +51,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
language: %Schema{type: :string, nullable: true},
|
language: %Schema{type: :string, nullable: true},
|
||||||
media_attachments: %Schema{
|
media_attachments: %Schema{
|
||||||
type: :array,
|
type: :array,
|
||||||
items: %Schema{
|
items: Attachment
|
||||||
type: :object,
|
|
||||||
properties: %{
|
|
||||||
id: %Schema{type: :string},
|
|
||||||
url: %Schema{type: :string, format: :uri},
|
|
||||||
remote_url: %Schema{type: :string, format: :uri},
|
|
||||||
preview_url: %Schema{type: :string, format: :uri},
|
|
||||||
text_url: %Schema{type: :string, format: :uri},
|
|
||||||
description: %Schema{type: :string},
|
|
||||||
type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]},
|
|
||||||
pleroma: %Schema{
|
|
||||||
type: :object,
|
|
||||||
properties: %{mime_type: %Schema{type: :string}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mentions: %Schema{
|
mentions: %Schema{
|
||||||
type: :array,
|
type: :array,
|
||||||
|
|
|
@ -27,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||||
|
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
|
plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
||||||
|
|
||||||
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
|
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
|
||||||
|
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
@local_mastodon_name "Mastodon-Local"
|
@local_mastodon_name "Mastodon-Local"
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
|
defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
:skip_plug,
|
:skip_plug,
|
||||||
|
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
||||||
|
|
||||||
@oauth_read_actions [:show, :index]
|
@oauth_read_actions [:show, :index]
|
||||||
|
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
|
plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
|
||||||
|
|
||||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation
|
||||||
|
|
|
@ -11,17 +11,21 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
|
||||||
alias Pleroma.ScheduledActivity
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||||
|
|
||||||
plug(:assign_scheduled_activity when action != :index)
|
|
||||||
|
|
||||||
@oauth_read_actions [:show, :index]
|
@oauth_read_actions [:show, :index]
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions)
|
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions)
|
||||||
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions)
|
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions)
|
||||||
|
plug(:assign_scheduled_activity when action != :index)
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ScheduledActivityOperation
|
||||||
|
|
||||||
@doc "GET /api/v1/scheduled_statuses"
|
@doc "GET /api/v1/scheduled_statuses"
|
||||||
def index(%{assigns: %{user: user}} = conn, params) do
|
def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
params = Map.new(params, fn {key, value} -> {to_string(key), value} end)
|
||||||
|
|
||||||
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
|
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(scheduled_activities)
|
|> add_link_headers(scheduled_activities)
|
||||||
|
@ -35,7 +39,7 @@ def show(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "PUT /api/v1/scheduled_statuses/:id"
|
@doc "PUT /api/v1/scheduled_statuses/:id"
|
||||||
def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do
|
def update(%{assigns: %{scheduled_activity: scheduled_activity}, body_params: params} = conn, _) do
|
||||||
with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
|
with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
|
||||||
render(conn, "show.json", scheduled_activity: scheduled_activity)
|
render(conn, "show.json", scheduled_activity: scheduled_activity)
|
||||||
end
|
end
|
||||||
|
@ -48,7 +52,7 @@ def delete(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do
|
defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do
|
||||||
case ScheduledActivity.get(user, id) do
|
case ScheduledActivity.get(user, id) do
|
||||||
%ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity)
|
%ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity)
|
||||||
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
|
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
|
||||||
|
|
|
@ -188,6 +188,7 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/reports/:id/notes", AdminAPIController, :report_notes_create)
|
post("/reports/:id/notes", AdminAPIController, :report_notes_create)
|
||||||
delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete)
|
delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete)
|
||||||
|
|
||||||
|
get("/statuses/:id", AdminAPIController, :status_show)
|
||||||
put("/statuses/:id", AdminAPIController, :status_update)
|
put("/statuses/:id", AdminAPIController, :status_update)
|
||||||
delete("/statuses/:id", AdminAPIController, :status_delete)
|
delete("/statuses/:id", AdminAPIController, :status_delete)
|
||||||
get("/statuses", AdminAPIController, :list_statuses)
|
get("/statuses", AdminAPIController, :list_statuses)
|
||||||
|
|
|
@ -86,54 +86,24 @@ def represent_user(user, "XML") do
|
||||||
|> XmlBuilder.to_doc()
|
|> XmlBuilder.to_doc()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_magic_key("data:application/magic-public-key," <> magic_key) do
|
|
||||||
{:ok, magic_key}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_magic_key(nil) do
|
|
||||||
Logger.debug("Undefined magic key.")
|
|
||||||
{:ok, nil}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_magic_key(_) do
|
|
||||||
{:error, "Missing magic key data."}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp webfinger_from_xml(doc) do
|
defp webfinger_from_xml(doc) do
|
||||||
with magic_key <- XML.string_from_xpath(~s{//Link[@rel="magic-public-key"]/@href}, doc),
|
subject = XML.string_from_xpath("//Subject", doc)
|
||||||
{:ok, magic_key} <- get_magic_key(magic_key),
|
|
||||||
topic <-
|
|
||||||
XML.string_from_xpath(
|
|
||||||
~s{//Link[@rel="http://schemas.google.com/g/2010#updates-from"]/@href},
|
|
||||||
doc
|
|
||||||
),
|
|
||||||
subject <- XML.string_from_xpath("//Subject", doc),
|
|
||||||
subscribe_address <-
|
|
||||||
XML.string_from_xpath(
|
|
||||||
~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template},
|
|
||||||
doc
|
|
||||||
),
|
|
||||||
ap_id <-
|
|
||||||
XML.string_from_xpath(
|
|
||||||
~s{//Link[@rel="self" and @type="application/activity+json"]/@href},
|
|
||||||
doc
|
|
||||||
) do
|
|
||||||
data = %{
|
|
||||||
"magic_key" => magic_key,
|
|
||||||
"topic" => topic,
|
|
||||||
"subject" => subject,
|
|
||||||
"subscribe_address" => subscribe_address,
|
|
||||||
"ap_id" => ap_id
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, data}
|
subscribe_address =
|
||||||
else
|
~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}
|
||||||
{:error, e} ->
|
|> XML.string_from_xpath(doc)
|
||||||
{:error, e}
|
|
||||||
|
|
||||||
e ->
|
ap_id =
|
||||||
{:error, e}
|
~s{//Link[@rel="self" and @type="application/activity+json"]/@href}
|
||||||
end
|
|> XML.string_from_xpath(doc)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
"subject" => subject,
|
||||||
|
"subscribe_address" => subscribe_address,
|
||||||
|
"ap_id" => ap_id
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp webfinger_from_json(doc) do
|
defp webfinger_from_json(doc) do
|
||||||
|
@ -146,9 +116,6 @@ defp webfinger_from_json(doc) do
|
||||||
{"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
|
{"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
|
||||||
Map.put(data, "ap_id", link["href"])
|
Map.put(data, "ap_id", link["href"])
|
||||||
|
|
||||||
{_, "http://ostatus.org/schema/1.0/subscribe"} ->
|
|
||||||
Map.put(data, "subscribe_address", link["template"])
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Logger.debug("Unhandled type: #{inspect(link["type"])}")
|
Logger.debug("Unhandled type: #{inspect(link["type"])}")
|
||||||
data
|
data
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.RemoveMagicKeyField do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
remove(:magic_key, :string)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -32,6 +32,7 @@ def user_factory do
|
||||||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||||
bio: sequence(:bio, &"Tester Number #{&1}"),
|
bio: sequence(:bio, &"Tester Number #{&1}"),
|
||||||
last_digest_emailed_at: NaiveDateTime.utc_now(),
|
last_digest_emailed_at: NaiveDateTime.utc_now(),
|
||||||
|
last_refreshed_at: NaiveDateTime.utc_now(),
|
||||||
notification_settings: %Pleroma.User.NotificationSetting{}
|
notification_settings: %Pleroma.User.NotificationSetting{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,12 +40,18 @@ defmacro __using__(_opts) do
|
||||||
clear_config: 2
|
clear_config: 2
|
||||||
]
|
]
|
||||||
|
|
||||||
def to_datetime(naive_datetime) do
|
def to_datetime(%NaiveDateTime{} = naive_datetime) do
|
||||||
naive_datetime
|
naive_datetime
|
||||||
|> DateTime.from_naive!("Etc/UTC")
|
|> DateTime.from_naive!("Etc/UTC")
|
||||||
|> DateTime.truncate(:second)
|
|> DateTime.truncate(:second)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_datetime(datetime) when is_binary(datetime) do
|
||||||
|
datetime
|
||||||
|
|> NaiveDateTime.from_iso8601!()
|
||||||
|
|> to_datetime()
|
||||||
|
end
|
||||||
|
|
||||||
def collect_ids(collection) do
|
def collect_ids(collection) do
|
||||||
collection
|
collection
|
||||||
|> Enum.map(& &1.id)
|
|> Enum.map(& &1.id)
|
||||||
|
|
|
@ -18,9 +18,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.Federator
|
alias Pleroma.Web.Federator
|
||||||
|
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
import Mock
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import Tesla.Mock
|
import Tesla.Mock
|
||||||
import Mock
|
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
@ -2403,4 +2404,51 @@ defp private_messages(_) do
|
||||||
u3: %{r1: r3_1.id, r2: r3_2.id},
|
u3: %{r1: r3_1.id, r2: r3_2.id},
|
||||||
u4: %{r1: r4_1.id}}
|
u4: %{r1: r4_1.id}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "maybe_update_follow_information/1" do
|
||||||
|
setup do
|
||||||
|
clear_config([:instance, :external_user_synchronization], true)
|
||||||
|
|
||||||
|
user = %{
|
||||||
|
local: false,
|
||||||
|
ap_id: "https://gensokyo.2hu/users/raymoo",
|
||||||
|
following_address: "https://gensokyo.2hu/users/following",
|
||||||
|
follower_address: "https://gensokyo.2hu/users/followers",
|
||||||
|
type: "Person"
|
||||||
|
}
|
||||||
|
|
||||||
|
%{user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "logs an error when it can't fetch the info", %{user: user} do
|
||||||
|
assert capture_log(fn ->
|
||||||
|
ActivityPub.maybe_update_follow_information(user)
|
||||||
|
end) =~ "Follower/Following counter update for #{user.ap_id} failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "just returns the input if the user type is Application", %{
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
user =
|
||||||
|
user
|
||||||
|
|> Map.put(:type, "Application")
|
||||||
|
|
||||||
|
refute capture_log(fn ->
|
||||||
|
assert ^user = ActivityPub.maybe_update_follow_information(user)
|
||||||
|
end) =~ "Follower/Following counter update for #{user.ap_id} failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it just returns the input if the user has no following/follower addresses", %{
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
user =
|
||||||
|
user
|
||||||
|
|> Map.put(:following_address, nil)
|
||||||
|
|> Map.put(:follower_address, nil)
|
||||||
|
|
||||||
|
refute capture_log(fn ->
|
||||||
|
assert ^user = ActivityPub.maybe_update_follow_information(user)
|
||||||
|
end) =~ "Follower/Following counter update for #{user.ap_id} failed"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,6 +36,32 @@ test "is valid for a valid object", %{valid_like: valid_like} do
|
||||||
assert LikeValidator.cast_and_validate(valid_like).valid?
|
assert LikeValidator.cast_and_validate(valid_like).valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "sets the 'to' field to the object actor if no recipients are given", %{
|
||||||
|
valid_like: valid_like,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
without_recipients =
|
||||||
|
valid_like
|
||||||
|
|> Map.delete("to")
|
||||||
|
|
||||||
|
{:ok, object, _meta} = ObjectValidator.validate(without_recipients, [])
|
||||||
|
|
||||||
|
assert object["to"] == [user.ap_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sets the context field to the context of the object if no context is given", %{
|
||||||
|
valid_like: valid_like,
|
||||||
|
post_activity: post_activity
|
||||||
|
} do
|
||||||
|
without_context =
|
||||||
|
valid_like
|
||||||
|
|> Map.delete("context")
|
||||||
|
|
||||||
|
{:ok, object, _meta} = ObjectValidator.validate(without_context, [])
|
||||||
|
|
||||||
|
assert object["context"] == post_activity.data["context"]
|
||||||
|
end
|
||||||
|
|
||||||
test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
|
test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
|
||||||
without_actor = Map.delete(valid_like, "actor")
|
without_actor = Map.delete(valid_like, "actor")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "it works for incoming likes" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-like.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"])
|
||||||
|
|
||||||
|
_actor = insert(:user, ap_id: data["actor"], local: false)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
refute Enum.empty?(activity.recipients)
|
||||||
|
|
||||||
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
|
assert data["type"] == "Like"
|
||||||
|
assert data["id"] == "http://mastodon.example.org/users/admin#likes/2"
|
||||||
|
assert data["object"] == activity.data["object"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming misskey likes, turning them into EmojiReacts" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/misskey-like.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"])
|
||||||
|
|
||||||
|
_actor = insert(:user, ap_id: data["actor"], local: false)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: activity_data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert activity_data["actor"] == data["actor"]
|
||||||
|
assert activity_data["type"] == "EmojiReact"
|
||||||
|
assert activity_data["id"] == data["id"]
|
||||||
|
assert activity_data["object"] == activity.data["object"]
|
||||||
|
assert activity_data["content"] == "🍮"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/misskey-like.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"])
|
||||||
|
|> Map.put("_misskey_reaction", "⭐")
|
||||||
|
|
||||||
|
_actor = insert(:user, ap_id: data["actor"], local: false)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: activity_data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert activity_data["actor"] == data["actor"]
|
||||||
|
assert activity_data["type"] == "EmojiReact"
|
||||||
|
assert activity_data["id"] == data["id"]
|
||||||
|
assert activity_data["object"] == activity.data["object"]
|
||||||
|
assert activity_data["content"] == "⭐"
|
||||||
|
end
|
||||||
|
end
|
|
@ -325,62 +325,6 @@ test "it cleans up incoming notices which are not really DMs" do
|
||||||
assert object_data["cc"] == to
|
assert object_data["cc"] == to
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming likes" do
|
|
||||||
user = insert(:user)
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/mastodon-like.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|> Map.put("object", activity.data["object"])
|
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
|
|
||||||
|
|
||||||
refute Enum.empty?(activity.recipients)
|
|
||||||
|
|
||||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
|
||||||
assert data["type"] == "Like"
|
|
||||||
assert data["id"] == "http://mastodon.example.org/users/admin#likes/2"
|
|
||||||
assert data["object"] == activity.data["object"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it works for incoming misskey likes, turning them into EmojiReacts" do
|
|
||||||
user = insert(:user)
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/misskey-like.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|> Map.put("object", activity.data["object"])
|
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
|
||||||
|
|
||||||
assert data["actor"] == data["actor"]
|
|
||||||
assert data["type"] == "EmojiReact"
|
|
||||||
assert data["id"] == data["id"]
|
|
||||||
assert data["object"] == activity.data["object"]
|
|
||||||
assert data["content"] == "🍮"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do
|
|
||||||
user = insert(:user)
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/misskey-like.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|> Map.put("object", activity.data["object"])
|
|
||||||
|> Map.put("_misskey_reaction", "⭐")
|
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
|
||||||
|
|
||||||
assert data["actor"] == data["actor"]
|
|
||||||
assert data["type"] == "EmojiReact"
|
|
||||||
assert data["id"] == data["id"]
|
|
||||||
assert data["object"] == activity.data["object"]
|
|
||||||
assert data["content"] == "⭐"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it works for incoming emoji reactions" do
|
test "it works for incoming emoji reactions" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||||
|
|
|
@ -1620,6 +1620,25 @@ test "returns 403 when requested by anonymous" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "GET /api/pleroma/admin/statuses/:id" do
|
||||||
|
test "not found", %{conn: conn} do
|
||||||
|
assert conn
|
||||||
|
|> get("/api/pleroma/admin/statuses/not_found")
|
||||||
|
|> json_response(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows activity", %{conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> get("/api/pleroma/admin/statuses/#{activity.id}")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert response["id"] == activity.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "PUT /api/pleroma/admin/statuses/:id" do
|
describe "PUT /api/pleroma/admin/statuses/:id" do
|
||||||
setup do
|
setup do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
|
|
|
@ -24,19 +24,19 @@ test "shows scheduled activities" do
|
||||||
# min_id
|
# min_id
|
||||||
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
|
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
result = json_response(conn_res, 200)
|
result = json_response_and_validate_schema(conn_res, 200)
|
||||||
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
|
||||||
# since_id
|
# since_id
|
||||||
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
|
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
result = json_response(conn_res, 200)
|
result = json_response_and_validate_schema(conn_res, 200)
|
||||||
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
|
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
|
||||||
|
|
||||||
# max_id
|
# max_id
|
||||||
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
|
conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
|
||||||
|
|
||||||
result = json_response(conn_res, 200)
|
result = json_response_and_validate_schema(conn_res, 200)
|
||||||
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -46,12 +46,12 @@ test "shows a scheduled activity" do
|
||||||
|
|
||||||
res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
|
assert %{"id" => scheduled_activity_id} = json_response_and_validate_schema(res_conn, 200)
|
||||||
assert scheduled_activity_id == scheduled_activity.id |> to_string()
|
assert scheduled_activity_id == scheduled_activity.id |> to_string()
|
||||||
|
|
||||||
res_conn = get(conn, "/api/v1/scheduled_statuses/404")
|
res_conn = get(conn, "/api/v1/scheduled_statuses/404")
|
||||||
|
|
||||||
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "updates a scheduled activity" do
|
test "updates a scheduled activity" do
|
||||||
|
@ -74,22 +74,32 @@ test "updates a scheduled activity" do
|
||||||
assert job.args == %{"activity_id" => scheduled_activity.id}
|
assert job.args == %{"activity_id" => scheduled_activity.id}
|
||||||
assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at)
|
assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at)
|
||||||
|
|
||||||
new_scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 120)
|
new_scheduled_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> Timex.shift(minutes: 120)
|
||||||
|
|> Timex.format!("%Y-%m-%dT%H:%M:%S.%fZ", :strftime)
|
||||||
|
|
||||||
res_conn =
|
res_conn =
|
||||||
put(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
|
||||||
scheduled_at: new_scheduled_at
|
scheduled_at: new_scheduled_at
|
||||||
})
|
})
|
||||||
|
|
||||||
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
|
assert %{"scheduled_at" => expected_scheduled_at} =
|
||||||
|
json_response_and_validate_schema(res_conn, 200)
|
||||||
|
|
||||||
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
|
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
|
||||||
job = refresh_record(job)
|
job = refresh_record(job)
|
||||||
|
|
||||||
assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at)
|
assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at)
|
||||||
|
|
||||||
res_conn = put(conn, "/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
|
||||||
|
|
||||||
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "deletes a scheduled activity" do
|
test "deletes a scheduled activity" do
|
||||||
|
@ -115,7 +125,7 @@ test "deletes a scheduled activity" do
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
assert %{} = json_response(res_conn, 200)
|
assert %{} = json_response_and_validate_schema(res_conn, 200)
|
||||||
refute Repo.get(ScheduledActivity, scheduled_activity.id)
|
refute Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||||
refute Repo.get(Oban.Job, job.id)
|
refute Repo.get(Oban.Job, job.id)
|
||||||
|
|
||||||
|
@ -124,6 +134,6 @@ test "deletes a scheduled activity" do
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -402,11 +402,17 @@ test "attachments" do
|
||||||
pleroma: %{mime_type: "image/png"}
|
pleroma: %{mime_type: "image/png"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api_spec = Pleroma.Web.ApiSpec.spec()
|
||||||
|
|
||||||
assert expected == StatusView.render("attachment.json", %{attachment: object})
|
assert expected == StatusView.render("attachment.json", %{attachment: object})
|
||||||
|
OpenApiSpex.TestAssertions.assert_schema(expected, "Attachment", api_spec)
|
||||||
|
|
||||||
# If theres a "id", use that instead of the generated one
|
# If theres a "id", use that instead of the generated one
|
||||||
object = Map.put(object, "id", 2)
|
object = Map.put(object, "id", 2)
|
||||||
assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object})
|
result = StatusView.render("attachment.json", %{attachment: object})
|
||||||
|
|
||||||
|
assert %{id: "2"} = result
|
||||||
|
OpenApiSpex.TestAssertions.assert_schema(result, "Attachment", api_spec)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "put the url advertised in the Activity in to the url attribute" do
|
test "put the url advertised in the Activity in to the url attribute" do
|
||||||
|
|
|
@ -67,7 +67,7 @@ test "it work for AP-only user" do
|
||||||
assert data["magic_key"] == nil
|
assert data["magic_key"] == nil
|
||||||
assert data["salmon"] == nil
|
assert data["salmon"] == nil
|
||||||
|
|
||||||
assert data["topic"] == "https://mstdn.jp/users/kPherox.atom"
|
assert data["topic"] == nil
|
||||||
assert data["subject"] == "acct:kPherox@mstdn.jp"
|
assert data["subject"] == "acct:kPherox@mstdn.jp"
|
||||||
assert data["ap_id"] == "https://mstdn.jp/users/kPherox"
|
assert data["ap_id"] == "https://mstdn.jp/users/kPherox"
|
||||||
assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}"
|
assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}"
|
||||||
|
|
Loading…
Reference in New Issue