forked from AkkomaGang/akkoma
Merge branch 'preload-fixups-2' into preloadfixups
This commit is contained in:
commit
0176b7bca2
40 changed files with 496 additions and 207 deletions
|
@ -14,7 +14,7 @@
|
||||||
* Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE):
|
* Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE):
|
||||||
* Elixir version (`elixir -v` for from source installations, N/A for OTP):
|
* Elixir version (`elixir -v` for from source installations, N/A for OTP):
|
||||||
* Operating system:
|
* Operating system:
|
||||||
* PostgreSQL version (`postgres -V`):
|
* PostgreSQL version (`psql -V`):
|
||||||
|
|
||||||
|
|
||||||
### Bug description
|
### Bug description
|
||||||
|
|
|
@ -51,6 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
- Mastodon API: Add pleroma.parents_visible field to statuses.
|
||||||
- Mastodon API: Extended `/api/v1/instance`.
|
- Mastodon API: Extended `/api/v1/instance`.
|
||||||
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
|
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
|
||||||
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
||||||
|
|
|
@ -437,8 +437,6 @@
|
||||||
config :pleroma, Pleroma.Web.Preload,
|
config :pleroma, Pleroma.Web.Preload,
|
||||||
providers: [
|
providers: [
|
||||||
Pleroma.Web.Preload.Providers.Instance,
|
Pleroma.Web.Preload.Providers.Instance,
|
||||||
Pleroma.Web.Preload.Providers.User,
|
|
||||||
Pleroma.Web.Preload.Providers.Timelines,
|
|
||||||
Pleroma.Web.Preload.Providers.StatusNet
|
Pleroma.Web.Preload.Providers.StatusNet
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
||||||
- `thread_muted`: true if the thread the post belongs to is muted
|
- `thread_muted`: true if the thread the post belongs to is muted
|
||||||
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
||||||
|
- `parent_visible`: If the parent of this post is visible to the user or not.
|
||||||
|
|
||||||
## Media Attachments
|
## Media Attachments
|
||||||
|
|
||||||
|
@ -51,11 +52,14 @@ The `id` parameter can also be the `nickname` of the user. This only works in th
|
||||||
|
|
||||||
Has these additional fields under the `pleroma` object:
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
|
- `ap_id`: nullable URL string, ActivityPub id of the user
|
||||||
|
- `background_image`: nullable URL string, background image of the user
|
||||||
- `tags`: Lists an array of tags for the user
|
- `tags`: Lists an array of tags for the user
|
||||||
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
|
- `relationship` (object): Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
|
||||||
- `is_moderator`: boolean, nullable, true if user is a moderator
|
- `is_moderator`: boolean, nullable, true if user is a moderator
|
||||||
- `is_admin`: boolean, nullable, true if user is an admin
|
- `is_admin`: boolean, nullable, true if user is an admin
|
||||||
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
||||||
|
- `hide_favorites`: boolean, true when the user has hiding favorites enabled
|
||||||
- `hide_followers`: boolean, true when the user has follower hiding enabled
|
- `hide_followers`: boolean, true when the user has follower hiding enabled
|
||||||
- `hide_follows`: boolean, true when the user has follow hiding enabled
|
- `hide_follows`: boolean, true when the user has follow hiding enabled
|
||||||
- `hide_followers_count`: boolean, true when the user has follower stat hiding enabled
|
- `hide_followers_count`: boolean, true when the user has follower stat hiding enabled
|
||||||
|
@ -66,6 +70,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
|
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
|
||||||
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
|
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
|
||||||
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
|
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
|
||||||
|
- `notification_settings`: object, can be absent. See `/api/pleroma/notification_settings` for the parameters/keys returned.
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ def extract_first_external_url(object, content) do
|
||||||
result =
|
result =
|
||||||
content
|
content
|
||||||
|> Floki.parse_fragment!()
|
|> Floki.parse_fragment!()
|
||||||
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")
|
|> Floki.filter_out("a.mention,a.hashtag,a.attachment,a[rel~=\"tag\"]")
|
||||||
|> Floki.attribute("a", "href")
|
|> Floki.attribute("a", "href")
|
||||||
|> Enum.at(0)
|
|> Enum.at(0)
|
||||||
|
|
||||||
|
|
|
@ -367,6 +367,7 @@ defp do_create_notifications(%Activity{} = activity, options) do
|
||||||
do_send = do_send && user in enabled_receivers
|
do_send = do_send && user in enabled_receivers
|
||||||
create_notification(activity, user, do_send)
|
create_notification(activity, user, do_send)
|
||||||
end)
|
end)
|
||||||
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|
||||||
{:ok, notifications}
|
{:ok, notifications}
|
||||||
end
|
end
|
||||||
|
|
|
@ -83,8 +83,8 @@ def fetch_object_from_id(id, options \\ []) do
|
||||||
{:transmogrifier, {:error, {:reject, nil}}} ->
|
{:transmogrifier, {:error, {:reject, nil}}} ->
|
||||||
{:reject, nil}
|
{:reject, nil}
|
||||||
|
|
||||||
{:transmogrifier, _} ->
|
{:transmogrifier, _} = e ->
|
||||||
{:error, "Transmogrifier failure."}
|
{:error, e}
|
||||||
|
|
||||||
{:object, data, nil} ->
|
{:object, data, nil} ->
|
||||||
reinject_object(%Object{}, data)
|
reinject_object(%Object{}, data)
|
||||||
|
|
|
@ -1309,7 +1309,8 @@ def block(%User{} = blocker, %User{} = blocked) do
|
||||||
|
|
||||||
unsubscribe(blocked, blocker)
|
unsubscribe(blocked, blocker)
|
||||||
|
|
||||||
if following?(blocked, blocker), do: unfollow(blocked, blocker)
|
unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
|
||||||
|
if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = update_follower_count(blocker)
|
{:ok, blocker} = update_follower_count(blocker)
|
||||||
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
||||||
|
@ -1527,8 +1528,7 @@ def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
|
||||||
blocked_identifiers,
|
blocked_identifiers,
|
||||||
fn blocked_identifier ->
|
fn blocked_identifier ->
|
||||||
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
||||||
{:ok, _user_block} <- block(blocker, blocked),
|
{:ok, _block} <- CommonAPI.block(blocker, blocked) do
|
||||||
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
|
||||||
blocked
|
blocked
|
||||||
else
|
else
|
||||||
err ->
|
err ->
|
||||||
|
|
|
@ -366,33 +366,6 @@ defp do_unfollow(follower, followed, activity_id, local) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
|
|
||||||
{:ok, Activity.t()} | {:error, any()}
|
|
||||||
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
|
|
||||||
with {:ok, result} <-
|
|
||||||
Repo.transaction(fn -> do_block(blocker, blocked, activity_id, local) end) do
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_block(blocker, blocked, activity_id, local) do
|
|
||||||
unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
|
|
||||||
|
|
||||||
if unfollow_blocked and fetch_latest_follow(blocker, blocked) do
|
|
||||||
unfollow(blocker, blocked, nil, local)
|
|
||||||
end
|
|
||||||
|
|
||||||
block_data = make_block_data(blocker, blocked, activity_id)
|
|
||||||
|
|
||||||
with {:ok, activity} <- insert(block_data, local),
|
|
||||||
_ <- notify_and_stream(activity),
|
|
||||||
:ok <- maybe_federate(activity) do
|
|
||||||
{:ok, activity}
|
|
||||||
else
|
|
||||||
{:error, error} -> Repo.rollback(error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||||
def flag(
|
def flag(
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -138,6 +138,18 @@ def update(actor, object) do
|
||||||
}, []}
|
}, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
|
||||||
|
def block(blocker, blocked) do
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"type" => "Block",
|
||||||
|
"actor" => blocker.ap_id,
|
||||||
|
"object" => blocked.ap_id,
|
||||||
|
"to" => [blocked.ap_id]
|
||||||
|
}, []}
|
||||||
|
end
|
||||||
|
|
||||||
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
||||||
def announce(actor, object, options \\ []) do
|
def announce(actor, object, options \\ []) do
|
||||||
public? = Keyword.get(options, :public, false)
|
public? = Keyword.get(options, :public, false)
|
||||||
|
|
|
@ -27,11 +27,14 @@ defp contains_links?(_), do: false
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
||||||
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
|
with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
||||||
{:old_user, true} <- {:old_user, old_user?(u)} do
|
{:old_user, true} <- {:old_user, old_user?(u)} do
|
||||||
{:ok, message}
|
{:ok, message}
|
||||||
else
|
else
|
||||||
|
{:ok, %User{local: true}} ->
|
||||||
|
{:ok, message}
|
||||||
|
|
||||||
{:contains_links, false} ->
|
{:contains_links, false} ->
|
||||||
{:ok, message}
|
{:ok, message}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||||
|
@ -24,6 +25,25 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||||
def validate(object, meta)
|
def validate(object, meta)
|
||||||
|
|
||||||
|
def validate(%{"type" => "Block"} = block_activity, meta) do
|
||||||
|
with {:ok, block_activity} <-
|
||||||
|
block_activity
|
||||||
|
|> BlockValidator.cast_and_validate()
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
block_activity = stringify_keys(block_activity)
|
||||||
|
outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
|
||||||
|
|
||||||
|
meta =
|
||||||
|
if !outgoing_blocks do
|
||||||
|
Keyword.put(meta, :do_not_federate, true)
|
||||||
|
else
|
||||||
|
meta
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, block_activity, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate(%{"type" => "Update"} = update_activity, meta) do
|
def validate(%{"type" => "Update"} = update_activity, meta) do
|
||||||
with {:ok, update_activity} <-
|
with {:ok, update_activity} <-
|
||||||
update_activity
|
update_activity
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# 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.ObjectValidators.BlockValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
|
field(:type, :string)
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:object, ObjectValidators.ObjectID)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(cng) do
|
||||||
|
cng
|
||||||
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|
|> validate_inclusion(:type, ["Block"])
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_actor_presence(field_name: :object)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data
|
||||||
|
|> validate_data
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,6 +20,21 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
|
|
||||||
def handle(object, meta \\ [])
|
def handle(object, meta \\ [])
|
||||||
|
|
||||||
|
# Tasks this handles:
|
||||||
|
# - Unfollow and block
|
||||||
|
def handle(
|
||||||
|
%{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} =
|
||||||
|
object,
|
||||||
|
meta
|
||||||
|
) do
|
||||||
|
with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user),
|
||||||
|
%User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do
|
||||||
|
User.block(blocker, blocked)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
|
||||||
# Tasks this handles:
|
# Tasks this handles:
|
||||||
# - Update the user
|
# - Update the user
|
||||||
#
|
#
|
||||||
|
|
|
@ -673,7 +673,7 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(%{"type" => type} = data, _options)
|
def handle_incoming(%{"type" => type} = data, _options)
|
||||||
when type in ["Like", "EmojiReact", "Announce"] do
|
when type in ~w{Like EmojiReact Announce} do
|
||||||
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||||
{:ok, activity, _meta} <-
|
{:ok, activity, _meta} <-
|
||||||
Pipeline.common_pipeline(data, local: false) do
|
Pipeline.common_pipeline(data, local: false) do
|
||||||
|
@ -684,9 +684,10 @@ def handle_incoming(%{"type" => type} = data, _options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Update"} = data,
|
%{"type" => type} = data,
|
||||||
_options
|
_options
|
||||||
) do
|
)
|
||||||
|
when type in ~w{Update Block} do
|
||||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||||
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -765,21 +766,6 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
|
||||||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
|
|
||||||
_options
|
|
||||||
) do
|
|
||||||
with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
|
||||||
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
|
|
||||||
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
|
||||||
User.unfollow(blocker, blocked)
|
|
||||||
User.block(blocker, blocked)
|
|
||||||
{:ok, activity}
|
|
||||||
else
|
|
||||||
_e -> :error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{
|
%{
|
||||||
"type" => "Move",
|
"type" => "Move",
|
||||||
|
|
|
@ -47,6 +47,10 @@ def is_list?(_), do: false
|
||||||
@spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
|
@spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
|
||||||
def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
|
def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
|
||||||
|
|
||||||
|
def visible_for_user?(nil, _), do: false
|
||||||
|
|
||||||
|
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
|
||||||
|
|
||||||
def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
|
def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
|
||||||
user.ap_id in activity.data["to"] ||
|
user.ap_id in activity.data["to"] ||
|
||||||
list_ap_id
|
list_ap_id
|
||||||
|
@ -54,8 +58,6 @@ def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{
|
||||||
|> Pleroma.List.member?(user)
|
|> Pleroma.List.member?(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
|
|
||||||
|
|
||||||
def visible_for_user?(%{local: local} = activity, nil) do
|
def visible_for_user?(%{local: local} = activity, nil) do
|
||||||
cfg_key =
|
cfg_key =
|
||||||
if local,
|
if local,
|
||||||
|
|
|
@ -40,7 +40,7 @@ def call(%{private: %{open_api_spex: private_data}} = conn, %{
|
||||||
|> List.first()
|
|> List.first()
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
"application/json"
|
||||||
end
|
end
|
||||||
|
|
||||||
private_data = Map.put(private_data, :operation_id, operation_id)
|
private_data = Map.put(private_data, :operation_id, operation_id)
|
||||||
|
|
|
@ -40,20 +40,53 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
pleroma: %Schema{
|
pleroma: %Schema{
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
allow_following_move: %Schema{type: :boolean},
|
allow_following_move: %Schema{
|
||||||
background_image: %Schema{type: :string, nullable: true},
|
type: :boolean,
|
||||||
|
description: "whether the user allows automatically follow moved following accounts"
|
||||||
|
},
|
||||||
|
background_image: %Schema{type: :string, nullable: true, format: :uri},
|
||||||
chat_token: %Schema{type: :string},
|
chat_token: %Schema{type: :string},
|
||||||
confirmation_pending: %Schema{type: :boolean},
|
confirmation_pending: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description:
|
||||||
|
"whether the user account is waiting on email confirmation to be activated"
|
||||||
|
},
|
||||||
hide_favorites: %Schema{type: :boolean},
|
hide_favorites: %Schema{type: :boolean},
|
||||||
hide_followers_count: %Schema{type: :boolean},
|
hide_followers_count: %Schema{
|
||||||
hide_followers: %Schema{type: :boolean},
|
type: :boolean,
|
||||||
hide_follows_count: %Schema{type: :boolean},
|
description: "whether the user has follower stat hiding enabled"
|
||||||
hide_follows: %Schema{type: :boolean},
|
},
|
||||||
is_admin: %Schema{type: :boolean},
|
hide_followers: %Schema{
|
||||||
is_moderator: %Schema{type: :boolean},
|
type: :boolean,
|
||||||
|
description: "whether the user has follower hiding enabled"
|
||||||
|
},
|
||||||
|
hide_follows_count: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "whether the user has follow stat hiding enabled"
|
||||||
|
},
|
||||||
|
hide_follows: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "whether the user has follow hiding enabled"
|
||||||
|
},
|
||||||
|
is_admin: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "whether the user is an admin of the local instance"
|
||||||
|
},
|
||||||
|
is_moderator: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "whether the user is a moderator of the local instance"
|
||||||
|
},
|
||||||
skip_thread_containment: %Schema{type: :boolean},
|
skip_thread_containment: %Schema{type: :boolean},
|
||||||
tags: %Schema{type: :array, items: %Schema{type: :string}},
|
tags: %Schema{
|
||||||
unread_conversation_count: %Schema{type: :integer},
|
type: :array,
|
||||||
|
items: %Schema{type: :string},
|
||||||
|
description:
|
||||||
|
"List of tags being used for things like extra roles or moderation(ie. marking all media as nsfw all)."
|
||||||
|
},
|
||||||
|
unread_conversation_count: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
description: "The count of unread conversations. Only returned to the account owner."
|
||||||
|
},
|
||||||
notification_settings: %Schema{
|
notification_settings: %Schema{
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
|
@ -66,7 +99,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
},
|
},
|
||||||
relationship: AccountRelationship,
|
relationship: AccountRelationship,
|
||||||
settings_store: %Schema{
|
settings_store: %Schema{
|
||||||
type: :object
|
type: :object,
|
||||||
|
description:
|
||||||
|
"A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -74,16 +109,32 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
fields: %Schema{type: :array, items: AccountField},
|
fields: %Schema{type: :array, items: AccountField},
|
||||||
note: %Schema{type: :string},
|
note: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"Plaintext version of the bio without formatting applied by the backend, used for editing the bio."
|
||||||
|
},
|
||||||
privacy: VisibilityScope,
|
privacy: VisibilityScope,
|
||||||
sensitive: %Schema{type: :boolean},
|
sensitive: %Schema{type: :boolean},
|
||||||
pleroma: %Schema{
|
pleroma: %Schema{
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
actor_type: ActorType,
|
actor_type: ActorType,
|
||||||
discoverable: %Schema{type: :boolean},
|
discoverable: %Schema{
|
||||||
no_rich_text: %Schema{type: :boolean},
|
type: :boolean,
|
||||||
show_role: %Schema{type: :boolean}
|
description:
|
||||||
|
"whether the user allows discovery of the account in search results and other services."
|
||||||
|
},
|
||||||
|
no_rich_text: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description:
|
||||||
|
"whether the HTML tags for rich-text formatting are stripped from all statuses requested from the API."
|
||||||
|
},
|
||||||
|
show_role: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description:
|
||||||
|
"whether the user wants their role (e.g admin, moderator) to be shown"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,6 +184,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
thread_muted: %Schema{
|
thread_muted: %Schema{
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "`true` if the thread the post belongs to is muted"
|
description: "`true` if the thread the post belongs to is muted"
|
||||||
|
},
|
||||||
|
parent_visible: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "`true` if the parent post is visible to the user"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -25,6 +25,13 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
def block(blocker, blocked) do
|
||||||
|
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
||||||
|
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
||||||
|
{:ok, block}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
||||||
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
||||||
:ok <- validate_chat_content_length(content, !!maybe_attachment),
|
:ok <- validate_chat_content_length(content, !!maybe_attachment),
|
||||||
|
|
|
@ -385,8 +385,7 @@ def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
|
||||||
|
|
||||||
@doc "POST /api/v1/accounts/:id/block"
|
@doc "POST /api/v1/accounts/:id/block"
|
||||||
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
||||||
with {:ok, _user_block} <- User.block(blocker, blocked),
|
with {:ok, _activity} <- CommonAPI.block(blocker, blocked) do
|
||||||
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
|
||||||
render(conn, "relationship.json", user: blocker, target: blocked)
|
render(conn, "relationship.json", user: blocker, target: blocked)
|
||||||
else
|
else
|
||||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||||
|
|
|
@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
|
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
|
||||||
|
|
||||||
# TODO: Add cached version.
|
# TODO: Add cached version.
|
||||||
defp get_replied_to_activities([]), do: %{}
|
defp get_replied_to_activities([]), do: %{}
|
||||||
|
@ -364,7 +364,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
expires_at: expires_at,
|
expires_at: expires_at,
|
||||||
direct_conversation_id: direct_conversation_id,
|
direct_conversation_id: direct_conversation_id,
|
||||||
thread_muted: thread_muted?,
|
thread_muted: thread_muted?,
|
||||||
emoji_reactions: emoji_reactions
|
emoji_reactions: emoji_reactions,
|
||||||
|
parent_visible: visible_for_user?(reply_to, opts[:for])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.Preload.Providers.Instance do
|
||||||
alias Pleroma.Web.MastodonAPI.InstanceView
|
alias Pleroma.Web.MastodonAPI.InstanceView
|
||||||
alias Pleroma.Web.Nodeinfo.Nodeinfo
|
alias Pleroma.Web.Nodeinfo.Nodeinfo
|
||||||
alias Pleroma.Web.Preload.Providers.Provider
|
alias Pleroma.Web.Preload.Providers.Provider
|
||||||
|
alias Pleroma.Plugs.InstanceStatic
|
||||||
|
|
||||||
@behaviour Provider
|
@behaviour Provider
|
||||||
@instance_url "/api/v1/instance"
|
@instance_url "/api/v1/instance"
|
||||||
|
@ -27,7 +28,7 @@ defp build_info_tag(acc) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_panel_tag(acc) do
|
defp build_panel_tag(acc) do
|
||||||
instance_path = Path.join(:code.priv_dir(:pleroma), "static/instance/panel.html")
|
instance_path = InstanceStatic.file_path(@panel_url |> to_string())
|
||||||
|
|
||||||
if File.exists?(instance_path) do
|
if File.exists?(instance_path) do
|
||||||
panel_data = File.read!(instance_path)
|
panel_data = File.read!(instance_path)
|
||||||
|
|
5
mix.exs
5
mix.exs
|
@ -159,7 +159,10 @@ defp deps do
|
||||||
{:cors_plug, "~> 1.5"},
|
{:cors_plug, "~> 1.5"},
|
||||||
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
|
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
|
||||||
{:web_push_encryption, "~> 0.2.1"},
|
{:web_push_encryption, "~> 0.2.1"},
|
||||||
{:swoosh, "~> 0.23.2"},
|
{:swoosh,
|
||||||
|
git: "https://github.com/swoosh/swoosh",
|
||||||
|
ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5",
|
||||||
|
override: true},
|
||||||
{:phoenix_swoosh, "~> 0.2"},
|
{:phoenix_swoosh, "~> 0.2"},
|
||||||
{:gen_smtp, "~> 0.13"},
|
{:gen_smtp, "~> 0.13"},
|
||||||
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
|
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
|
||||||
|
|
4
mix.lock
4
mix.lock
|
@ -104,9 +104,9 @@
|
||||||
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
|
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||||
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
|
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
|
||||||
"swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"},
|
"swoosh": {:git, "https://github.com/swoosh/swoosh", "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", [ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5"]},
|
||||||
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
||||||
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
|
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
||||||
"tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]},
|
"tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]},
|
||||||
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
|
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
|
||||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||||
|
|
1
test/fixtures/preload_static/instance/panel.html
vendored
Normal file
1
test/fixtures/preload_static/instance/panel.html
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
HEY!
|
|
@ -237,5 +237,19 @@ test "does not crash when there is an HTML entity in a link" do
|
||||||
|
|
||||||
assert {:ok, nil} = HTML.extract_first_external_url(object, object.data["content"])
|
assert {:ok, nil} = HTML.extract_first_external_url(object, object.data["content"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "skips attachment links" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
status:
|
||||||
|
"<a href=\"https://pleroma.gov/media/d24caa3a498e21e0298377a9ca0149a4f4f8b767178aacf837542282e2d94fb1.png?name=image.png\" class=\"attachment\">image.png</a>"
|
||||||
|
})
|
||||||
|
|
||||||
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
|
assert {:ok, nil} = HTML.extract_first_external_url(object, object.data["content"])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,16 @@ defmodule Pleroma.NotificationTest do
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
|
|
||||||
describe "create_notifications" do
|
describe "create_notifications" do
|
||||||
|
test "never returns nil" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user, %{invisible: true})
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
|
||||||
|
{:ok, activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||||
|
|
||||||
|
refute {:ok, [nil]} == Notification.create_notifications(activity)
|
||||||
|
end
|
||||||
|
|
||||||
test "creates a notification for an emoji reaction" do
|
test "creates a notification for an emoji reaction" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
|
@ -992,54 +992,6 @@ test "creates an undo activity for a pending follow request" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "blocking" do
|
|
||||||
test "reverts block activity on error" do
|
|
||||||
[blocker, blocked] = insert_list(2, :user)
|
|
||||||
|
|
||||||
with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
|
|
||||||
assert {:error, :reverted} = ActivityPub.block(blocker, blocked)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert Repo.aggregate(Activity, :count, :id) == 0
|
|
||||||
assert Repo.aggregate(Object, :count, :id) == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
test "creates a block activity" do
|
|
||||||
clear_config([:instance, :federating], true)
|
|
||||||
blocker = insert(:user)
|
|
||||||
blocked = insert(:user)
|
|
||||||
|
|
||||||
with_mock Pleroma.Web.Federator,
|
|
||||||
publish: fn _ -> nil end do
|
|
||||||
{:ok, activity} = ActivityPub.block(blocker, blocked)
|
|
||||||
|
|
||||||
assert activity.data["type"] == "Block"
|
|
||||||
assert activity.data["actor"] == blocker.ap_id
|
|
||||||
assert activity.data["object"] == blocked.ap_id
|
|
||||||
|
|
||||||
assert called(Pleroma.Web.Federator.publish(activity))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "works with outgoing blocks disabled, but doesn't federate" do
|
|
||||||
clear_config([:instance, :federating], true)
|
|
||||||
clear_config([:activitypub, :outgoing_blocks], false)
|
|
||||||
blocker = insert(:user)
|
|
||||||
blocked = insert(:user)
|
|
||||||
|
|
||||||
with_mock Pleroma.Web.Federator,
|
|
||||||
publish: fn _ -> nil end do
|
|
||||||
{:ok, activity} = ActivityPub.block(blocker, blocked)
|
|
||||||
|
|
||||||
assert activity.data["type"] == "Block"
|
|
||||||
assert activity.data["actor"] == blocker.ap_id
|
|
||||||
assert activity.data["object"] == blocked.ap_id
|
|
||||||
|
|
||||||
refute called(Pleroma.Web.Federator.publish(:_))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "timeline post-processing" do
|
describe "timeline post-processing" do
|
||||||
test "it filters broken threads" do
|
test "it filters broken threads" do
|
||||||
user1 = insert(:user)
|
user1 = insert(:user)
|
||||||
|
|
|
@ -33,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
|
||||||
|
|
||||||
describe "with new user" do
|
describe "with new user" do
|
||||||
test "it allows posts without links" do
|
test "it allows posts without links" do
|
||||||
user = insert(:user)
|
user = insert(:user, local: false)
|
||||||
|
|
||||||
assert user.note_count == 0
|
assert user.note_count == 0
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ test "it allows posts without links" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it disallows posts with links" do
|
test "it disallows posts with links" do
|
||||||
user = insert(:user)
|
user = insert(:user, local: false)
|
||||||
|
|
||||||
assert user.note_count == 0
|
assert user.note_count == 0
|
||||||
|
|
||||||
|
@ -55,6 +55,18 @@ test "it disallows posts with links" do
|
||||||
|
|
||||||
{:reject, _} = AntiLinkSpamPolicy.filter(message)
|
{:reject, _} = AntiLinkSpamPolicy.filter(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it allows posts with links for local users" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
assert user.note_count == 0
|
||||||
|
|
||||||
|
message =
|
||||||
|
@linkful_message
|
||||||
|
|> Map.put("actor", user.ap_id)
|
||||||
|
|
||||||
|
{:ok, _message} = AntiLinkSpamPolicy.filter(message)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with old user" do
|
describe "with old user" do
|
||||||
|
|
|
@ -654,4 +654,31 @@ test "returns an error if the object can't be updated by the actor", %{
|
||||||
assert {:error, _cng} = ObjectValidator.validate(update, [])
|
assert {:error, _cng} = ObjectValidator.validate(update, [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "blocks" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user, local: false)
|
||||||
|
blocked = insert(:user)
|
||||||
|
|
||||||
|
{:ok, valid_block, []} = Builder.block(user, blocked)
|
||||||
|
|
||||||
|
%{user: user, valid_block: valid_block}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "validates a basic object", %{
|
||||||
|
valid_block: valid_block
|
||||||
|
} do
|
||||||
|
assert {:ok, _block, []} = ObjectValidator.validate(valid_block, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns an error if we don't know the blocked user", %{
|
||||||
|
valid_block: valid_block
|
||||||
|
} do
|
||||||
|
block =
|
||||||
|
valid_block
|
||||||
|
|> Map.put("object", "https://gensokyo.2hu/users/raymoo")
|
||||||
|
|
||||||
|
assert {:error, _cng} = ObjectValidator.validate(block, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -64,6 +64,47 @@ test "it streams out notifications and streams" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "blocking users" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
User.follow(blocked, user)
|
||||||
|
User.follow(user, blocked)
|
||||||
|
|
||||||
|
{:ok, block_data, []} = Builder.block(user, blocked)
|
||||||
|
{:ok, block, _meta} = ActivityPub.persist(block_data, local: true)
|
||||||
|
|
||||||
|
%{user: user, blocked: blocked, block: block}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it unfollows and blocks", %{user: user, blocked: blocked, block: block} do
|
||||||
|
assert User.following?(user, blocked)
|
||||||
|
assert User.following?(blocked, user)
|
||||||
|
|
||||||
|
{:ok, _, _} = SideEffects.handle(block)
|
||||||
|
|
||||||
|
refute User.following?(user, blocked)
|
||||||
|
refute User.following?(blocked, user)
|
||||||
|
assert User.blocks?(user, blocked)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it blocks but does not unfollow if the relevant setting is set", %{
|
||||||
|
user: user,
|
||||||
|
blocked: blocked,
|
||||||
|
block: block
|
||||||
|
} do
|
||||||
|
clear_config([:activitypub, :unfollow_blocked], false)
|
||||||
|
assert User.following?(user, blocked)
|
||||||
|
assert User.following?(blocked, user)
|
||||||
|
|
||||||
|
{:ok, _, _} = SideEffects.handle(block)
|
||||||
|
|
||||||
|
refute User.following?(user, blocked)
|
||||||
|
assert User.following?(blocked, user)
|
||||||
|
assert User.blocks?(user, blocked)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "update users" do
|
describe "update users" do
|
||||||
setup do
|
setup do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -242,8 +283,7 @@ test "when activation is required", %{delete: delete, user: user} do
|
||||||
{:ok, like} = CommonAPI.favorite(user, post.id)
|
{:ok, like} = CommonAPI.favorite(user, post.id)
|
||||||
{:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍")
|
{:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍")
|
||||||
{:ok, announce} = CommonAPI.repeat(post.id, user)
|
{:ok, announce} = CommonAPI.repeat(post.id, user)
|
||||||
{:ok, block} = ActivityPub.block(user, poster)
|
{:ok, block} = CommonAPI.block(user, poster)
|
||||||
User.block(user, poster)
|
|
||||||
|
|
||||||
{:ok, undo_data, _meta} = Builder.undo(user, like)
|
{:ok, undo_data, _meta} = Builder.undo(user, like)
|
||||||
{:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true)
|
{:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true)
|
||||||
|
|
63
test/web/activity_pub/transmogrifier/block_handling_test.exs
Normal file
63
test/web/activity_pub/transmogrifier/block_handling_test.exs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# 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.BlockHandlingTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "it works for incoming blocks" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-block-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", user.ap_id)
|
||||||
|
|
||||||
|
blocker = insert(:user, ap_id: data["actor"])
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["type"] == "Block"
|
||||||
|
assert data["object"] == user.ap_id
|
||||||
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
|
|
||||||
|
assert User.blocks?(blocker, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "incoming blocks successfully tear down any follow relationship" do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-block-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", blocked.ap_id)
|
||||||
|
|> Map.put("actor", blocker.ap_id)
|
||||||
|
|
||||||
|
{:ok, blocker} = User.follow(blocker, blocked)
|
||||||
|
{:ok, blocked} = User.follow(blocked, blocker)
|
||||||
|
|
||||||
|
assert User.following?(blocker, blocked)
|
||||||
|
assert User.following?(blocked, blocker)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["type"] == "Block"
|
||||||
|
assert data["object"] == blocked.ap_id
|
||||||
|
assert data["actor"] == blocker.ap_id
|
||||||
|
|
||||||
|
blocker = User.get_cached_by_ap_id(data["actor"])
|
||||||
|
blocked = User.get_cached_by_ap_id(data["object"])
|
||||||
|
|
||||||
|
assert User.blocks?(blocker, blocked)
|
||||||
|
|
||||||
|
refute User.following?(blocker, blocked)
|
||||||
|
refute User.following?(blocked, blocker)
|
||||||
|
end
|
||||||
|
end
|
|
@ -445,56 +445,6 @@ test "it works for incoming follows to locked account" do
|
||||||
assert [^pending_follower] = User.get_follow_requests(user)
|
assert [^pending_follower] = User.get_follow_requests(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming blocks" do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/mastodon-block-activity.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|> Map.put("object", user.ap_id)
|
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
|
||||||
|
|
||||||
assert data["type"] == "Block"
|
|
||||||
assert data["object"] == user.ap_id
|
|
||||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
|
||||||
|
|
||||||
blocker = User.get_cached_by_ap_id(data["actor"])
|
|
||||||
|
|
||||||
assert User.blocks?(blocker, user)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "incoming blocks successfully tear down any follow relationship" do
|
|
||||||
blocker = insert(:user)
|
|
||||||
blocked = insert(:user)
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/mastodon-block-activity.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|> Map.put("object", blocked.ap_id)
|
|
||||||
|> Map.put("actor", blocker.ap_id)
|
|
||||||
|
|
||||||
{:ok, blocker} = User.follow(blocker, blocked)
|
|
||||||
{:ok, blocked} = User.follow(blocked, blocker)
|
|
||||||
|
|
||||||
assert User.following?(blocker, blocked)
|
|
||||||
assert User.following?(blocked, blocker)
|
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
|
||||||
|
|
||||||
assert data["type"] == "Block"
|
|
||||||
assert data["object"] == blocked.ap_id
|
|
||||||
assert data["actor"] == blocker.ap_id
|
|
||||||
|
|
||||||
blocker = User.get_cached_by_ap_id(data["actor"])
|
|
||||||
blocked = User.get_cached_by_ap_id(data["object"])
|
|
||||||
|
|
||||||
assert User.blocks?(blocker, blocked)
|
|
||||||
|
|
||||||
refute User.following?(blocker, blocked)
|
|
||||||
refute User.following?(blocked, blocker)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it works for incoming accepts which were pre-accepted" do
|
test "it works for incoming accepts which were pre-accepted" do
|
||||||
follower = insert(:user)
|
follower = insert(:user)
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
|
|
|
@ -27,16 +27,6 @@ test "fetches the latest Follow activity" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "fetch the latest Block" do
|
|
||||||
test "fetches the latest Block activity" do
|
|
||||||
blocker = insert(:user)
|
|
||||||
blocked = insert(:user)
|
|
||||||
{:ok, activity} = ActivityPub.block(blocker, blocked)
|
|
||||||
|
|
||||||
assert activity == Utils.fetch_latest_block(blocker, blocked)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "determine_explicit_mentions()" do
|
describe "determine_explicit_mentions()" do
|
||||||
test "works with an object that has mentions" do
|
test "works with an object that has mentions" do
|
||||||
object = %{
|
object = %{
|
||||||
|
@ -344,9 +334,9 @@ test "fetches last block activities" do
|
||||||
user1 = insert(:user)
|
user1 = insert(:user)
|
||||||
user2 = insert(:user)
|
user2 = insert(:user)
|
||||||
|
|
||||||
assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2)
|
assert {:ok, %Activity{} = _} = CommonAPI.block(user1, user2)
|
||||||
assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2)
|
assert {:ok, %Activity{} = _} = CommonAPI.block(user1, user2)
|
||||||
assert {:ok, %Activity{} = activity} = ActivityPub.block(user1, user2)
|
assert {:ok, %Activity{} = activity} = CommonAPI.block(user1, user2)
|
||||||
|
|
||||||
assert Utils.fetch_latest_block(user1, user2) == activity
|
assert Utils.fetch_latest_block(user1, user2) == activity
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,6 +25,52 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
setup do: clear_config([:instance, :limit])
|
setup do: clear_config([:instance, :limit])
|
||||||
setup do: clear_config([:instance, :max_pinned_statuses])
|
setup do: clear_config([:instance, :max_pinned_statuses])
|
||||||
|
|
||||||
|
describe "blocking" do
|
||||||
|
setup do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
User.follow(blocker, blocked)
|
||||||
|
User.follow(blocked, blocker)
|
||||||
|
%{blocker: blocker, blocked: blocked}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
|
||||||
|
clear_config([:instance, :federating], true)
|
||||||
|
|
||||||
|
with_mock Pleroma.Web.Federator,
|
||||||
|
publish: fn _ -> nil end do
|
||||||
|
assert {:ok, block} = CommonAPI.block(blocker, blocked)
|
||||||
|
|
||||||
|
assert block.local
|
||||||
|
assert User.blocks?(blocker, blocked)
|
||||||
|
refute User.following?(blocker, blocked)
|
||||||
|
refute User.following?(blocked, blocker)
|
||||||
|
|
||||||
|
assert called(Pleroma.Web.Federator.publish(block))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it blocks and does not federate if outgoing blocks are disabled", %{
|
||||||
|
blocker: blocker,
|
||||||
|
blocked: blocked
|
||||||
|
} do
|
||||||
|
clear_config([:instance, :federating], true)
|
||||||
|
clear_config([:activitypub, :outgoing_blocks], false)
|
||||||
|
|
||||||
|
with_mock Pleroma.Web.Federator,
|
||||||
|
publish: fn _ -> nil end do
|
||||||
|
assert {:ok, block} = CommonAPI.block(blocker, blocked)
|
||||||
|
|
||||||
|
assert block.local
|
||||||
|
assert User.blocks?(blocker, blocked)
|
||||||
|
refute User.following?(blocker, blocked)
|
||||||
|
refute User.following?(blocked, blocker)
|
||||||
|
|
||||||
|
refute called(Pleroma.Web.Federator.publish(block))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "posting chat messages" do
|
describe "posting chat messages" do
|
||||||
setup do: clear_config([:instance, :chat_limit])
|
setup do: clear_config([:instance, :chat_limit])
|
||||||
|
|
||||||
|
|
|
@ -216,10 +216,20 @@ test "updates the user's avatar", %{user: user, conn: conn} do
|
||||||
filename: "an_image.jpg"
|
filename: "an_image.jpg"
|
||||||
}
|
}
|
||||||
|
|
||||||
conn = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
|
res =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
|
||||||
|
|
||||||
assert user_response = json_response_and_validate_schema(conn, 200)
|
assert user_response = json_response_and_validate_schema(res, 200)
|
||||||
assert user_response["avatar"] != User.avatar_url(user)
|
assert user_response["avatar"] != User.avatar_url(user)
|
||||||
|
|
||||||
|
# Also removes it
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"avatar" => nil})
|
||||||
|
|
||||||
|
assert user_response = json_response_and_validate_schema(res, 200)
|
||||||
|
assert user_response["avatar"] == User.avatar_url(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "updates the user's banner", %{user: user, conn: conn} do
|
test "updates the user's banner", %{user: user, conn: conn} do
|
||||||
|
@ -229,10 +239,21 @@ test "updates the user's banner", %{user: user, conn: conn} do
|
||||||
filename: "an_image.jpg"
|
filename: "an_image.jpg"
|
||||||
}
|
}
|
||||||
|
|
||||||
conn = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header})
|
res =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
|
||||||
|
|
||||||
assert user_response = json_response_and_validate_schema(conn, 200)
|
assert user_response = json_response_and_validate_schema(res, 200)
|
||||||
assert user_response["header"] != User.banner_url(user)
|
assert user_response["header"] != User.banner_url(user)
|
||||||
|
|
||||||
|
# Also removes it
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"header" => nil})
|
||||||
|
|
||||||
|
assert user_response = json_response_and_validate_schema(res, 200)
|
||||||
|
assert user_response["header"] == User.banner_url(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "updates the user's background", %{conn: conn} do
|
test "updates the user's background", %{conn: conn} do
|
||||||
|
@ -242,13 +263,25 @@ test "updates the user's background", %{conn: conn} do
|
||||||
filename: "an_image.jpg"
|
filename: "an_image.jpg"
|
||||||
}
|
}
|
||||||
|
|
||||||
conn =
|
res =
|
||||||
patch(conn, "/api/v1/accounts/update_credentials", %{
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{
|
||||||
"pleroma_background_image" => new_header
|
"pleroma_background_image" => new_header
|
||||||
})
|
})
|
||||||
|
|
||||||
assert user_response = json_response_and_validate_schema(conn, 200)
|
assert user_response = json_response_and_validate_schema(res, 200)
|
||||||
assert user_response["pleroma"]["background_image"]
|
assert user_response["pleroma"]["background_image"]
|
||||||
|
|
||||||
|
# Also removes it
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{
|
||||||
|
"pleroma_background_image" => nil
|
||||||
|
})
|
||||||
|
|
||||||
|
assert user_response = json_response_and_validate_schema(res, 200)
|
||||||
|
refute user_response["pleroma"]["background_image"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "requires 'write:accounts' permission" do
|
test "requires 'write:accounts' permission" do
|
||||||
|
|
|
@ -780,7 +780,6 @@ test "with notifications", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"id" => _id, "muting" => true, "muting_notifications" => true} =
|
assert %{"id" => _id, "muting" => true, "muting_notifications" => true} =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> post("/api/v1/accounts/#{other_user.id}/mute")
|
|> post("/api/v1/accounts/#{other_user.id}/mute")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
|
|
@ -226,7 +226,8 @@ test "a note activity" do
|
||||||
expires_at: nil,
|
expires_at: nil,
|
||||||
direct_conversation_id: nil,
|
direct_conversation_id: nil,
|
||||||
thread_muted: false,
|
thread_muted: false,
|
||||||
emoji_reactions: []
|
emoji_reactions: [],
|
||||||
|
parent_visible: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,4 +621,20 @@ test "visibility/list" do
|
||||||
|
|
||||||
assert status.visibility == "list"
|
assert status.visibility == "list"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "has a field for parent visibility" do
|
||||||
|
user = insert(:user)
|
||||||
|
poster = insert(:user)
|
||||||
|
|
||||||
|
{:ok, invisible} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
|
||||||
|
|
||||||
|
{:ok, visible} =
|
||||||
|
CommonAPI.post(poster, %{status: "hey", visibility: "private", in_reply_to_id: invisible.id})
|
||||||
|
|
||||||
|
status = StatusView.render("show.json", activity: visible, for: user)
|
||||||
|
refute status.pleroma.parent_visible
|
||||||
|
|
||||||
|
status = StatusView.render("show.json", activity: visible, for: poster)
|
||||||
|
assert status.pleroma.parent_visible
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,6 +25,17 @@ test "it renders the panel", %{"/instance/panel.html" => panel} do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it works with overrides" do
|
||||||
|
clear_config([:instance, :static_dir], "test/fixtures/preload_static")
|
||||||
|
|
||||||
|
%{"/instance/panel.html" => panel} = Instance.generate_terms(nil)
|
||||||
|
|
||||||
|
assert String.contains?(
|
||||||
|
panel,
|
||||||
|
"HEY!"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
test "it renders the node_info", %{"/nodeinfo/2.0.json" => nodeinfo} do
|
test "it renders the node_info", %{"/nodeinfo/2.0.json" => nodeinfo} do
|
||||||
%{
|
%{
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
|
Loading…
Reference in a new issue