forked from AkkomaGang/akkoma
Merge branch 'develop' into openapi/account
This commit is contained in:
commit
f1ca917bb0
42 changed files with 1044 additions and 445 deletions
|
@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
<summary>API Changes</summary>
|
||||
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
|
||||
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
||||
- Mastodon API: Add support for filtering replies in public and home timelines
|
||||
- Admin API: endpoints for create/update/delete OAuth Apps.
|
||||
</details>
|
||||
|
||||
|
|
|
@ -279,7 +279,7 @@ defp insert_activity("like", visibility, group, user, friends, non_friends, opts
|
|||
actor = get_actor(group, user, friends, non_friends)
|
||||
|
||||
with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(),
|
||||
{:ok, _activity, _object} <- CommonAPI.favorite(activity_id, actor) do
|
||||
{:ok, _activity} <- CommonAPI.favorite(actor, activity_id) do
|
||||
:ok
|
||||
else
|
||||
{:error, _} ->
|
||||
|
@ -313,7 +313,7 @@ defp insert_activity("simple_thread", visibility, group, user, friends, non_frie
|
|||
tasks = get_reply_tasks(visibility, group)
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{"status" => "Simple status", "visibility" => "unlisted"})
|
||||
CommonAPI.post(user, %{"status" => "Simple status", "visibility" => visibility})
|
||||
|
||||
acc = {activity.id, ["@" <> actor.nickname, "reply to status"]}
|
||||
insert_replies(tasks, visibility, user, friends, non_friends, acc)
|
||||
|
|
|
@ -41,6 +41,7 @@ defp fetch_timelines(user) do
|
|||
fetch_notifications(user)
|
||||
fetch_favourites(user)
|
||||
fetch_long_thread(user)
|
||||
fetch_timelines_with_reply_filtering(user)
|
||||
end
|
||||
|
||||
defp render_views(user) do
|
||||
|
@ -495,4 +496,58 @@ defp render_long_thread(user) do
|
|||
formatters: formatters()
|
||||
)
|
||||
end
|
||||
|
||||
defp fetch_timelines_with_reply_filtering(user) do
|
||||
public_params = opts_for_public_timeline(user)
|
||||
|
||||
Benchee.run(
|
||||
%{
|
||||
"Public timeline without reply filtering" => fn ->
|
||||
ActivityPub.fetch_public_activities(public_params)
|
||||
end,
|
||||
"Public timeline with reply filtering - following" => fn ->
|
||||
public_params
|
||||
|> Map.put("reply_visibility", "following")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
end,
|
||||
"Public timeline with reply filtering - self" => fn ->
|
||||
public_params
|
||||
|> Map.put("reply_visibility", "self")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
end
|
||||
},
|
||||
formatters: formatters()
|
||||
)
|
||||
|
||||
private_params = opts_for_home_timeline(user)
|
||||
|
||||
recipients = [user.ap_id | User.following(user)]
|
||||
|
||||
Benchee.run(
|
||||
%{
|
||||
"Home timeline without reply filtering" => fn ->
|
||||
ActivityPub.fetch_activities(recipients, private_params)
|
||||
end,
|
||||
"Home timeline with reply filtering - following" => fn ->
|
||||
private_params =
|
||||
private_params
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> Map.put("reply_visibility", "following")
|
||||
|
||||
ActivityPub.fetch_activities(recipients, private_params)
|
||||
end,
|
||||
"Home timeline with reply filtering - self" => fn ->
|
||||
private_params =
|
||||
private_params
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> Map.put("reply_visibility", "self")
|
||||
|
||||
ActivityPub.fetch_activities(recipients, private_params)
|
||||
end
|
||||
},
|
||||
formatters: formatters()
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,6 +44,7 @@ defmodule Mix.Tasks.Pleroma.LoadTesting do
|
|||
]
|
||||
|
||||
def run(args) do
|
||||
Logger.configure(level: :error)
|
||||
Mix.Pleroma.start_pleroma()
|
||||
clean_tables()
|
||||
{opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
|
||||
|
|
|
@ -14,6 +14,7 @@ Some apps operate under the assumption that no more than 4 attachments can be re
|
|||
|
||||
Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.
|
||||
Adding the parameter `exclude_visibilities` to the timeline queries will exclude the statuses with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`), e.g., `exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
||||
Adding the parameter `reply_visibility` to the public and home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you.
|
||||
|
||||
## Statuses
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ content-security-policy:
|
|||
default-src 'none';
|
||||
base-uri 'self';
|
||||
frame-ancestors 'none';
|
||||
img-src 'self' data: https:;
|
||||
img-src 'self' data: blob: https:;
|
||||
media-src 'self' https:;
|
||||
style-src 'self' 'unsafe-inline';
|
||||
font-src 'self';
|
||||
|
|
|
@ -75,7 +75,7 @@ defp csp_string do
|
|||
"default-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"img-src 'self' data: https:",
|
||||
"img-src 'self' data: blob: https:",
|
||||
"media-src 'self' https:",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"font-src 'self'",
|
||||
|
|
|
@ -45,11 +45,11 @@ def get_peers do
|
|||
end
|
||||
|
||||
def init(_args) do
|
||||
{:ok, get_stat_data()}
|
||||
{:ok, calculate_stat_data()}
|
||||
end
|
||||
|
||||
def handle_call(:force_update, _from, _state) do
|
||||
new_stats = get_stat_data()
|
||||
new_stats = calculate_stat_data()
|
||||
{:reply, new_stats, new_stats}
|
||||
end
|
||||
|
||||
|
@ -58,12 +58,12 @@ def handle_call(:get_state, _from, state) do
|
|||
end
|
||||
|
||||
def handle_cast(:run_update, _state) do
|
||||
new_stats = get_stat_data()
|
||||
new_stats = calculate_stat_data()
|
||||
|
||||
{:noreply, new_stats}
|
||||
end
|
||||
|
||||
defp get_stat_data do
|
||||
def calculate_stat_data do
|
||||
peers =
|
||||
from(
|
||||
u in User,
|
||||
|
@ -77,7 +77,15 @@ defp get_stat_data do
|
|||
|
||||
status_count = Repo.aggregate(User.Query.build(%{local: true}), :sum, :note_count)
|
||||
|
||||
user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
|
||||
users_query =
|
||||
from(u in User,
|
||||
where: u.deactivated != true,
|
||||
where: u.local == true,
|
||||
where: not is_nil(u.nickname),
|
||||
where: not u.invisible
|
||||
)
|
||||
|
||||
user_count = Repo.aggregate(users_query, :count, :id)
|
||||
|
||||
%{
|
||||
peers: peers,
|
||||
|
|
|
@ -832,6 +832,7 @@ def set_cache({:error, err}), do: {:error, err}
|
|||
def set_cache(%User{} = user) do
|
||||
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
|
||||
Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
|
@ -847,9 +848,22 @@ def update_and_set_cache(changeset) do
|
|||
end
|
||||
end
|
||||
|
||||
def get_user_friends_ap_ids(user) do
|
||||
from(u in User.get_friends_query(user), select: u.ap_id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
|
||||
def get_cached_user_friends_ap_ids(user) do
|
||||
Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
|
||||
get_user_friends_ap_ids(user)
|
||||
end)
|
||||
end
|
||||
|
||||
def invalidate_cache(user) do
|
||||
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
|
||||
Cachex.del(:user_cache, "nickname:#{user.nickname}")
|
||||
Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
|
||||
end
|
||||
|
||||
@spec get_cached_by_ap_id(String.t()) :: User.t() | nil
|
||||
|
|
|
@ -54,13 +54,13 @@ defmodule Pleroma.User.Query do
|
|||
select: term(),
|
||||
limit: pos_integer()
|
||||
}
|
||||
| %{}
|
||||
| map()
|
||||
|
||||
@ilike_criteria [:nickname, :name, :query]
|
||||
@equal_criteria [:email]
|
||||
@contains_criteria [:ap_id, :nickname]
|
||||
|
||||
@spec build(criteria()) :: Query.t()
|
||||
@spec build(Query.t(), criteria()) :: Query.t()
|
||||
def build(query \\ base_query(), criteria) do
|
||||
prepare_query(query, criteria)
|
||||
end
|
||||
|
|
|
@ -398,36 +398,6 @@ defp do_unreact_with_emoji(user, reaction_id, options) do
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
|
||||
@spec like(User.t(), Object.t(), String.t() | nil, boolean()) ::
|
||||
{:ok, Activity.t(), Object.t()} | {:error, any()}
|
||||
def like(user, object, activity_id \\ nil, local \\ true) do
|
||||
with {:ok, result} <- Repo.transaction(fn -> do_like(user, object, activity_id, local) end) do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
defp do_like(
|
||||
%User{ap_id: ap_id} = user,
|
||||
%Object{data: %{"id" => _}} = object,
|
||||
activity_id,
|
||||
local
|
||||
) do
|
||||
with nil <- get_existing_like(ap_id, object),
|
||||
like_data <- make_like_data(user, object, activity_id),
|
||||
{:ok, activity} <- insert(like_data, local),
|
||||
{:ok, object} <- add_like_to_object(activity, object),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
else
|
||||
%Activity{} = activity ->
|
||||
{:ok, activity, object}
|
||||
|
||||
{:error, error} ->
|
||||
Repo.rollback(error)
|
||||
end
|
||||
end
|
||||
|
||||
@spec unlike(User.t(), Object.t(), String.t() | nil, boolean()) ::
|
||||
{:ok, Activity.t(), Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}
|
||||
def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do
|
||||
|
@ -468,6 +438,7 @@ def announce(
|
|||
|
||||
defp do_announce(user, object, activity_id, local, public) do
|
||||
with true <- is_announceable?(object, user, public),
|
||||
object <- Object.get_by_id(object.id),
|
||||
announce_data <- make_announce_data(user, object, activity_id, public),
|
||||
{:ok, activity} <- insert(announce_data, local),
|
||||
{:ok, object} <- add_announce_to_object(activity, object),
|
||||
|
@ -1077,6 +1048,41 @@ defp restrict_replies(query, %{"exclude_replies" => val}) when val in [true, "tr
|
|||
)
|
||||
end
|
||||
|
||||
defp restrict_replies(query, %{
|
||||
"reply_filtering_user" => user,
|
||||
"reply_visibility" => "self"
|
||||
}) do
|
||||
from(
|
||||
[activity, object] in query,
|
||||
where:
|
||||
fragment(
|
||||
"?->>'inReplyTo' is null OR ? = ANY(?)",
|
||||
object.data,
|
||||
^user.ap_id,
|
||||
activity.recipients
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_replies(query, %{
|
||||
"reply_filtering_user" => user,
|
||||
"reply_visibility" => "following"
|
||||
}) do
|
||||
from(
|
||||
[activity, object] in query,
|
||||
where:
|
||||
fragment(
|
||||
"?->>'inReplyTo' is null OR ? && array_remove(?, ?) OR ? = ?",
|
||||
object.data,
|
||||
^[user.ap_id | User.get_cached_user_friends_ap_ids(user)],
|
||||
activity.recipients,
|
||||
activity.actor,
|
||||
activity.actor,
|
||||
^user.ap_id
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_replies(query, _), do: query
|
||||
|
||||
defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val in [true, "true", "1"] do
|
||||
|
@ -1296,6 +1302,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> maybe_set_thread_muted_field(opts)
|
||||
|> maybe_order(opts)
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|> restrict_replies(opts)
|
||||
|> restrict_tag(opts)
|
||||
|> restrict_tag_reject(opts)
|
||||
|> restrict_tag_all(opts)
|
||||
|
@ -1310,7 +1317,6 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> restrict_media(opts)
|
||||
|> restrict_visibility(opts)
|
||||
|> restrict_thread_visibility(opts, config)
|
||||
|> restrict_replies(opts)
|
||||
|> restrict_reblogs(opts)
|
||||
|> restrict_pinned(opts)
|
||||
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
||||
|
|
|
@ -12,8 +12,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
alias Pleroma.Plugs.EnsureAuthenticatedPlug
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||
alias Pleroma.Web.ActivityPub.ObjectView
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.UserView
|
||||
|
@ -421,7 +423,10 @@ defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
|
|||
|
||||
defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
|
||||
with %Object{} = object <- Object.normalize(params["object"]),
|
||||
{:ok, activity, _object} <- ActivityPub.like(user, object) do
|
||||
{_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
|
||||
{_, {:ok, %Activity{} = activity, _meta}} <-
|
||||
{:common_pipeline,
|
||||
Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
|
||||
{:ok, activity}
|
||||
else
|
||||
_ -> {:error, dgettext("errors", "Can't like object")}
|
||||
|
|
|
@ -15,12 +15,17 @@ def handle(object, meta \\ [])
|
|||
# - Add like to object
|
||||
# - Set up notification
|
||||
def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||
liked_object = Object.get_by_ap_id(object.data["object"])
|
||||
Utils.add_like_to_object(object, liked_object)
|
||||
{:ok, result} =
|
||||
Pleroma.Repo.transaction(fn ->
|
||||
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
|
||||
|
||||
# Nothing to do
|
||||
|
|
|
@ -8,15 +8,16 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
|
|||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
def render("index.json", opts) do
|
||||
safe_render_many(opts.activities, __MODULE__, "show.json", opts)
|
||||
end
|
||||
|
||||
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
||||
user = get_user(activity.data["actor"])
|
||||
user = StatusView.get_user(activity.data["actor"])
|
||||
|
||||
Pleroma.Web.MastodonAPI.StatusView.render("show.json", opts)
|
||||
StatusView.render("show.json", opts)
|
||||
|> Map.merge(%{account: merge_account_views(user)})
|
||||
end
|
||||
|
||||
|
@ -26,17 +27,4 @@ defp merge_account_views(%User{} = user) do
|
|||
end
|
||||
|
||||
defp merge_account_views(_), do: %{}
|
||||
|
||||
defp get_user(ap_id) do
|
||||
cond do
|
||||
user = User.get_cached_by_ap_id(ap_id) ->
|
||||
user
|
||||
|
||||
user = User.get_by_guessed_nickname(ap_id) ->
|
||||
user
|
||||
|
||||
true ->
|
||||
User.error_user(ap_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,6 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
|
|||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
|
||||
|
||||
@spec open_api_operation(atom) :: Operation.t()
|
||||
def open_api_operation(action) do
|
||||
|
@ -22,9 +20,9 @@ def create_operation do
|
|||
summary: "Create an application",
|
||||
description: "Create a new application to obtain OAuth2 credentials",
|
||||
operationId: "AppController.create",
|
||||
requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
|
||||
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("App", "application/json", AppCreateResponse),
|
||||
200 => Operation.response("App", "application/json", create_response()),
|
||||
422 =>
|
||||
Operation.response(
|
||||
"Unprocessable Entity",
|
||||
|
@ -89,4 +87,58 @@ def verify_credentials_operation do
|
|||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_request do
|
||||
%Schema{
|
||||
title: "AppCreateRequest",
|
||||
description: "POST body for creating an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
client_name: %Schema{type: :string, description: "A name for your application."},
|
||||
redirect_uris: %Schema{
|
||||
type: :string,
|
||||
description:
|
||||
"Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
|
||||
},
|
||||
scopes: %Schema{
|
||||
type: :string,
|
||||
description: "Space separated list of scopes",
|
||||
default: "read"
|
||||
},
|
||||
website: %Schema{type: :string, description: "A URL to the homepage of your app"}
|
||||
},
|
||||
required: [:client_name, :redirect_uris],
|
||||
example: %{
|
||||
"client_name" => "My App",
|
||||
"redirect_uris" => "https://myapp.com/auth/callback",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_response do
|
||||
%Schema{
|
||||
title: "AppCreateResponse",
|
||||
description: "Response schema for an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
name: %Schema{type: :string},
|
||||
client_id: %Schema{type: :string},
|
||||
client_secret: %Schema{type: :string},
|
||||
redirect_uri: %Schema{type: :string},
|
||||
vapid_key: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true}
|
||||
},
|
||||
example: %{
|
||||
"id" => "123",
|
||||
"name" => "My App",
|
||||
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
|
||||
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
|
||||
"vapid_key" =>
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias Pleroma.Web.ApiSpec.Schemas.CustomEmojisResponse
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.CustomEmoji
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
|
@ -18,8 +19,43 @@ def index_operation do
|
|||
description: "Returns custom emojis that are available on the server.",
|
||||
operationId: "CustomEmojiController.index",
|
||||
responses: %{
|
||||
200 => Operation.response("Custom Emojis", "application/json", CustomEmojisResponse)
|
||||
200 => Operation.response("Custom Emojis", "application/json", custom_emojis_resposnse())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp custom_emojis_resposnse do
|
||||
%Schema{
|
||||
title: "CustomEmojisResponse",
|
||||
description: "Response schema for custom emojis",
|
||||
type: :array,
|
||||
items: CustomEmoji,
|
||||
example: [
|
||||
%{
|
||||
"category" => "Fun",
|
||||
"shortcode" => "blank",
|
||||
"static_url" => "https://lain.com/emoji/blank.png",
|
||||
"tags" => ["Fun"],
|
||||
"url" => "https://lain.com/emoji/blank.png",
|
||||
"visible_in_picker" => false
|
||||
},
|
||||
%{
|
||||
"category" => "Gif,Fun",
|
||||
"shortcode" => "firefox",
|
||||
"static_url" => "https://lain.com/emoji/Firefox.gif",
|
||||
"tags" => ["Gif", "Fun"],
|
||||
"url" => "https://lain.com/emoji/Firefox.gif",
|
||||
"visible_in_picker" => true
|
||||
},
|
||||
%{
|
||||
"category" => "pack:mixed",
|
||||
"shortcode" => "sadcat",
|
||||
"static_url" => "https://lain.com/emoji/mixed/sadcat.png",
|
||||
"tags" => ["pack:mixed"],
|
||||
"url" => "https://lain.com/emoji/mixed/sadcat.png",
|
||||
"visible_in_picker" => true
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,6 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
|
|||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest
|
||||
alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
|
@ -22,7 +20,13 @@ def index_operation do
|
|||
security: [%{"oAuth" => ["follow", "read:blocks"]}],
|
||||
operationId: "DomainBlockController.index",
|
||||
responses: %{
|
||||
200 => Operation.response("Domain blocks", "application/json", DomainBlocksResponse)
|
||||
200 =>
|
||||
Operation.response("Domain blocks", "application/json", %Schema{
|
||||
description: "Response schema for domain blocks",
|
||||
type: :array,
|
||||
items: %Schema{type: :string},
|
||||
example: ["google.com", "facebook.com"]
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -40,7 +44,7 @@ def create_operation do
|
|||
- prevent following new users from it (but does not remove existing follows)
|
||||
""",
|
||||
operationId: "DomainBlockController.create",
|
||||
requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
|
||||
requestBody: domain_block_request(),
|
||||
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
|
||||
|
@ -54,11 +58,28 @@ def delete_operation do
|
|||
summary: "Unblock a domain",
|
||||
description: "Remove a domain block, if it exists in the user's array of blocked domains.",
|
||||
operationId: "DomainBlockController.delete",
|
||||
requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
|
||||
requestBody: domain_block_request(),
|
||||
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp domain_block_request do
|
||||
Helpers.request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
domain: %Schema{type: :string}
|
||||
},
|
||||
required: [:domain]
|
||||
},
|
||||
required: true,
|
||||
example: %{
|
||||
"domain" => "facebook.com"
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
# 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.AppCreateRequest do
|
||||
alias OpenApiSpex.Schema
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "AppCreateRequest",
|
||||
description: "POST body for creating an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
client_name: %Schema{type: :string, description: "A name for your application."},
|
||||
redirect_uris: %Schema{
|
||||
type: :string,
|
||||
description:
|
||||
"Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
|
||||
},
|
||||
scopes: %Schema{
|
||||
type: :string,
|
||||
description: "Space separated list of scopes. If none is provided, defaults to `read`."
|
||||
},
|
||||
website: %Schema{
|
||||
type: :string,
|
||||
description: "A URL to the homepage of your app",
|
||||
format: :uri
|
||||
}
|
||||
},
|
||||
required: [:client_name, :redirect_uris],
|
||||
example: %{
|
||||
"client_name" => "My App",
|
||||
"redirect_uris" => "https://myapp.com/auth/callback",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
})
|
||||
end
|
|
@ -1,33 +0,0 @@
|
|||
# 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.AppCreateResponse do
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "AppCreateResponse",
|
||||
description: "Response schema for an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
name: %Schema{type: :string},
|
||||
client_id: %Schema{type: :string},
|
||||
client_secret: %Schema{type: :string},
|
||||
redirect_uri: %Schema{type: :string, format: :uri},
|
||||
vapid_key: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true, format: :uri}
|
||||
},
|
||||
example: %{
|
||||
"id" => "123",
|
||||
"name" => "My App",
|
||||
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
|
||||
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
|
||||
"vapid_key" =>
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
})
|
||||
end
|
|
@ -1,42 +0,0 @@
|
|||
# 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.CustomEmojisResponse do
|
||||
alias Pleroma.Web.ApiSpec.Schemas.CustomEmoji
|
||||
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "CustomEmojisResponse",
|
||||
description: "Response schema for custom emojis",
|
||||
type: :array,
|
||||
items: CustomEmoji,
|
||||
example: [
|
||||
%{
|
||||
"category" => "Fun",
|
||||
"shortcode" => "blank",
|
||||
"static_url" => "https://lain.com/emoji/blank.png",
|
||||
"tags" => ["Fun"],
|
||||
"url" => "https://lain.com/emoji/blank.png",
|
||||
"visible_in_picker" => true
|
||||
},
|
||||
%{
|
||||
"category" => "Gif,Fun",
|
||||
"shortcode" => "firefox",
|
||||
"static_url" => "https://lain.com/emoji/Firefox.gif",
|
||||
"tags" => ["Gif", "Fun"],
|
||||
"url" => "https://lain.com/emoji/Firefox.gif",
|
||||
"visible_in_picker" => true
|
||||
},
|
||||
%{
|
||||
"category" => "pack:mixed",
|
||||
"shortcode" => "sadcat",
|
||||
"static_url" => "https://lain.com/emoji/mixed/sadcat.png",
|
||||
"tags" => ["pack:mixed"],
|
||||
"url" => "https://lain.com/emoji/mixed/sadcat.png",
|
||||
"visible_in_picker" => true
|
||||
}
|
||||
]
|
||||
})
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
# 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.DomainBlockRequest do
|
||||
alias OpenApiSpex.Schema
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "DomainBlockRequest",
|
||||
type: :object,
|
||||
properties: %{
|
||||
domain: %Schema{type: :string}
|
||||
},
|
||||
required: [:domain],
|
||||
example: %{
|
||||
"domain" => "facebook.com"
|
||||
}
|
||||
})
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
# 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.DomainBlocksResponse do
|
||||
require OpenApiSpex
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "DomainBlocksResponse",
|
||||
description: "Response schema for domain blocks",
|
||||
type: :array,
|
||||
items: %Schema{type: :string},
|
||||
example: ["google.com", "facebook.com"]
|
||||
})
|
||||
end
|
|
@ -84,14 +84,18 @@ defp attachments(%{params: params} = draft) do
|
|||
%__MODULE__{draft | attachments: attachments}
|
||||
end
|
||||
|
||||
defp in_reply_to(draft) do
|
||||
case Map.get(draft.params, "in_reply_to_status_id") do
|
||||
"" -> draft
|
||||
nil -> draft
|
||||
id -> %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
|
||||
end
|
||||
defp in_reply_to(%{params: %{"in_reply_to_status_id" => ""}} = draft), do: draft
|
||||
|
||||
defp in_reply_to(%{params: %{"in_reply_to_status_id" => id}} = draft) when is_binary(id) do
|
||||
%__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
|
||||
end
|
||||
|
||||
defp in_reply_to(%{params: %{"in_reply_to_status_id" => %Activity{} = in_reply_to}} = draft) do
|
||||
%__MODULE__{draft | in_reply_to: in_reply_to}
|
||||
end
|
||||
|
||||
defp in_reply_to(draft), do: draft
|
||||
|
||||
defp in_reply_to_conversation(draft) do
|
||||
in_reply_to_conversation = Participation.get(draft.params["in_reply_to_conversation_id"])
|
||||
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
|
||||
|
|
|
@ -86,8 +86,9 @@ def delete(activity_id, user) do
|
|||
end
|
||||
end
|
||||
|
||||
def repeat(id_or_ap_id, user, params \\ %{}) do
|
||||
with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)},
|
||||
def repeat(id, user, params \\ %{}) do
|
||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(id)},
|
||||
object <- Object.normalize(activity),
|
||||
announce_activity <- Utils.get_existing_announce(user.ap_id, object),
|
||||
public <- public_announce?(object, params) do
|
||||
|
@ -102,8 +103,9 @@ def repeat(id_or_ap_id, user, params \\ %{}) do
|
|||
end
|
||||
end
|
||||
|
||||
def unrepeat(id_or_ap_id, user) do
|
||||
with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do
|
||||
def unrepeat(id, user) do
|
||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(id)} do
|
||||
object = Object.normalize(activity)
|
||||
ActivityPub.unannounce(user, object)
|
||||
else
|
||||
|
@ -160,8 +162,9 @@ def favorite_helper(user, id) do
|
|||
end
|
||||
end
|
||||
|
||||
def unfavorite(id_or_ap_id, user) do
|
||||
with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do
|
||||
def unfavorite(id, user) do
|
||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(id)} do
|
||||
object = Object.normalize(activity)
|
||||
ActivityPub.unlike(user, object)
|
||||
else
|
||||
|
@ -332,12 +335,12 @@ defp maybe_create_activity_expiration({:ok, activity}, %NaiveDateTime{} = expire
|
|||
|
||||
defp maybe_create_activity_expiration(result, _), do: result
|
||||
|
||||
def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
||||
def pin(id, %{ap_id: user_ap_id} = user) do
|
||||
with %Activity{
|
||||
actor: ^user_ap_id,
|
||||
data: %{"type" => "Create"},
|
||||
object: %Object{data: %{"type" => object_type}}
|
||||
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
} = activity <- Activity.get_by_id_with_object(id),
|
||||
true <- object_type in ["Note", "Article", "Question"],
|
||||
true <- Visibility.is_public?(activity),
|
||||
{:ok, _user} <- User.add_pinnned_activity(user, activity) do
|
||||
|
@ -348,8 +351,8 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
|||
end
|
||||
end
|
||||
|
||||
def unpin(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
def unpin(id, user) do
|
||||
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
|
||||
{:ok, _user} <- User.remove_pinnned_activity(user, activity) do
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
|
@ -22,24 +22,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
# This is a hack for twidere.
|
||||
def get_by_id_or_ap_id(id) do
|
||||
activity =
|
||||
with true <- FlakeId.flake_id?(id),
|
||||
%Activity{} = activity <- Activity.get_by_id_with_object(id) do
|
||||
activity
|
||||
else
|
||||
_ -> Activity.get_create_by_object_ap_id_with_object(id)
|
||||
end
|
||||
|
||||
activity &&
|
||||
if activity.data["type"] == "Create" do
|
||||
activity
|
||||
else
|
||||
Activity.get_create_by_object_ap_id_with_object(activity.data["object"])
|
||||
end
|
||||
end
|
||||
|
||||
def attachments_from_ids(%{"media_ids" => ids, "descriptions" => desc} = _) do
|
||||
attachments_from_ids_descs(ids, desc)
|
||||
end
|
||||
|
|
|
@ -127,7 +127,8 @@ def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
|
|||
def create(
|
||||
%{assigns: %{user: user}} = conn,
|
||||
%{"status" => _, "scheduled_at" => scheduled_at} = params
|
||||
) do
|
||||
)
|
||||
when not is_nil(scheduled_at) do
|
||||
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
|
||||
|
||||
with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)},
|
||||
|
|
|
@ -37,6 +37,7 @@ def home(%{assigns: %{user: user}} = conn, params) do
|
|||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> Map.put("user", user)
|
||||
|
||||
recipients = [user.ap_id | User.following(user)]
|
||||
|
@ -100,6 +101,7 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
|||
|> Map.put("local_only", local_only)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|
||||
conn
|
||||
|
|
|
@ -45,7 +45,7 @@ defp get_replied_to_activities(activities) do
|
|||
end)
|
||||
end
|
||||
|
||||
defp get_user(ap_id) do
|
||||
def get_user(ap_id, fake_record_fallback \\ true) do
|
||||
cond do
|
||||
user = User.get_cached_by_ap_id(ap_id) ->
|
||||
user
|
||||
|
@ -53,8 +53,12 @@ defp get_user(ap_id) do
|
|||
user = User.get_by_guessed_nickname(ap_id) ->
|
||||
user
|
||||
|
||||
true ->
|
||||
fake_record_fallback ->
|
||||
# TODO: refactor (fake records is never a good idea)
|
||||
User.error_user(ap_id)
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -97,7 +101,11 @@ def render("index.json", opts) do
|
|||
UserRelationship.view_relationships_option(nil, [])
|
||||
|
||||
true ->
|
||||
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
|
||||
# Note: unresolved users are filtered out
|
||||
actors =
|
||||
(activities ++ parent_activities)
|
||||
|> Enum.map(&get_user(&1.data["actor"], false))
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
UserRelationship.view_relationships_option(reading_user, actors,
|
||||
source_mutes_only: opts[:skip_relationships]
|
||||
|
|
|
@ -17,12 +17,8 @@ defmodule Pleroma.Web.OAuth.Scopes do
|
|||
"""
|
||||
@spec fetch_scopes(map() | struct(), list()) :: list()
|
||||
|
||||
def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do
|
||||
parse_scopes(scopes, default)
|
||||
end
|
||||
|
||||
def fetch_scopes(params, default) do
|
||||
parse_scopes(params["scope"] || params["scopes"], default)
|
||||
parse_scopes(params["scope"] || params["scopes"] || params[:scopes], default)
|
||||
end
|
||||
|
||||
def parse_scopes(scopes, _default) when is_list(scopes) do
|
||||
|
|
|
@ -2,11 +2,21 @@
|
|||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.StateTest do
|
||||
defmodule Pleroma.StatsTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
describe "user count" do
|
||||
test "it ignores internal users" do
|
||||
_user = insert(:user, local: true)
|
||||
_internal = insert(:user, local: true, nickname: nil)
|
||||
_internal = Pleroma.Web.ActivityPub.Relay.get_actor()
|
||||
|
||||
assert match?(%{stats: %{user_count: 1}}, Pleroma.Stats.calculate_stat_data())
|
||||
end
|
||||
end
|
||||
|
||||
describe "status visibility count" do
|
||||
test "on new status" do
|
||||
user = insert(:user)
|
57
test/support/api_spec_helpers.ex
Normal file
57
test/support/api_spec_helpers.ex
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Tests.ApiSpecHelpers do
|
||||
@moduledoc """
|
||||
OpenAPI spec test helpers
|
||||
"""
|
||||
|
||||
import ExUnit.Assertions
|
||||
|
||||
alias OpenApiSpex.Cast.Error
|
||||
alias OpenApiSpex.Reference
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
def assert_schema(value, schema) do
|
||||
api_spec = Pleroma.Web.ApiSpec.spec()
|
||||
|
||||
case OpenApiSpex.cast_value(value, schema, api_spec) do
|
||||
{:ok, data} ->
|
||||
data
|
||||
|
||||
{:error, errors} ->
|
||||
errors =
|
||||
Enum.map(errors, fn error ->
|
||||
message = Error.message(error)
|
||||
path = Error.path_to_string(error)
|
||||
"#{message} at #{path}"
|
||||
end)
|
||||
|
||||
flunk(
|
||||
"Value does not conform to schema #{schema.title}: #{Enum.join(errors, "\n")}\n#{
|
||||
inspect(value)
|
||||
}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_schema(%Schema{} = schema), do: schema
|
||||
|
||||
def resolve_schema(%Reference{} = ref) do
|
||||
schemas = Pleroma.Web.ApiSpec.spec().components.schemas
|
||||
Reference.resolve_schema(ref, schemas)
|
||||
end
|
||||
|
||||
def api_operations do
|
||||
paths = Pleroma.Web.ApiSpec.spec().paths
|
||||
|
||||
Enum.flat_map(paths, fn {_, path_item} ->
|
||||
path_item
|
||||
|> Map.take([:delete, :get, :head, :options, :patch, :post, :put, :trace])
|
||||
|> Map.values()
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Enum.uniq()
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -56,6 +56,42 @@ defp request_content_type(%{conn: conn}) do
|
|||
[conn: conn]
|
||||
end
|
||||
|
||||
defp json_response_and_validate_schema(conn, status \\ nil) do
|
||||
content_type =
|
||||
conn
|
||||
|> Plug.Conn.get_resp_header("content-type")
|
||||
|> List.first()
|
||||
|> String.split(";")
|
||||
|> List.first()
|
||||
|
||||
status = status || conn.status
|
||||
|
||||
%{private: %{open_api_spex: %{operation_id: op_id, operation_lookup: lookup, spec: spec}}} =
|
||||
conn
|
||||
|
||||
schema = lookup[op_id].responses[status].content[content_type].schema
|
||||
json = json_response(conn, status)
|
||||
|
||||
case OpenApiSpex.cast_value(json, schema, spec) do
|
||||
{:ok, _data} ->
|
||||
json
|
||||
|
||||
{:error, errors} ->
|
||||
errors =
|
||||
Enum.map(errors, fn error ->
|
||||
message = OpenApiSpex.Cast.Error.message(error)
|
||||
path = OpenApiSpex.Cast.Error.path_to_string(error)
|
||||
"#{message} at #{path}"
|
||||
end)
|
||||
|
||||
flunk(
|
||||
"Response does not conform to schema of #{op_id} operation: #{
|
||||
Enum.join(errors, "\n")
|
||||
}\n#{inspect(json)}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_federating_or_authenticated(conn, url, user) do
|
||||
initial_setting = Config.get([:instance, :federating])
|
||||
on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)
|
||||
|
|
|
@ -994,72 +994,6 @@ test "reverts emoji unreact on error" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "like an object" do
|
||||
test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
|
||||
Config.put([:instance, :federating], true)
|
||||
note_activity = insert(:note_activity)
|
||||
assert object_activity = Object.normalize(note_activity)
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
|
||||
assert called(Federator.publish(like_activity))
|
||||
end
|
||||
|
||||
test "returns exist activity if object already liked" do
|
||||
note_activity = insert(:note_activity)
|
||||
assert object_activity = Object.normalize(note_activity)
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
|
||||
|
||||
{:ok, like_activity_exist, _object} = ActivityPub.like(user, object_activity)
|
||||
assert like_activity == like_activity_exist
|
||||
end
|
||||
|
||||
test "reverts like activity on error" do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
|
||||
assert {:error, :reverted} = ActivityPub.like(user, object)
|
||||
end
|
||||
|
||||
assert Repo.aggregate(Activity, :count, :id) == 1
|
||||
assert Repo.get(Object, object.id) == object
|
||||
end
|
||||
|
||||
test "adds a like activity to the db" do
|
||||
note_activity = insert(:note_activity)
|
||||
assert object = Object.normalize(note_activity)
|
||||
|
||||
user = insert(:user)
|
||||
user_two = insert(:user)
|
||||
|
||||
{:ok, like_activity, object} = ActivityPub.like(user, object)
|
||||
|
||||
assert like_activity.data["actor"] == user.ap_id
|
||||
assert like_activity.data["type"] == "Like"
|
||||
assert like_activity.data["object"] == object.data["id"]
|
||||
assert like_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]]
|
||||
assert like_activity.data["context"] == object.data["context"]
|
||||
assert object.data["like_count"] == 1
|
||||
assert object.data["likes"] == [user.ap_id]
|
||||
|
||||
# Just return the original activity if the user already liked it.
|
||||
{:ok, same_like_activity, object} = ActivityPub.like(user, object)
|
||||
|
||||
assert like_activity == same_like_activity
|
||||
assert object.data["likes"] == [user.ap_id]
|
||||
assert object.data["like_count"] == 1
|
||||
|
||||
{:ok, _like_activity, object} = ActivityPub.like(user_two, object)
|
||||
assert object.data["like_count"] == 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "unliking" do
|
||||
test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
|
||||
Config.put([:instance, :federating], true)
|
||||
|
@ -1071,7 +1005,8 @@ test "adds a like activity to the db" do
|
|||
{:ok, object} = ActivityPub.unlike(user, object)
|
||||
refute called(Federator.publish())
|
||||
|
||||
{:ok, _like_activity, object} = ActivityPub.like(user, object)
|
||||
{:ok, _like_activity} = CommonAPI.favorite(user, note_activity.id)
|
||||
object = Object.get_by_id(object.id)
|
||||
assert object.data["like_count"] == 1
|
||||
|
||||
{:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
|
||||
|
@ -1082,10 +1017,10 @@ test "adds a like activity to the db" do
|
|||
|
||||
test "reverts unliking on error" do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, like_activity, object} = ActivityPub.like(user, object)
|
||||
{:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
|
||||
object = Object.normalize(note_activity)
|
||||
assert object.data["like_count"] == 1
|
||||
|
||||
with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
|
||||
|
@ -1106,7 +1041,9 @@ test "unliking a previously liked object" do
|
|||
{:ok, object} = ActivityPub.unlike(user, object)
|
||||
assert object.data["like_count"] == 0
|
||||
|
||||
{:ok, like_activity, object} = ActivityPub.like(user, object)
|
||||
{:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
|
||||
|
||||
object = Object.get_by_id(object.id)
|
||||
assert object.data["like_count"] == 1
|
||||
|
||||
{:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
|
||||
|
@ -1973,4 +1910,497 @@ test "old user must be in the new user's `also_known_as` list" do
|
|||
ActivityPub.move(old_user, new_user)
|
||||
end
|
||||
end
|
||||
|
||||
test "doesn't retrieve replies activities with exclude_replies" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "yeah"})
|
||||
|
||||
{:ok, _reply} =
|
||||
CommonAPI.post(user, %{"status" => "yeah", "in_reply_to_status_id" => activity.id})
|
||||
|
||||
[result] = ActivityPub.fetch_public_activities(%{"exclude_replies" => "true"})
|
||||
|
||||
assert result.id == activity.id
|
||||
|
||||
assert length(ActivityPub.fetch_public_activities()) == 2
|
||||
end
|
||||
|
||||
describe "replies filtering with public messages" do
|
||||
setup :public_messages
|
||||
|
||||
test "public timeline", %{users: %{u1: user}} do
|
||||
activities_ids =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", false)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert length(activities_ids) == 16
|
||||
end
|
||||
|
||||
test "public timeline with reply_visibility `following`", %{
|
||||
users: %{u1: user},
|
||||
u1: u1,
|
||||
u2: u2,
|
||||
u3: u3,
|
||||
u4: u4,
|
||||
activities: activities
|
||||
} do
|
||||
activities_ids =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", false)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("reply_visibility", "following")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert length(activities_ids) == 14
|
||||
|
||||
visible_ids =
|
||||
Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
|
||||
|
||||
assert Enum.all?(visible_ids, &(&1 in activities_ids))
|
||||
end
|
||||
|
||||
test "public timeline with reply_visibility `self`", %{
|
||||
users: %{u1: user},
|
||||
u1: u1,
|
||||
u2: u2,
|
||||
u3: u3,
|
||||
u4: u4,
|
||||
activities: activities
|
||||
} do
|
||||
activities_ids =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", false)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("reply_visibility", "self")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert length(activities_ids) == 10
|
||||
visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
|
||||
assert Enum.all?(visible_ids, &(&1 in activities_ids))
|
||||
end
|
||||
|
||||
test "home timeline", %{
|
||||
users: %{u1: user},
|
||||
activities: activities,
|
||||
u1: u1,
|
||||
u2: u2,
|
||||
u3: u3,
|
||||
u4: u4
|
||||
} do
|
||||
params =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|
||||
activities_ids =
|
||||
ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert length(activities_ids) == 13
|
||||
|
||||
visible_ids =
|
||||
Map.values(u1) ++
|
||||
Map.values(u3) ++
|
||||
[
|
||||
activities[:a1],
|
||||
activities[:a2],
|
||||
activities[:a4],
|
||||
u2[:r1],
|
||||
u2[:r3],
|
||||
u4[:r1],
|
||||
u4[:r2]
|
||||
]
|
||||
|
||||
assert Enum.all?(visible_ids, &(&1 in activities_ids))
|
||||
end
|
||||
|
||||
test "home timeline with reply_visibility `following`", %{
|
||||
users: %{u1: user},
|
||||
activities: activities,
|
||||
u1: u1,
|
||||
u2: u2,
|
||||
u3: u3,
|
||||
u4: u4
|
||||
} do
|
||||
params =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("reply_visibility", "following")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|
||||
activities_ids =
|
||||
ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert length(activities_ids) == 11
|
||||
|
||||
visible_ids =
|
||||
Map.values(u1) ++
|
||||
[
|
||||
activities[:a1],
|
||||
activities[:a2],
|
||||
activities[:a4],
|
||||
u2[:r1],
|
||||
u2[:r3],
|
||||
u3[:r1],
|
||||
u4[:r1],
|
||||
u4[:r2]
|
||||
]
|
||||
|
||||
assert Enum.all?(visible_ids, &(&1 in activities_ids))
|
||||
end
|
||||
|
||||
test "home timeline with reply_visibility `self`", %{
|
||||
users: %{u1: user},
|
||||
activities: activities,
|
||||
u1: u1,
|
||||
u2: u2,
|
||||
u3: u3,
|
||||
u4: u4
|
||||
} do
|
||||
params =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("reply_visibility", "self")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|
||||
activities_ids =
|
||||
ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert length(activities_ids) == 9
|
||||
|
||||
visible_ids =
|
||||
Map.values(u1) ++
|
||||
[
|
||||
activities[:a1],
|
||||
activities[:a2],
|
||||
activities[:a4],
|
||||
u2[:r1],
|
||||
u3[:r1],
|
||||
u4[:r1]
|
||||
]
|
||||
|
||||
assert Enum.all?(visible_ids, &(&1 in activities_ids))
|
||||
end
|
||||
end
|
||||
|
||||
describe "replies filtering with private messages" do
|
||||
setup :private_messages
|
||||
|
||||
test "public timeline", %{users: %{u1: user}} do
|
||||
activities_ids =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", false)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert activities_ids == []
|
||||
end
|
||||
|
||||
test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
|
||||
activities_ids =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", false)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("reply_visibility", "following")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert activities_ids == []
|
||||
end
|
||||
|
||||
test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
|
||||
activities_ids =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", false)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("reply_visibility", "self")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert activities_ids == []
|
||||
end
|
||||
|
||||
test "home timeline", %{users: %{u1: user}} do
|
||||
params =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|
||||
activities_ids =
|
||||
ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert length(activities_ids) == 12
|
||||
end
|
||||
|
||||
test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
|
||||
params =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("reply_visibility", "following")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|
||||
activities_ids =
|
||||
ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert length(activities_ids) == 12
|
||||
end
|
||||
|
||||
test "home timeline with default reply_visibility `self`", %{
|
||||
users: %{u1: user},
|
||||
activities: activities,
|
||||
u1: u1,
|
||||
u2: u2,
|
||||
u3: u3,
|
||||
u4: u4
|
||||
} do
|
||||
params =
|
||||
%{}
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("reply_visibility", "self")
|
||||
|> Map.put("reply_filtering_user", user)
|
||||
|
||||
activities_ids =
|
||||
ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
assert length(activities_ids) == 10
|
||||
|
||||
visible_ids =
|
||||
Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
|
||||
|
||||
assert Enum.all?(visible_ids, &(&1 in activities_ids))
|
||||
end
|
||||
end
|
||||
|
||||
defp public_messages(_) do
|
||||
[u1, u2, u3, u4] = insert_list(4, :user)
|
||||
{:ok, u1} = User.follow(u1, u2)
|
||||
{:ok, u2} = User.follow(u2, u1)
|
||||
{:ok, u1} = User.follow(u1, u4)
|
||||
{:ok, u4} = User.follow(u4, u1)
|
||||
|
||||
{:ok, u2} = User.follow(u2, u3)
|
||||
{:ok, u3} = User.follow(u3, u2)
|
||||
|
||||
{:ok, a1} = CommonAPI.post(u1, %{"status" => "Status"})
|
||||
|
||||
{:ok, r1_1} =
|
||||
CommonAPI.post(u2, %{
|
||||
"status" => "@#{u1.nickname} reply from u2 to u1",
|
||||
"in_reply_to_status_id" => a1.id
|
||||
})
|
||||
|
||||
{:ok, r1_2} =
|
||||
CommonAPI.post(u3, %{
|
||||
"status" => "@#{u1.nickname} reply from u3 to u1",
|
||||
"in_reply_to_status_id" => a1.id
|
||||
})
|
||||
|
||||
{:ok, r1_3} =
|
||||
CommonAPI.post(u4, %{
|
||||
"status" => "@#{u1.nickname} reply from u4 to u1",
|
||||
"in_reply_to_status_id" => a1.id
|
||||
})
|
||||
|
||||
{:ok, a2} = CommonAPI.post(u2, %{"status" => "Status"})
|
||||
|
||||
{:ok, r2_1} =
|
||||
CommonAPI.post(u1, %{
|
||||
"status" => "@#{u2.nickname} reply from u1 to u2",
|
||||
"in_reply_to_status_id" => a2.id
|
||||
})
|
||||
|
||||
{:ok, r2_2} =
|
||||
CommonAPI.post(u3, %{
|
||||
"status" => "@#{u2.nickname} reply from u3 to u2",
|
||||
"in_reply_to_status_id" => a2.id
|
||||
})
|
||||
|
||||
{:ok, r2_3} =
|
||||
CommonAPI.post(u4, %{
|
||||
"status" => "@#{u2.nickname} reply from u4 to u2",
|
||||
"in_reply_to_status_id" => a2.id
|
||||
})
|
||||
|
||||
{:ok, a3} = CommonAPI.post(u3, %{"status" => "Status"})
|
||||
|
||||
{:ok, r3_1} =
|
||||
CommonAPI.post(u1, %{
|
||||
"status" => "@#{u3.nickname} reply from u1 to u3",
|
||||
"in_reply_to_status_id" => a3.id
|
||||
})
|
||||
|
||||
{:ok, r3_2} =
|
||||
CommonAPI.post(u2, %{
|
||||
"status" => "@#{u3.nickname} reply from u2 to u3",
|
||||
"in_reply_to_status_id" => a3.id
|
||||
})
|
||||
|
||||
{:ok, r3_3} =
|
||||
CommonAPI.post(u4, %{
|
||||
"status" => "@#{u3.nickname} reply from u4 to u3",
|
||||
"in_reply_to_status_id" => a3.id
|
||||
})
|
||||
|
||||
{:ok, a4} = CommonAPI.post(u4, %{"status" => "Status"})
|
||||
|
||||
{:ok, r4_1} =
|
||||
CommonAPI.post(u1, %{
|
||||
"status" => "@#{u4.nickname} reply from u1 to u4",
|
||||
"in_reply_to_status_id" => a4.id
|
||||
})
|
||||
|
||||
{:ok, r4_2} =
|
||||
CommonAPI.post(u2, %{
|
||||
"status" => "@#{u4.nickname} reply from u2 to u4",
|
||||
"in_reply_to_status_id" => a4.id
|
||||
})
|
||||
|
||||
{:ok, r4_3} =
|
||||
CommonAPI.post(u3, %{
|
||||
"status" => "@#{u4.nickname} reply from u3 to u4",
|
||||
"in_reply_to_status_id" => a4.id
|
||||
})
|
||||
|
||||
{:ok,
|
||||
users: %{u1: u1, u2: u2, u3: u3, u4: u4},
|
||||
activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
|
||||
u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
|
||||
u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
|
||||
u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
|
||||
u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
|
||||
end
|
||||
|
||||
defp private_messages(_) do
|
||||
[u1, u2, u3, u4] = insert_list(4, :user)
|
||||
{:ok, u1} = User.follow(u1, u2)
|
||||
{:ok, u2} = User.follow(u2, u1)
|
||||
{:ok, u1} = User.follow(u1, u3)
|
||||
{:ok, u3} = User.follow(u3, u1)
|
||||
{:ok, u1} = User.follow(u1, u4)
|
||||
{:ok, u4} = User.follow(u4, u1)
|
||||
|
||||
{:ok, u2} = User.follow(u2, u3)
|
||||
{:ok, u3} = User.follow(u3, u2)
|
||||
|
||||
{:ok, a1} = CommonAPI.post(u1, %{"status" => "Status", "visibility" => "private"})
|
||||
|
||||
{:ok, r1_1} =
|
||||
CommonAPI.post(u2, %{
|
||||
"status" => "@#{u1.nickname} reply from u2 to u1",
|
||||
"in_reply_to_status_id" => a1.id,
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
{:ok, r1_2} =
|
||||
CommonAPI.post(u3, %{
|
||||
"status" => "@#{u1.nickname} reply from u3 to u1",
|
||||
"in_reply_to_status_id" => a1.id,
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
{:ok, r1_3} =
|
||||
CommonAPI.post(u4, %{
|
||||
"status" => "@#{u1.nickname} reply from u4 to u1",
|
||||
"in_reply_to_status_id" => a1.id,
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
{:ok, a2} = CommonAPI.post(u2, %{"status" => "Status", "visibility" => "private"})
|
||||
|
||||
{:ok, r2_1} =
|
||||
CommonAPI.post(u1, %{
|
||||
"status" => "@#{u2.nickname} reply from u1 to u2",
|
||||
"in_reply_to_status_id" => a2.id,
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
{:ok, r2_2} =
|
||||
CommonAPI.post(u3, %{
|
||||
"status" => "@#{u2.nickname} reply from u3 to u2",
|
||||
"in_reply_to_status_id" => a2.id,
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
{:ok, a3} = CommonAPI.post(u3, %{"status" => "Status", "visibility" => "private"})
|
||||
|
||||
{:ok, r3_1} =
|
||||
CommonAPI.post(u1, %{
|
||||
"status" => "@#{u3.nickname} reply from u1 to u3",
|
||||
"in_reply_to_status_id" => a3.id,
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
{:ok, r3_2} =
|
||||
CommonAPI.post(u2, %{
|
||||
"status" => "@#{u3.nickname} reply from u2 to u3",
|
||||
"in_reply_to_status_id" => a3.id,
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
{:ok, a4} = CommonAPI.post(u4, %{"status" => "Status", "visibility" => "private"})
|
||||
|
||||
{:ok, r4_1} =
|
||||
CommonAPI.post(u1, %{
|
||||
"status" => "@#{u4.nickname} reply from u1 to u4",
|
||||
"in_reply_to_status_id" => a4.id,
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
{:ok,
|
||||
users: %{u1: u1, u2: u2, u3: u3, u4: u4},
|
||||
activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
|
||||
u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
|
||||
u2: %{r1: r2_1.id, r2: r2_2.id},
|
||||
u3: %{r1: r3_1.id, r2: r3_2.id},
|
||||
u4: %{r1: r4_1.id}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -224,8 +224,7 @@ test "fetches only Create activities" do
|
|||
|
||||
object = Object.normalize(activity)
|
||||
{:ok, [vote], object} = CommonAPI.vote(other_user, object, [0])
|
||||
vote_object = Object.normalize(vote)
|
||||
{:ok, _activity, _object} = ActivityPub.like(user, vote_object)
|
||||
{:ok, _activity} = CommonAPI.favorite(user, activity.id)
|
||||
[fetched_vote] = Utils.get_existing_votes(other_user.ap_id, object)
|
||||
assert fetched_vote.id == vote.id
|
||||
end
|
||||
|
@ -346,7 +345,7 @@ test "fetches existing like" do
|
|||
|
||||
user = insert(:user)
|
||||
refute Utils.get_existing_like(user.ap_id, object)
|
||||
{:ok, like_activity, _object} = ActivityPub.like(user, object)
|
||||
{:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
|
||||
|
||||
assert ^like_activity = Utils.get_existing_like(user.ap_id, object)
|
||||
end
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
# 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.AppOperationTest do
|
||||
use Pleroma.Web.ConnCase, async: true
|
||||
|
||||
alias Pleroma.Web.ApiSpec
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
|
||||
|
||||
import OpenApiSpex.TestAssertions
|
||||
import Pleroma.Factory
|
||||
|
||||
test "AppCreateRequest example matches schema" do
|
||||
api_spec = ApiSpec.spec()
|
||||
schema = AppCreateRequest.schema()
|
||||
assert_schema(schema.example, "AppCreateRequest", api_spec)
|
||||
end
|
||||
|
||||
test "AppCreateResponse example matches schema" do
|
||||
api_spec = ApiSpec.spec()
|
||||
schema = AppCreateResponse.schema()
|
||||
assert_schema(schema.example, "AppCreateResponse", api_spec)
|
||||
end
|
||||
|
||||
test "AppController produces a AppCreateResponse", %{conn: conn} do
|
||||
api_spec = ApiSpec.spec()
|
||||
app_attrs = build(:oauth_app)
|
||||
|
||||
json =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post(
|
||||
"/api/v1/apps",
|
||||
Jason.encode!(%{
|
||||
client_name: app_attrs.client_name,
|
||||
redirect_uris: app_attrs.redirect_uris
|
||||
})
|
||||
)
|
||||
|> json_response(200)
|
||||
|
||||
assert_schema(json, "AppCreateResponse", api_spec)
|
||||
end
|
||||
end
|
43
test/web/api_spec/schema_examples_test.exs
Normal file
43
test/web/api_spec/schema_examples_test.exs
Normal file
|
@ -0,0 +1,43 @@
|
|||
# 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.SchemaExamplesTest do
|
||||
use ExUnit.Case, async: true
|
||||
import Pleroma.Tests.ApiSpecHelpers
|
||||
|
||||
@content_type "application/json"
|
||||
|
||||
for operation <- api_operations() do
|
||||
describe operation.operationId <> " Request Body" do
|
||||
if operation.requestBody do
|
||||
@media_type operation.requestBody.content[@content_type]
|
||||
@schema resolve_schema(@media_type.schema)
|
||||
|
||||
if @media_type.example do
|
||||
test "request body media type example matches schema" do
|
||||
assert_schema(@media_type.example, @schema)
|
||||
end
|
||||
end
|
||||
|
||||
if @schema.example do
|
||||
test "request body schema example matches schema" do
|
||||
assert_schema(@schema.example, @schema)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for {status, response} <- operation.responses do
|
||||
describe "#{operation.operationId} - #{status} Response" do
|
||||
@schema resolve_schema(response.content[@content_type].schema)
|
||||
|
||||
if @schema.example do
|
||||
test "example matches schema" do
|
||||
assert_schema(@schema.example, @schema)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -21,6 +21,60 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||
setup do: clear_config([:instance, :limit])
|
||||
setup do: clear_config([:instance, :max_pinned_statuses])
|
||||
|
||||
test "favoriting race condition" do
|
||||
user = insert(:user)
|
||||
users_serial = insert_list(10, :user)
|
||||
users = insert_list(10, :user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "."})
|
||||
|
||||
users_serial
|
||||
|> Enum.map(fn user ->
|
||||
CommonAPI.favorite(user, activity.id)
|
||||
end)
|
||||
|
||||
object = Object.get_by_ap_id(activity.data["object"])
|
||||
assert object.data["like_count"] == 10
|
||||
|
||||
users
|
||||
|> Enum.map(fn user ->
|
||||
Task.async(fn ->
|
||||
CommonAPI.favorite(user, activity.id)
|
||||
end)
|
||||
end)
|
||||
|> Enum.map(&Task.await/1)
|
||||
|
||||
object = Object.get_by_ap_id(activity.data["object"])
|
||||
assert object.data["like_count"] == 20
|
||||
end
|
||||
|
||||
test "repeating race condition" do
|
||||
user = insert(:user)
|
||||
users_serial = insert_list(10, :user)
|
||||
users = insert_list(10, :user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "."})
|
||||
|
||||
users_serial
|
||||
|> Enum.map(fn user ->
|
||||
CommonAPI.repeat(activity.id, user)
|
||||
end)
|
||||
|
||||
object = Object.get_by_ap_id(activity.data["object"])
|
||||
assert object.data["announcement_count"] == 10
|
||||
|
||||
users
|
||||
|> Enum.map(fn user ->
|
||||
Task.async(fn ->
|
||||
CommonAPI.repeat(activity.id, user)
|
||||
end)
|
||||
end)
|
||||
|> Enum.map(&Task.await/1)
|
||||
|
||||
object = Object.get_by_ap_id(activity.data["object"])
|
||||
assert object.data["announcement_count"] == 20
|
||||
end
|
||||
|
||||
test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
|
||||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
|
||||
|
@ -256,6 +310,16 @@ test "repeating a status" do
|
|||
{:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
|
||||
end
|
||||
|
||||
test "can't repeat a repeat" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||
|
||||
{:ok, %Activity{} = announce, _} = CommonAPI.repeat(activity.id, other_user)
|
||||
|
||||
refute match?({:ok, %Activity{}, _}, CommonAPI.repeat(announce.id, user))
|
||||
end
|
||||
|
||||
test "repeating a status privately" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
@ -285,8 +349,8 @@ test "retweeting a status twice returns the status" do
|
|||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||
{:ok, %Activity{} = activity, object} = CommonAPI.repeat(activity.id, user)
|
||||
{:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)
|
||||
{:ok, %Activity{} = announce, object} = CommonAPI.repeat(activity.id, user)
|
||||
{:ok, ^announce, ^object} = CommonAPI.repeat(activity.id, user)
|
||||
end
|
||||
|
||||
test "favoriting a status twice returns ok, but without the like activity" do
|
||||
|
@ -360,7 +424,9 @@ test "unpin status", %{user: user, activity: activity} do
|
|||
|
||||
user = refresh_record(user)
|
||||
|
||||
assert {:ok, ^activity} = CommonAPI.unpin(activity.id, user)
|
||||
id = activity.id
|
||||
|
||||
assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
|
||||
|
||||
user = refresh_record(user)
|
||||
|
||||
|
|
|
@ -335,26 +335,6 @@ test "for direct posts, a reply" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "get_by_id_or_ap_id/1" do
|
||||
test "get activity by id" do
|
||||
activity = insert(:note_activity)
|
||||
%Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.id)
|
||||
assert note.id == activity.id
|
||||
end
|
||||
|
||||
test "get activity by ap_id" do
|
||||
activity = insert(:note_activity)
|
||||
%Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.data["object"])
|
||||
assert note.id == activity.id
|
||||
end
|
||||
|
||||
test "get activity by object when type isn't `Create` " do
|
||||
activity = insert(:like_activity)
|
||||
%Pleroma.Activity{} = like = Utils.get_by_id_or_ap_id(activity.id)
|
||||
assert like.data["object"] == activity.data["object"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "to_master_date/1" do
|
||||
test "removes microseconds from date (NaiveDateTime)" do
|
||||
assert Utils.to_masto_date(~N[2015-01-23 23:50:07.123]) == "2015-01-23T23:50:07.000Z"
|
||||
|
|
|
@ -27,7 +27,7 @@ test "apps/verify_credentials", %{conn: conn} do
|
|||
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
|
||||
}
|
||||
|
||||
assert expected == json_response(conn, 200)
|
||||
assert expected == json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "creates an oauth app", %{conn: conn} do
|
||||
|
@ -55,6 +55,6 @@ test "creates an oauth app", %{conn: conn} do
|
|||
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
|
||||
}
|
||||
|
||||
assert expected == json_response(conn, 200)
|
||||
assert expected == json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,15 +5,13 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.CustomEmojiControllerTest do
|
||||
use Pleroma.Web.ConnCase, async: true
|
||||
alias Pleroma.Web.ApiSpec
|
||||
alias Pleroma.Web.ApiSpec.Schemas.CustomEmoji
|
||||
alias Pleroma.Web.ApiSpec.Schemas.CustomEmojisResponse
|
||||
import OpenApiSpex.TestAssertions
|
||||
|
||||
test "with tags", %{conn: conn} do
|
||||
assert resp =
|
||||
conn
|
||||
|> get("/api/v1/custom_emojis")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [emoji | _body] = resp
|
||||
assert Map.has_key?(emoji, "shortcode")
|
||||
|
@ -23,19 +21,6 @@ test "with tags", %{conn: conn} do
|
|||
assert Map.has_key?(emoji, "category")
|
||||
assert Map.has_key?(emoji, "url")
|
||||
assert Map.has_key?(emoji, "visible_in_picker")
|
||||
assert_schema(resp, "CustomEmojisResponse", ApiSpec.spec())
|
||||
assert_schema(emoji, "CustomEmoji", ApiSpec.spec())
|
||||
end
|
||||
|
||||
test "CustomEmoji example matches schema" do
|
||||
api_spec = ApiSpec.spec()
|
||||
schema = CustomEmoji.schema()
|
||||
assert_schema(schema.example, "CustomEmoji", api_spec)
|
||||
end
|
||||
|
||||
test "CustomEmojisResponse example matches schema" do
|
||||
api_spec = ApiSpec.spec()
|
||||
schema = CustomEmojisResponse.schema()
|
||||
assert_schema(schema.example, "CustomEmojisResponse", api_spec)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,11 +6,8 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do
|
|||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ApiSpec
|
||||
alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
|
||||
|
||||
import Pleroma.Factory
|
||||
import OpenApiSpex.TestAssertions
|
||||
|
||||
test "blocking / unblocking a domain" do
|
||||
%{user: user, conn: conn} = oauth_access(["write:blocks"])
|
||||
|
@ -21,7 +18,7 @@ test "blocking / unblocking a domain" do
|
|||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
|
||||
|
||||
assert %{} = json_response(ret_conn, 200)
|
||||
assert %{} == json_response_and_validate_schema(ret_conn, 200)
|
||||
user = User.get_cached_by_ap_id(user.ap_id)
|
||||
assert User.blocks?(user, other_user)
|
||||
|
||||
|
@ -30,7 +27,7 @@ test "blocking / unblocking a domain" do
|
|||
|> put_req_header("content-type", "application/json")
|
||||
|> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
|
||||
|
||||
assert %{} = json_response(ret_conn, 200)
|
||||
assert %{} == json_response_and_validate_schema(ret_conn, 200)
|
||||
user = User.get_cached_by_ap_id(user.ap_id)
|
||||
refute User.blocks?(user, other_user)
|
||||
end
|
||||
|
@ -41,21 +38,10 @@ test "getting a list of domain blocks" do
|
|||
{:ok, user} = User.block_domain(user, "bad.site")
|
||||
{:ok, user} = User.block_domain(user, "even.worse.site")
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/domain_blocks")
|
||||
|
||||
domain_blocks = json_response(conn, 200)
|
||||
|
||||
assert "bad.site" in domain_blocks
|
||||
assert "even.worse.site" in domain_blocks
|
||||
assert_schema(domain_blocks, "DomainBlocksResponse", ApiSpec.spec())
|
||||
end
|
||||
|
||||
test "DomainBlocksResponse example matches schema" do
|
||||
api_spec = ApiSpec.spec()
|
||||
schema = DomainBlocksResponse.schema()
|
||||
assert_schema(schema.example, "DomainBlocksResponse", api_spec)
|
||||
assert ["even.worse.site", "bad.site"] ==
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/domain_blocks")
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -302,6 +302,17 @@ test "creates a scheduled activity", %{conn: conn} do
|
|||
assert [] == Repo.all(Activity)
|
||||
end
|
||||
|
||||
test "ignores nil values", %{conn: conn} do
|
||||
conn =
|
||||
post(conn, "/api/v1/statuses", %{
|
||||
"status" => "not scheduled",
|
||||
"scheduled_at" => nil
|
||||
})
|
||||
|
||||
assert result = json_response(conn, 200)
|
||||
assert Activity.get_by_id(result["id"])
|
||||
end
|
||||
|
||||
test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
|
||||
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||
|
||||
|
|
Loading…
Reference in a new issue