forked from AkkomaGang/akkoma
Merge branch 'develop' into issue/1276-2
This commit is contained in:
commit
fe7a0d660e
87 changed files with 1413 additions and 604 deletions
|
@ -15,15 +15,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- **Breaking:** removed `with_move` parameter from notifications timeline.
|
||||
|
||||
### Added
|
||||
- Instance: Extend `/api/v1/instance` with Pleroma-specific information.
|
||||
- NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
|
||||
- NodeInfo: `pleroma_emoji_reactions` to the `features` list.
|
||||
- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
|
||||
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
|
||||
- Mix task to create trusted OAuth App.
|
||||
- Notifications: Added `follow_request` notification type (configurable, see `[:notifications, :enable_follow_request_notifications]` setting).
|
||||
- Notifications: Added `follow_request` notification type.
|
||||
- Added `:reject_deletes` group to SimplePolicy
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
- Mastodon API: Extended `/api/v1/instance`.
|
||||
- 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
|
||||
|
@ -37,6 +39,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Filtering of push notifications on activities from blocked domains
|
||||
|
||||
## [unreleased-patch]
|
||||
### Security
|
||||
- Disallow re-registration of previously deleted users, which allowed viewing direct messages addressed to them
|
||||
- Mastodon API: Fix `POST /api/v1/follow_requests/:id/authorize` allowing to force a follow from a local user even if they didn't request to follow
|
||||
|
||||
### Fixed
|
||||
- Logger configuration through AdminFE
|
||||
- HTTP Basic Authentication permissions issue
|
||||
|
|
|
@ -562,8 +562,6 @@
|
|||
inactivity_threshold: 7
|
||||
}
|
||||
|
||||
config :pleroma, :notifications, enable_follow_request_notifications: false
|
||||
|
||||
config :pleroma, :oauth2,
|
||||
token_expires_in: 600,
|
||||
issue_new_refresh_token: true,
|
||||
|
|
|
@ -2273,20 +2273,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: :notifications,
|
||||
type: :group,
|
||||
description: "Notification settings",
|
||||
children: [
|
||||
%{
|
||||
key: :enable_follow_request_notifications,
|
||||
type: :boolean,
|
||||
description:
|
||||
"Enables notifications on new follow requests (causes issues with older PleromaFE versions)."
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Emails.UserEmail,
|
||||
|
|
|
@ -202,8 +202,22 @@ Has theses additional parameters (which are the same as in Pleroma-API):
|
|||
- `bio`: optional
|
||||
- `captcha_solution`: optional, contains provider-specific captcha solution,
|
||||
- `captcha_token`: optional, contains provider-specific captcha token
|
||||
- `captcha_answer_data`: optional, contains provider-specific captcha data
|
||||
- `token`: invite token required when the registrations aren't public.
|
||||
|
||||
## Instance
|
||||
|
||||
`GET /api/v1/instance` has additional fields
|
||||
|
||||
- `max_toot_chars`: The maximum characters per post
|
||||
- `poll_limits`: The limits of polls
|
||||
- `upload_limit`: The maximum upload file size
|
||||
- `avatar_upload_limit`: The same for avatars
|
||||
- `background_upload_limit`: The same for backgrounds
|
||||
- `banner_upload_limit`: The same for banners
|
||||
- `pleroma.metadata.features`: A list of supported features
|
||||
- `pleroma.metadata.federation`: The federation restrictions of this instance
|
||||
- `vapid_public_key`: The public key needed for push messages
|
||||
|
||||
## Markers
|
||||
|
||||
|
|
|
@ -49,11 +49,11 @@ Feel free to contact us to be added to this list!
|
|||
- Platforms: Android
|
||||
- Features: Streaming Ready
|
||||
|
||||
### Roma
|
||||
- Homepage: <https://www.pleroma.com/#mobileApps>
|
||||
- Source Code: [iOS](https://github.com/roma-apps/roma-ios), [Android](https://github.com/roma-apps/roma-android)
|
||||
### Fedi
|
||||
- Homepage: <https://www.fediapp.com/>
|
||||
- Source Code: Proprietary, but free
|
||||
- Platforms: iOS, Android
|
||||
- Features: No Streaming
|
||||
- Features: Pleroma-specific features like Reactions
|
||||
|
||||
### Tusky
|
||||
- Homepage: <https://tuskyapp.github.io/>
|
||||
|
|
|
@ -73,7 +73,6 @@ def start(_type, _args) do
|
|||
Pleroma.Repo,
|
||||
Config.TransferTask,
|
||||
Pleroma.Emoji,
|
||||
Pleroma.Captcha,
|
||||
Pleroma.Plugs.RateLimiter.Supervisor
|
||||
] ++
|
||||
cachex_children() ++
|
||||
|
|
|
@ -3,53 +3,22 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Captcha do
|
||||
import Pleroma.Web.Gettext
|
||||
|
||||
alias Calendar.DateTime
|
||||
alias Plug.Crypto.KeyGenerator
|
||||
alias Plug.Crypto.MessageEncryptor
|
||||
|
||||
use GenServer
|
||||
|
||||
@doc false
|
||||
def start_link(_) do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def init(_) do
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ask the configured captcha service for a new captcha
|
||||
"""
|
||||
def new do
|
||||
GenServer.call(__MODULE__, :new)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Ask the configured captcha service to validate the captcha
|
||||
"""
|
||||
def validate(token, captcha, answer_data) do
|
||||
GenServer.call(__MODULE__, {:validate, token, captcha, answer_data})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(:new, _from, state) do
|
||||
enabled = Pleroma.Config.get([__MODULE__, :enabled])
|
||||
|
||||
if !enabled do
|
||||
{:reply, %{type: :none}, state}
|
||||
if not enabled?() do
|
||||
%{type: :none}
|
||||
else
|
||||
new_captcha = method().new()
|
||||
|
||||
secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base])
|
||||
|
||||
# This make salt a little different for two keys
|
||||
token = new_captcha[:token]
|
||||
secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt")
|
||||
sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign")
|
||||
{secret, sign_secret} = secret_pair(new_captcha[:token])
|
||||
|
||||
# Basically copy what Phoenix.Token does here, add the time to
|
||||
# the actual data and make it a binary to then encrypt it
|
||||
encrypted_captcha_answer =
|
||||
|
@ -60,55 +29,73 @@ def handle_call(:new, _from, state) do
|
|||
|> :erlang.term_to_binary()
|
||||
|> MessageEncryptor.encrypt(secret, sign_secret)
|
||||
|
||||
{
|
||||
:reply,
|
||||
# Replace the answer with the encrypted answer
|
||||
%{new_captcha | answer_data: encrypted_captcha_answer},
|
||||
state
|
||||
}
|
||||
# Replace the answer with the encrypted answer
|
||||
%{new_captcha | answer_data: encrypted_captcha_answer}
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call({:validate, token, captcha, answer_data}, _from, state) do
|
||||
@doc """
|
||||
Ask the configured captcha service to validate the captcha
|
||||
"""
|
||||
def validate(token, captcha, answer_data) do
|
||||
with {:ok, %{at: at, answer_data: answer_md5}} <- validate_answer_data(token, answer_data),
|
||||
:ok <- validate_expiration(at),
|
||||
:ok <- validate_usage(token),
|
||||
:ok <- method().validate(token, captcha, answer_md5),
|
||||
{:ok, _} <- mark_captcha_as_used(token) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled], false)
|
||||
|
||||
defp seconds_valid, do: Pleroma.Config.get!([__MODULE__, :seconds_valid])
|
||||
|
||||
defp secret_pair(token) do
|
||||
secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base])
|
||||
secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt")
|
||||
sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign")
|
||||
|
||||
{secret, sign_secret}
|
||||
end
|
||||
|
||||
defp validate_answer_data(token, answer_data) do
|
||||
{secret, sign_secret} = secret_pair(token)
|
||||
|
||||
with false <- is_nil(answer_data),
|
||||
{:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
|
||||
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
|
||||
{:ok, %{at: at, answer_data: answer_md5}}
|
||||
else
|
||||
_ -> {:error, :invalid_answer_data}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_expiration(created_at) do
|
||||
# If the time found is less than (current_time-seconds_valid) then the time has already passed
|
||||
# Later we check that the time found is more than the presumed invalidatation time, that means
|
||||
# that the data is still valid and the captcha can be checked
|
||||
seconds_valid = Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid])
|
||||
valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid)
|
||||
|
||||
result =
|
||||
with false <- is_nil(answer_data),
|
||||
{:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
|
||||
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
|
||||
try do
|
||||
if DateTime.before?(at, valid_if_after),
|
||||
do: throw({:error, dgettext("errors", "CAPTCHA expired")})
|
||||
valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid())
|
||||
|
||||
if not is_nil(Cachex.get!(:used_captcha_cache, token)),
|
||||
do: throw({:error, dgettext("errors", "CAPTCHA already used")})
|
||||
if DateTime.before?(created_at, valid_if_after) do
|
||||
{:error, :expired}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
res = method().validate(token, captcha, answer_md5)
|
||||
# Throw if an error occurs
|
||||
if res != :ok, do: throw(res)
|
||||
defp validate_usage(token) do
|
||||
if is_nil(Cachex.get!(:used_captcha_cache, token)) do
|
||||
:ok
|
||||
else
|
||||
{:error, :already_used}
|
||||
end
|
||||
end
|
||||
|
||||
# Mark this captcha as used
|
||||
{:ok, _} =
|
||||
Cachex.put(:used_captcha_cache, token, true, ttl: :timer.seconds(seconds_valid))
|
||||
|
||||
:ok
|
||||
catch
|
||||
:throw, e -> e
|
||||
end
|
||||
else
|
||||
_ -> {:error, dgettext("errors", "Invalid answer data")}
|
||||
end
|
||||
|
||||
{:reply, result, state}
|
||||
defp mark_captcha_as_used(token) do
|
||||
ttl = seconds_valid() |> :timer.seconds()
|
||||
Cachex.put(:used_captcha_cache, token, true, ttl: ttl)
|
||||
end
|
||||
|
||||
defp method, do: Pleroma.Config.get!([__MODULE__, :method])
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Captcha.Kocaptcha do
|
||||
import Pleroma.Web.Gettext
|
||||
alias Pleroma.Captcha.Service
|
||||
@behaviour Service
|
||||
|
||||
|
@ -13,7 +12,7 @@ def new do
|
|||
|
||||
case Tesla.get(endpoint <> "/new") do
|
||||
{:error, _} ->
|
||||
%{error: dgettext("errors", "Kocaptcha service unavailable")}
|
||||
%{error: :kocaptcha_service_unavailable}
|
||||
|
||||
{:ok, res} ->
|
||||
json_resp = Jason.decode!(res.body)
|
||||
|
@ -33,6 +32,6 @@ def validate(_token, captcha, answer_data) do
|
|||
if not is_nil(captcha) and
|
||||
:crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data),
|
||||
do: :ok,
|
||||
else: {:error, dgettext("errors", "Invalid CAPTCHA")}
|
||||
else: {:error, :invalid}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Captcha.Native do
|
||||
import Pleroma.Web.Gettext
|
||||
alias Pleroma.Captcha.Service
|
||||
@behaviour Service
|
||||
|
||||
|
@ -11,7 +10,7 @@ defmodule Pleroma.Captcha.Native do
|
|||
def new do
|
||||
case Captcha.get() do
|
||||
:error ->
|
||||
%{error: dgettext("errors", "Captcha error")}
|
||||
%{error: :captcha_error}
|
||||
|
||||
{:ok, answer_data, img_binary} ->
|
||||
%{
|
||||
|
@ -25,7 +24,7 @@ def new do
|
|||
|
||||
@impl Service
|
||||
def validate(_token, captcha, captcha) when not is_nil(captcha), do: :ok
|
||||
def validate(_token, _captcha, _answer), do: {:error, dgettext("errors", "Invalid CAPTCHA")}
|
||||
def validate(_token, _captcha, _answer), do: {:error, :invalid}
|
||||
|
||||
defp token do
|
||||
10
|
||||
|
|
|
@ -20,4 +20,9 @@ defmodule Pleroma.Constants do
|
|||
"deleted_activity_id"
|
||||
]
|
||||
)
|
||||
|
||||
const(static_only_files,
|
||||
do:
|
||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
|
||||
)
|
||||
end
|
||||
|
|
|
@ -311,17 +311,8 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = act
|
|||
end
|
||||
end
|
||||
|
||||
def create_notifications(%Activity{data: %{"type" => "Follow"}} = activity) do
|
||||
if Pleroma.Config.get([:notifications, :enable_follow_request_notifications]) ||
|
||||
Activity.follow_accepted?(activity) do
|
||||
do_create_notifications(activity)
|
||||
else
|
||||
{:ok, []}
|
||||
end
|
||||
end
|
||||
|
||||
def create_notifications(%Activity{data: %{"type" => type}} = activity)
|
||||
when type in ["Like", "Announce", "Move", "EmojiReact"] do
|
||||
when type in ["Follow", "Like", "Announce", "Move", "EmojiReact"] do
|
||||
do_create_notifications(activity)
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.InstanceStatic do
|
||||
require Pleroma.Constants
|
||||
|
||||
@moduledoc """
|
||||
This is a shim to call `Plug.Static` but with runtime `from` configuration.
|
||||
|
||||
|
@ -21,9 +23,6 @@ def file_path(path) do
|
|||
end
|
||||
end
|
||||
|
||||
@only ~w(index.html robots.txt static emoji packs sounds images instance favicon.png sw.js
|
||||
sw-pleroma.js)
|
||||
|
||||
def init(opts) do
|
||||
opts
|
||||
|> Keyword.put(:from, "__unconfigured_instance_static_plug")
|
||||
|
@ -31,7 +30,7 @@ def init(opts) do
|
|||
|> Plug.Static.init()
|
||||
end
|
||||
|
||||
for only <- @only do
|
||||
for only <- Pleroma.Constants.static_only_files() do
|
||||
at = Plug.Router.Utils.split("/")
|
||||
|
||||
def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
|
||||
|
|
|
@ -13,8 +13,9 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
|||
def init(options), do: options
|
||||
|
||||
defp key_id_from_conn(conn) do
|
||||
with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn) do
|
||||
Signature.key_id_to_actor_id(key_id)
|
||||
with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn),
|
||||
{:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do
|
||||
ap_id
|
||||
else
|
||||
_ ->
|
||||
nil
|
||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Signature do
|
|||
alias Pleroma.Keys
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
|
||||
def key_id_to_actor_id(key_id) do
|
||||
uri =
|
||||
|
@ -21,12 +22,23 @@ def key_id_to_actor_id(key_id) do
|
|||
uri
|
||||
end
|
||||
|
||||
URI.to_string(uri)
|
||||
maybe_ap_id = URI.to_string(uri)
|
||||
|
||||
case Types.ObjectID.cast(maybe_ap_id) do
|
||||
{:ok, ap_id} ->
|
||||
{:ok, ap_id}
|
||||
|
||||
_ ->
|
||||
case Pleroma.Web.WebFinger.finger(maybe_ap_id) do
|
||||
%{"ap_id" => ap_id} -> {:ok, ap_id}
|
||||
_ -> {:error, maybe_ap_id}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_public_key(conn) do
|
||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||
actor_id <- key_id_to_actor_id(kid),
|
||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
|
@ -37,7 +49,7 @@ def fetch_public_key(conn) do
|
|||
|
||||
def refetch_public_key(conn) do
|
||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||
actor_id <- key_id_to_actor_id(kid),
|
||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
|
|
|
@ -1445,8 +1445,15 @@ def perform(:delete, %User{} = user) do
|
|||
end)
|
||||
|
||||
delete_user_activities(user)
|
||||
invalidate_cache(user)
|
||||
Repo.delete(user)
|
||||
|
||||
if user.local do
|
||||
user
|
||||
|> change(%{deactivated: true, email: nil})
|
||||
|> update_and_set_cache()
|
||||
else
|
||||
invalidate_cache(user)
|
||||
Repo.delete(user)
|
||||
end
|
||||
end
|
||||
|
||||
def perform(:deactivate_async, user, status), do: deactivate(user, status)
|
||||
|
|
|
@ -37,9 +37,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
[unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions
|
||||
)
|
||||
|
||||
# Note: :following and :followers must be served even without authentication (as via :api)
|
||||
plug(
|
||||
EnsureAuthenticatedPlug
|
||||
when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :following, :followers]
|
||||
when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
A module to handle coding from internal to wire ActivityPub and back.
|
||||
"""
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.EarmarkRenderer
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
|
@ -43,6 +44,7 @@ def fix_object(object, options \\ []) do
|
|||
|> fix_addressing
|
||||
|> fix_summary
|
||||
|> fix_type(options)
|
||||
|> fix_content
|
||||
end
|
||||
|
||||
def fix_summary(%{"summary" => nil} = object) do
|
||||
|
@ -357,6 +359,18 @@ def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
|
|||
|
||||
def fix_type(object, _), do: object
|
||||
|
||||
defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object)
|
||||
when is_binary(content) do
|
||||
html_content =
|
||||
content
|
||||
|> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
|
||||
|> Pleroma.HTML.filter_tags()
|
||||
|
||||
Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"})
|
||||
end
|
||||
|
||||
defp fix_content(object), do: object
|
||||
|
||||
defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
|
||||
with true <- id =~ "follows",
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follower_id),
|
||||
|
@ -1207,18 +1221,24 @@ def add_attributed_to(object) do
|
|||
|
||||
def prepare_attachments(object) do
|
||||
attachments =
|
||||
(object["attachment"] || [])
|
||||
object
|
||||
|> Map.get("attachment", [])
|
||||
|> Enum.map(fn data ->
|
||||
[%{"mediaType" => media_type, "href" => href} | _] = data["url"]
|
||||
%{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
|
||||
|
||||
%{
|
||||
"url" => href,
|
||||
"mediaType" => media_type,
|
||||
"name" => data["name"],
|
||||
"type" => "Document"
|
||||
}
|
||||
end)
|
||||
|
||||
Map.put(object, "attachment", attachments)
|
||||
end
|
||||
|
||||
def strip_internal_fields(object) do
|
||||
object
|
||||
|> Map.drop(Pleroma.Constants.object_internal_fields())
|
||||
Map.drop(object, Pleroma.Constants.object_internal_fields())
|
||||
end
|
||||
|
||||
defp strip_internal_tags(%{"tag" => tags} = object) do
|
||||
|
|
|
@ -41,9 +41,17 @@ def pagination_params do
|
|||
Operation.parameter(
|
||||
:limit,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 20, maximum: 40},
|
||||
"Limit"
|
||||
%Schema{type: :integer, default: 20},
|
||||
"Maximum number of items to return. Will be ignored if it's more than 40"
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
def empty_object_response do
|
||||
Operation.response("Empty object", "application/json", %Schema{type: :object, example: %{}})
|
||||
end
|
||||
|
||||
def empty_array_response do
|
||||
Operation.response("Empty array", "application/json", %Schema{type: :array, example: []})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -344,7 +344,7 @@ def endorsements_operation do
|
|||
description: "Not implemented",
|
||||
security: [%{"oAuth" => ["read:accounts"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
|
||||
200 => empty_array_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -356,7 +356,7 @@ def identity_proofs_operation do
|
|||
operationId: "AccountController.identity_proofs",
|
||||
description: "Not implemented",
|
||||
responses: %{
|
||||
200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
|
||||
200 => empty_array_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
|
@ -46,9 +46,7 @@ def create_operation do
|
|||
operationId: "DomainBlockController.create",
|
||||
requestBody: domain_block_request(),
|
||||
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
|
||||
}
|
||||
responses: %{200 => empty_object_response()}
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -67,7 +65,7 @@ def delete_operation do
|
|||
end
|
||||
|
||||
defp domain_block_request do
|
||||
Helpers.request_body(
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
|
|
211
lib/pleroma/web/api_spec/operations/notification_operation.ex
Normal file
211
lib/pleroma/web/api_spec/operations/notification_operation.ex
Normal file
|
@ -0,0 +1,211 @@
|
|||
# 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.NotificationOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Status
|
||||
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Notifications"],
|
||||
summary: "Get all notifications",
|
||||
description:
|
||||
"Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and `id` values.",
|
||||
operationId: "NotificationController.index",
|
||||
security: [%{"oAuth" => ["read:notifications"]}],
|
||||
parameters:
|
||||
[
|
||||
Operation.parameter(
|
||||
:exclude_types,
|
||||
:query,
|
||||
%Schema{type: :array, items: notification_type()},
|
||||
"Array of types to exclude"
|
||||
),
|
||||
Operation.parameter(
|
||||
:account_id,
|
||||
:query,
|
||||
%Schema{type: :string},
|
||||
"Return only notifications received from this account"
|
||||
),
|
||||
Operation.parameter(
|
||||
:exclude_visibilities,
|
||||
:query,
|
||||
%Schema{type: :array, items: VisibilityScope},
|
||||
"Exclude the notifications for activities with the given visibilities"
|
||||
),
|
||||
Operation.parameter(
|
||||
:include_types,
|
||||
:query,
|
||||
%Schema{type: :array, items: notification_type()},
|
||||
"Include the notifications for activities with the given types"
|
||||
),
|
||||
Operation.parameter(
|
||||
:with_muted,
|
||||
:query,
|
||||
BooleanLike,
|
||||
"Include the notifications from muted users"
|
||||
)
|
||||
] ++ pagination_params(),
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Array of notifications", "application/json", %Schema{
|
||||
type: :array,
|
||||
items: notification()
|
||||
}),
|
||||
404 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Notifications"],
|
||||
summary: "Get a single notification",
|
||||
description: "View information about a notification with a given ID.",
|
||||
operationId: "NotificationController.show",
|
||||
security: [%{"oAuth" => ["read:notifications"]}],
|
||||
parameters: [id_param()],
|
||||
responses: %{
|
||||
200 => Operation.response("Notification", "application/json", notification())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def clear_operation do
|
||||
%Operation{
|
||||
tags: ["Notifications"],
|
||||
summary: "Dismiss all notifications",
|
||||
description: "Clear all notifications from the server.",
|
||||
operationId: "NotificationController.clear",
|
||||
security: [%{"oAuth" => ["write:notifications"]}],
|
||||
responses: %{200 => empty_object_response()}
|
||||
}
|
||||
end
|
||||
|
||||
def dismiss_operation do
|
||||
%Operation{
|
||||
tags: ["Notifications"],
|
||||
summary: "Dismiss a single notification",
|
||||
description: "Clear a single notification from the server.",
|
||||
operationId: "NotificationController.dismiss",
|
||||
parameters: [id_param()],
|
||||
security: [%{"oAuth" => ["write:notifications"]}],
|
||||
responses: %{200 => empty_object_response()}
|
||||
}
|
||||
end
|
||||
|
||||
def dismiss_via_body_operation do
|
||||
%Operation{
|
||||
tags: ["Notifications"],
|
||||
summary: "Dismiss a single notification",
|
||||
deprecated: true,
|
||||
description: "Clear a single notification from the server.",
|
||||
operationId: "NotificationController.dismiss_via_body",
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{type: :object, properties: %{id: %Schema{type: :string}}},
|
||||
required: true
|
||||
),
|
||||
security: [%{"oAuth" => ["write:notifications"]}],
|
||||
responses: %{200 => empty_object_response()}
|
||||
}
|
||||
end
|
||||
|
||||
def destroy_multiple_operation do
|
||||
%Operation{
|
||||
tags: ["Notifications"],
|
||||
summary: "Dismiss multiple notifications",
|
||||
operationId: "NotificationController.destroy_multiple",
|
||||
security: [%{"oAuth" => ["write:notifications"]}],
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:ids,
|
||||
:query,
|
||||
%Schema{type: :array, items: %Schema{type: :string}},
|
||||
"Array of notification IDs to dismiss",
|
||||
required: true
|
||||
)
|
||||
],
|
||||
responses: %{200 => empty_object_response()}
|
||||
}
|
||||
end
|
||||
|
||||
defp notification do
|
||||
%Schema{
|
||||
title: "Notification",
|
||||
description: "Response schema for a notification",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
type: notification_type(),
|
||||
created_at: %Schema{type: :string, format: :"date-time"},
|
||||
account: %Schema{
|
||||
allOf: [Account],
|
||||
description: "The account that performed the action that generated the notification."
|
||||
},
|
||||
status: %Schema{
|
||||
allOf: [Status],
|
||||
description:
|
||||
"Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.",
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"id" => "34975861",
|
||||
"type" => "mention",
|
||||
"created_at" => "2019-11-23T07:49:02.064Z",
|
||||
"account" => Account.schema().example,
|
||||
"status" => Status.schema().example
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp notification_type do
|
||||
%Schema{
|
||||
type: :string,
|
||||
enum: [
|
||||
"follow",
|
||||
"favourite",
|
||||
"reblog",
|
||||
"mention",
|
||||
"poll",
|
||||
"pleroma:emoji_reaction",
|
||||
"move",
|
||||
"follow_request"
|
||||
],
|
||||
description: """
|
||||
The type of event that resulted in the notification.
|
||||
|
||||
- `follow` - Someone followed you
|
||||
- `mention` - Someone mentioned you in their status
|
||||
- `reblog` - Someone boosted one of your statuses
|
||||
- `favourite` - Someone favourited one of your statuses
|
||||
- `poll` - A poll you have voted in or created has ended
|
||||
- `move` - Someone moved their account
|
||||
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
|
||||
"""
|
||||
}
|
||||
end
|
||||
|
||||
defp id_param do
|
||||
Operation.parameter(:id, :path, :string, "Notification ID",
|
||||
example: "123",
|
||||
required: true
|
||||
)
|
||||
end
|
||||
end
|
78
lib/pleroma/web/api_spec/operations/report_operation.ex
Normal file
78
lib/pleroma/web/api_spec/operations/report_operation.ex
Normal file
|
@ -0,0 +1,78 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.ReportOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["reports"],
|
||||
summary: "File a report",
|
||||
description: "Report problematic users to your moderators",
|
||||
operationId: "ReportController.create",
|
||||
security: [%{"oAuth" => ["follow", "write:reports"]}],
|
||||
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("Report", "application/json", create_response()),
|
||||
400 => Operation.response("Report", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_request do
|
||||
%Schema{
|
||||
title: "ReportCreateRequest",
|
||||
description: "POST body for creating a report",
|
||||
type: :object,
|
||||
properties: %{
|
||||
account_id: %Schema{type: :string, description: "ID of the account to report"},
|
||||
status_ids: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{type: :string},
|
||||
description: "Array of Statuses to attach to the report, for context"
|
||||
},
|
||||
comment: %Schema{
|
||||
type: :string,
|
||||
description: "Reason for the report"
|
||||
},
|
||||
forward: %Schema{
|
||||
type: :boolean,
|
||||
default: false,
|
||||
description:
|
||||
"If the account is remote, should the report be forwarded to the remote admin?"
|
||||
}
|
||||
},
|
||||
required: [:account_id],
|
||||
example: %{
|
||||
"account_id" => "123",
|
||||
"status_ids" => ["1337"],
|
||||
"comment" => "bad status!",
|
||||
"forward" => "false"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_response do
|
||||
%Schema{
|
||||
title: "ReportResponse",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string, description: "Report ID"},
|
||||
action_taken: %Schema{type: :boolean, description: "Is action taken?"}
|
||||
},
|
||||
example: %{
|
||||
"id" => "123",
|
||||
"action_taken" => false
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -43,8 +43,8 @@ def unfollow(follower, unfollowed) do
|
|||
end
|
||||
|
||||
def accept_follow_request(follower, followed) do
|
||||
with {:ok, follower} <- User.follow(follower, followed),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follower} <- User.follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),
|
||||
{:ok, _activity} <-
|
||||
|
@ -382,9 +382,9 @@ def thread_muted?(user, activity) do
|
|||
ThreadMute.exists?(user.id, activity.data["context"])
|
||||
end
|
||||
|
||||
def report(user, %{"account_id" => account_id} = data) do
|
||||
with {:ok, account} <- get_reported_account(account_id),
|
||||
{:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
|
||||
def report(user, data) do
|
||||
with {:ok, account} <- get_reported_account(data.account_id),
|
||||
{:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),
|
||||
{:ok, statuses} <- get_report_statuses(account, data) do
|
||||
ActivityPub.flag(%{
|
||||
context: Utils.generate_context_id(),
|
||||
|
@ -392,13 +392,11 @@ def report(user, %{"account_id" => account_id} = data) do
|
|||
account: account,
|
||||
statuses: statuses,
|
||||
content: content_html,
|
||||
forward: data["forward"] || false
|
||||
forward: Map.get(data, :forward, false)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def report(_user, _params), do: {:error, dgettext("errors", "Valid `account_id` required")}
|
||||
|
||||
defp get_reported_account(account_id) do
|
||||
case User.get_cached_by_id(account_id) do
|
||||
%User{} = account -> {:ok, account}
|
||||
|
|
|
@ -504,7 +504,8 @@ def make_report_content_html(comment) do
|
|||
end
|
||||
end
|
||||
|
||||
def get_report_statuses(%User{ap_id: actor}, %{"status_ids" => status_ids}) do
|
||||
def get_report_statuses(%User{ap_id: actor}, %{status_ids: status_ids})
|
||||
when is_list(status_ids) do
|
||||
{:ok, Activity.all_by_actor_and_id(actor, status_ids)}
|
||||
end
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
defmodule Pleroma.Web.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :pleroma
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
socket("/socket", Pleroma.Web.UserSocket)
|
||||
|
||||
plug(Pleroma.Plugs.SetLocalePlug)
|
||||
|
@ -34,8 +36,7 @@ defmodule Pleroma.Web.Endpoint do
|
|||
Plug.Static,
|
||||
at: "/",
|
||||
from: :pleroma,
|
||||
only:
|
||||
~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc),
|
||||
only: Pleroma.Constants.static_only_files(),
|
||||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||
gzip: true,
|
||||
cache_control_for_etags: @static_cache_control,
|
||||
|
|
|
@ -94,24 +94,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
|
||||
@doc "POST /api/v1/accounts"
|
||||
def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
|
||||
params =
|
||||
params
|
||||
|> Map.take([
|
||||
:email,
|
||||
:bio,
|
||||
:captcha_solution,
|
||||
:captcha_token,
|
||||
:captcha_answer_data,
|
||||
:token,
|
||||
:password,
|
||||
:fullname
|
||||
])
|
||||
|> Map.put(:nickname, params.username)
|
||||
|> Map.put(:fullname, Map.get(params, :fullname, params.username))
|
||||
|> Map.put(:confirm, params.password)
|
||||
|> Map.put(:trusted_app, app.trusted)
|
||||
|
||||
with :ok <- validate_email_param(params),
|
||||
:ok <- TwitterAPI.validate_captcha(app, params),
|
||||
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
|
||||
{:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
|
||||
json(conn, %{
|
||||
|
@ -121,7 +105,7 @@ def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
|
|||
created_at: Token.Utils.format_created_at(token)
|
||||
})
|
||||
else
|
||||
{:error, errors} -> json_response(conn, :bad_request, errors)
|
||||
{:error, error} -> json_response(conn, :bad_request, %{error: error})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -133,11 +117,11 @@ def create(conn, _) do
|
|||
render_error(conn, :forbidden, "Invalid credentials")
|
||||
end
|
||||
|
||||
defp validate_email_param(%{:email => email}) when not is_nil(email), do: :ok
|
||||
defp validate_email_param(%{email: email}) when not is_nil(email), do: :ok
|
||||
|
||||
defp validate_email_param(_) do
|
||||
case Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||
true -> {:error, %{"error" => "Missing parameters"}}
|
||||
true -> {:error, dgettext("errors", "Missing parameter: %{name}", name: "email")}
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,6 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
|||
|
||||
@oauth_read_actions [:show, :index]
|
||||
|
||||
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:notifications"]} when action in @oauth_read_actions
|
||||
|
@ -20,14 +22,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
|||
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.NotificationOperation
|
||||
|
||||
# GET /api/v1/notifications
|
||||
def index(conn, %{"account_id" => account_id} = params) do
|
||||
def index(conn, %{account_id: account_id} = params) do
|
||||
case Pleroma.User.get_cached_by_id(account_id) do
|
||||
%{ap_id: account_ap_id} ->
|
||||
params =
|
||||
params
|
||||
|> Map.delete("account_id")
|
||||
|> Map.put("account_ap_id", account_ap_id)
|
||||
|> Map.delete(:account_id)
|
||||
|> Map.put(:account_ap_id, account_ap_id)
|
||||
|
||||
index(conn, params)
|
||||
|
||||
|
@ -39,6 +43,7 @@ def index(conn, %{"account_id" => account_id} = params) do
|
|||
end
|
||||
|
||||
def index(%{assigns: %{user: user}} = conn, params) do
|
||||
params = Map.new(params, fn {k, v} -> {to_string(k), v} end)
|
||||
notifications = MastodonAPI.get_notifications(user, params)
|
||||
|
||||
conn
|
||||
|
@ -51,7 +56,7 @@ def index(%{assigns: %{user: user}} = conn, params) do
|
|||
end
|
||||
|
||||
# GET /api/v1/notifications/:id
|
||||
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||
with {:ok, notification} <- Notification.get(user, id) do
|
||||
render(conn, "show.json", notification: notification, for: user)
|
||||
else
|
||||
|
@ -69,8 +74,8 @@ def clear(%{assigns: %{user: user}} = conn, _params) do
|
|||
end
|
||||
|
||||
# POST /api/v1/notifications/:id/dismiss
|
||||
# POST /api/v1/notifications/dismiss (deprecated)
|
||||
def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||
|
||||
def dismiss(%{assigns: %{user: user}} = conn, %{id: id} = _params) do
|
||||
with {:ok, _notif} <- Notification.dismiss(user, id) do
|
||||
json(conn, %{})
|
||||
else
|
||||
|
@ -81,8 +86,13 @@ def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
|||
end
|
||||
end
|
||||
|
||||
# POST /api/v1/notifications/dismiss (deprecated)
|
||||
def dismiss_via_body(%{body_params: params} = conn, _) do
|
||||
dismiss(conn, params)
|
||||
end
|
||||
|
||||
# DELETE /api/v1/notifications/destroy_multiple
|
||||
def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do
|
||||
def destroy_multiple(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do
|
||||
Notification.destroy_multiple(user, ids)
|
||||
json(conn, %{})
|
||||
end
|
||||
|
|
|
@ -9,10 +9,13 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do
|
|||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation
|
||||
|
||||
@doc "POST /api/v1/reports"
|
||||
def create(%{assigns: %{user: user}} = conn, params) do
|
||||
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
||||
with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do
|
||||
render(conn, "show.json", activity: activity)
|
||||
end
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@mastodon_api_level "2.7.2"
|
||||
|
||||
def render("show.json", _) do
|
||||
instance = Pleroma.Config.get(:instance)
|
||||
instance = Config.get(:instance)
|
||||
|
||||
%{
|
||||
uri: Pleroma.Web.base_url(),
|
||||
|
@ -29,7 +32,58 @@ def render("show.json", _) do
|
|||
upload_limit: Keyword.get(instance, :upload_limit),
|
||||
avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
|
||||
background_upload_limit: Keyword.get(instance, :background_upload_limit),
|
||||
banner_upload_limit: Keyword.get(instance, :banner_upload_limit)
|
||||
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
|
||||
pleroma: %{
|
||||
metadata: %{
|
||||
features: features(),
|
||||
federation: federation()
|
||||
},
|
||||
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def features do
|
||||
[
|
||||
"pleroma_api",
|
||||
"mastodon_api",
|
||||
"mastodon_api_streaming",
|
||||
"polls",
|
||||
"pleroma_explicit_addressing",
|
||||
"shareable_emoji_packs",
|
||||
"multifetch",
|
||||
"pleroma:api/v1/notifications:include_types_filter",
|
||||
if Config.get([:media_proxy, :enabled]) do
|
||||
"media_proxy"
|
||||
end,
|
||||
if Config.get([:gopher, :enabled]) do
|
||||
"gopher"
|
||||
end,
|
||||
if Config.get([:chat, :enabled]) do
|
||||
"chat"
|
||||
end,
|
||||
if Config.get([:instance, :allow_relay]) do
|
||||
"relay"
|
||||
end,
|
||||
if Config.get([:instance, :safe_dm_mentions]) do
|
||||
"safe_dm_mentions"
|
||||
end,
|
||||
"pleroma_emoji_reactions"
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
||||
def federation do
|
||||
quarantined = Config.get([:instance, :quarantined_instances], [])
|
||||
|
||||
if Config.get([:instance, :mrf_transparency]) do
|
||||
{:ok, data} = MRF.describe()
|
||||
|
||||
data
|
||||
|> Map.merge(%{quarantined_instances: quarantined})
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|> Map.put(:enabled, Config.get([:instance, :federating]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,8 +9,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
alias Pleroma.Stats
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
alias Pleroma.Web.MastodonAPI.InstanceView
|
||||
|
||||
def schemas(conn, _params) do
|
||||
response = %{
|
||||
|
@ -34,51 +34,12 @@ def schemas(conn, _params) do
|
|||
def raw_nodeinfo do
|
||||
stats = Stats.get_stats()
|
||||
|
||||
quarantined = Config.get([:instance, :quarantined_instances], [])
|
||||
|
||||
staff_accounts =
|
||||
User.all_superusers()
|
||||
|> Enum.map(fn u -> u.ap_id end)
|
||||
|
||||
federation_response =
|
||||
if Config.get([:instance, :mrf_transparency]) do
|
||||
{:ok, data} = MRF.describe()
|
||||
|
||||
data
|
||||
|> Map.merge(%{quarantined_instances: quarantined})
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|> Map.put(:enabled, Config.get([:instance, :federating]))
|
||||
|
||||
features =
|
||||
[
|
||||
"pleroma_api",
|
||||
"mastodon_api",
|
||||
"mastodon_api_streaming",
|
||||
"polls",
|
||||
"pleroma_explicit_addressing",
|
||||
"shareable_emoji_packs",
|
||||
"multifetch",
|
||||
"pleroma:api/v1/notifications:include_types_filter",
|
||||
if Config.get([:media_proxy, :enabled]) do
|
||||
"media_proxy"
|
||||
end,
|
||||
if Config.get([:gopher, :enabled]) do
|
||||
"gopher"
|
||||
end,
|
||||
if Config.get([:chat, :enabled]) do
|
||||
"chat"
|
||||
end,
|
||||
if Config.get([:instance, :allow_relay]) do
|
||||
"relay"
|
||||
end,
|
||||
if Config.get([:instance, :safe_dm_mentions]) do
|
||||
"safe_dm_mentions"
|
||||
end,
|
||||
"pleroma_emoji_reactions"
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
features = InstanceView.features()
|
||||
federation = InstanceView.federation()
|
||||
|
||||
%{
|
||||
version: "2.0",
|
||||
|
@ -106,7 +67,7 @@ def raw_nodeinfo do
|
|||
enabled: false
|
||||
},
|
||||
staffAccounts: staff_accounts,
|
||||
federation: federation_response,
|
||||
federation: federation,
|
||||
pollLimits: Config.get([:instance, :poll_limits]),
|
||||
postFormats: Config.get([:instance, :allowed_post_formats]),
|
||||
uploadLimits: %{
|
||||
|
|
|
@ -61,7 +61,10 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}
|
|||
else
|
||||
users =
|
||||
Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.filter(fn
|
||||
%{deactivated: false} -> true
|
||||
_ -> false
|
||||
end)
|
||||
|
||||
%{
|
||||
name: emoji,
|
||||
|
|
|
@ -396,7 +396,7 @@ defmodule Pleroma.Web.Router do
|
|||
post("/notifications/clear", NotificationController, :clear)
|
||||
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
|
||||
# Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
|
||||
post("/notifications/dismiss", NotificationController, :dismiss)
|
||||
post("/notifications/dismiss", NotificationController, :dismiss_via_body)
|
||||
|
||||
post("/polls/:id/votes", PollController, :vote)
|
||||
|
||||
|
@ -585,6 +585,7 @@ defmodule Pleroma.Web.Router do
|
|||
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
|
||||
post("/api/ap/upload_media", ActivityPubController, :upload_media)
|
||||
|
||||
# The following two are S2S as well, see `ActivityPub.fetch_follow_information_for_user/1`:
|
||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||
get("/users/:nickname/following", ActivityPubController, :following)
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />
|
||||
<title><%= Pleroma.Config.get([:instance, :name]) %></title>
|
||||
<%= Phoenix.HTML.raw(assigns[:meta] || "") %>
|
||||
<link rel="stylesheet" href="/static/static-fe.css">
|
||||
<link rel="stylesheet" href="/static-fe/static-fe.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
|
@ -3,54 +3,27 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||
import Pleroma.Web.Gettext
|
||||
|
||||
alias Pleroma.Emails.Mailer
|
||||
alias Pleroma.Emails.UserEmail
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
def register_user(params, opts \\ []) do
|
||||
params =
|
||||
params
|
||||
|> Map.take([
|
||||
:nickname,
|
||||
:password,
|
||||
:captcha_solution,
|
||||
:captcha_token,
|
||||
:captcha_answer_data,
|
||||
:token,
|
||||
:email,
|
||||
:trusted_app
|
||||
])
|
||||
|> Map.put(:bio, User.parse_bio(params[:bio] || ""))
|
||||
|> Map.put(:name, params.fullname)
|
||||
|> Map.put(:password_confirmation, params[:confirm])
|
||||
|> Map.take([:email, :token, :password])
|
||||
|> Map.put(:bio, params |> Map.get(:bio, "") |> User.parse_bio())
|
||||
|> Map.put(:nickname, params[:username])
|
||||
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|
||||
|> Map.put(:password_confirmation, params[:password])
|
||||
|
||||
case validate_captcha(params) do
|
||||
:ok ->
|
||||
if Pleroma.Config.get([:instance, :registrations_open]) do
|
||||
create_user(params, opts)
|
||||
else
|
||||
create_user_with_invite(params, opts)
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
# I have no idea how this error handling works
|
||||
{:error, %{error: Jason.encode!(%{captcha: [error]})}}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_captcha(params) do
|
||||
if params[:trusted_app] || not Pleroma.Config.get([Pleroma.Captcha, :enabled]) do
|
||||
:ok
|
||||
if Pleroma.Config.get([:instance, :registrations_open]) do
|
||||
create_user(params, opts)
|
||||
else
|
||||
Pleroma.Captcha.validate(
|
||||
params.captcha_token,
|
||||
params.captcha_solution,
|
||||
params.captcha_answer_data
|
||||
)
|
||||
create_user_with_invite(params, opts)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -75,16 +48,17 @@ defp create_user(params, opts) do
|
|||
|
||||
{:error, changeset} ->
|
||||
errors =
|
||||
Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|
||||
changeset
|
||||
|> Ecto.Changeset.traverse_errors(fn {msg, _opts} -> msg end)
|
||||
|> Jason.encode!()
|
||||
|
||||
{:error, %{error: errors}}
|
||||
{:error, errors}
|
||||
end
|
||||
end
|
||||
|
||||
def password_reset(nickname_or_email) do
|
||||
with true <- is_binary(nickname_or_email),
|
||||
%User{local: true, email: email} = user when not is_nil(email) <-
|
||||
%User{local: true, email: email} = user when is_binary(email) <-
|
||||
User.get_by_nickname_or_email(nickname_or_email),
|
||||
{:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do
|
||||
user
|
||||
|
@ -106,4 +80,58 @@ def password_reset(nickname_or_email) do
|
|||
{:error, "unknown user"}
|
||||
end
|
||||
end
|
||||
|
||||
def validate_captcha(app, params) do
|
||||
if app.trusted || not Pleroma.Captcha.enabled?() do
|
||||
:ok
|
||||
else
|
||||
do_validate_captcha(params)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_validate_captcha(params) do
|
||||
with :ok <- validate_captcha_presence(params),
|
||||
:ok <-
|
||||
Pleroma.Captcha.validate(
|
||||
params[:captcha_token],
|
||||
params[:captcha_solution],
|
||||
params[:captcha_answer_data]
|
||||
) do
|
||||
:ok
|
||||
else
|
||||
{:error, :captcha_error} ->
|
||||
captcha_error(dgettext("errors", "CAPTCHA Error"))
|
||||
|
||||
{:error, :invalid} ->
|
||||
captcha_error(dgettext("errors", "Invalid CAPTCHA"))
|
||||
|
||||
{:error, :kocaptcha_service_unavailable} ->
|
||||
captcha_error(dgettext("errors", "Kocaptcha service unavailable"))
|
||||
|
||||
{:error, :expired} ->
|
||||
captcha_error(dgettext("errors", "CAPTCHA expired"))
|
||||
|
||||
{:error, :already_used} ->
|
||||
captcha_error(dgettext("errors", "CAPTCHA already used"))
|
||||
|
||||
{:error, :invalid_answer_data} ->
|
||||
captcha_error(dgettext("errors", "Invalid answer data"))
|
||||
|
||||
{:error, error} ->
|
||||
captcha_error(error)
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_captcha_presence(params) do
|
||||
[:captcha_solution, :captcha_token, :captcha_answer_data]
|
||||
|> Enum.find_value(:ok, fn key ->
|
||||
unless is_binary(params[key]) do
|
||||
error = dgettext("errors", "Invalid CAPTCHA (Missing parameter: %{name})", name: key)
|
||||
{:error, error}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
# For some reason FE expects error message to be a serialized JSON
|
||||
defp captcha_error(error), do: {:error, Jason.encode!(%{captcha: [error]})}
|
||||
end
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
defmodule Pleroma.Repo.Migrations.InsertSkeletonsForDeletedUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
def change do
|
||||
Application.ensure_all_started(:flake_id)
|
||||
|
||||
local_ap_id =
|
||||
User.Query.build(%{local: true})
|
||||
|> select([u], u.ap_id)
|
||||
|> limit(1)
|
||||
|> Repo.one()
|
||||
|
||||
unless local_ap_id == nil do
|
||||
# Hack to get instance base url because getting it from Phoenix
|
||||
# would require starting the whole application
|
||||
instance_uri =
|
||||
local_ap_id
|
||||
|> URI.parse()
|
||||
|> Map.put(:query, nil)
|
||||
|> Map.put(:path, nil)
|
||||
|> URI.to_string()
|
||||
|
||||
{:ok, %{rows: ap_ids}} =
|
||||
Ecto.Adapters.SQL.query(
|
||||
Repo,
|
||||
"select distinct unnest(nonexistent_locals.recipients) from activities, lateral (select array_agg(recipient) as recipients from unnest(activities.recipients) as recipient where recipient similar to '#{
|
||||
instance_uri
|
||||
}/users/[A-Za-z0-9]*' and not(recipient in (select ap_id from users where local = true))) nonexistent_locals;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
ap_ids
|
||||
|> Enum.each(fn [ap_id] ->
|
||||
Ecto.Changeset.change(%User{}, deactivated: true, ap_id: ap_id)
|
||||
|> Repo.insert()
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.1055039ce3f2fe4dd110.css rel=stylesheet><link href=/static/fontello.1587147224637.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.c5bbd3734647f0cc7eef.js></script><script type=text/javascript src=/static/js/app.def6476e8bc9b214218b.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.18fea621d430000acc27.css rel=stylesheet><link href=/static/css/app.613cef07981cd95ccceb.css rel=stylesheet><link href=/static/fontello.1588419330867.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.8aa781e6dd81307f544b.js></script><script type=text/javascript src=/static/js/app.fa89b90e606f4facd209.js></script></body></html>
|
|
@ -1,108 +0,0 @@
|
|||
.with-load-more-footer {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border-top: 1px solid;
|
||||
border-top-color: #222;
|
||||
border-top-color: var(--border, #222);
|
||||
}
|
||||
.with-load-more-footer .error {
|
||||
font-size: 14px;
|
||||
}
|
||||
.tab-switcher {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tab-switcher .contents {
|
||||
-ms-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
min-height: 0px;
|
||||
}
|
||||
.tab-switcher .contents .hidden {
|
||||
display: none;
|
||||
}
|
||||
.tab-switcher .contents.scrollable-tabs {
|
||||
-ms-flex-preferred-size: 0;
|
||||
flex-basis: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.tab-switcher .tabs {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
padding-top: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tab-switcher .tabs::after, .tab-switcher .tabs::before {
|
||||
display: block;
|
||||
content: "";
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: #222;
|
||||
border-bottom-color: var(--border, #222);
|
||||
}
|
||||
.tab-switcher .tabs .tab-wrapper {
|
||||
height: 28px;
|
||||
position: relative;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.tab-switcher .tabs .tab-wrapper .tab {
|
||||
width: 100%;
|
||||
min-width: 1px;
|
||||
position: relative;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
padding: 6px 1em;
|
||||
padding-bottom: 99px;
|
||||
margin-bottom: -93px;
|
||||
white-space: nowrap;
|
||||
color: #b9b9ba;
|
||||
color: var(--tabText, #b9b9ba);
|
||||
background-color: #182230;
|
||||
background-color: var(--tab, #182230);
|
||||
}
|
||||
.tab-switcher .tabs .tab-wrapper .tab:not(.active) {
|
||||
z-index: 4;
|
||||
}
|
||||
.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover {
|
||||
z-index: 6;
|
||||
}
|
||||
.tab-switcher .tabs .tab-wrapper .tab.active {
|
||||
background: transparent;
|
||||
z-index: 5;
|
||||
color: #b9b9ba;
|
||||
color: var(--tabActiveText, #b9b9ba);
|
||||
}
|
||||
.tab-switcher .tabs .tab-wrapper .tab img {
|
||||
max-height: 26px;
|
||||
vertical-align: top;
|
||||
margin-top: -5px;
|
||||
}
|
||||
.tab-switcher .tabs .tab-wrapper:not(.active)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 7;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: #222;
|
||||
border-bottom-color: var(--border, #222);
|
||||
}
|
||||
.with-subscription-loading {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.with-subscription-loading .error {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=app.1055039ce3f2fe4dd110.css.map*/
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACTA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACxFA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.1055039ce3f2fe4dd110.css","sourcesContent":[".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}",".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n overflow-y: auto;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher .tabs .tab-wrapper {\n height: 28px;\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tabs .tab-wrapper .tab {\n width: 100%;\n min-width: 1px;\n position: relative;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 1em;\n padding-bottom: 99px;\n margin-bottom: -93px;\n white-space: nowrap;\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tabs .tab-wrapper .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tabs .tab-wrapper .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 7;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}",".with-subscription-loading {\n padding: 10px;\n text-align: center;\n}\n.with-subscription-loading .error {\n font-size: 14px;\n}"],"sourceRoot":""}
|
5
priv/static/static/css/app.613cef07981cd95ccceb.css
Normal file
5
priv/static/static/css/app.613cef07981cd95ccceb.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.with-load-more-footer{padding:10px;text-align:center;border-top:1px solid;border-top-color:#222;border-top-color:var(--border, #222)}.with-load-more-footer .error{font-size:14px}
|
||||
.tab-switcher{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.tab-switcher .contents{-ms-flex:1 0 auto;flex:1 0 auto;min-height:0px}.tab-switcher .contents .hidden{display:none}.tab-switcher .contents.scrollable-tabs{-ms-flex-preferred-size:0;flex-basis:0;overflow-y:auto}.tab-switcher .tabs{display:-ms-flexbox;display:flex;position:relative;width:100%;overflow-y:hidden;overflow-x:auto;padding-top:5px;box-sizing:border-box}.tab-switcher .tabs::after,.tab-switcher .tabs::before{display:block;content:"";-ms-flex:1 1 auto;flex:1 1 auto;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}.tab-switcher .tabs .tab-wrapper{height:28px;position:relative;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto}.tab-switcher .tabs .tab-wrapper .tab{width:100%;min-width:1px;position:relative;border-bottom-left-radius:0;border-bottom-right-radius:0;padding:6px 1em;padding-bottom:99px;margin-bottom:-93px;white-space:nowrap;color:#b9b9ba;color:var(--tabText, #b9b9ba);background-color:#182230;background-color:var(--tab, #182230)}.tab-switcher .tabs .tab-wrapper .tab:not(.active){z-index:4}.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover{z-index:6}.tab-switcher .tabs .tab-wrapper .tab.active{background:transparent;z-index:5;color:#b9b9ba;color:var(--tabActiveText, #b9b9ba)}.tab-switcher .tabs .tab-wrapper .tab img{max-height:26px;vertical-align:top;margin-top:-5px}.tab-switcher .tabs .tab-wrapper:not(.active)::after{content:"";position:absolute;left:0;right:0;bottom:0;z-index:7;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}
|
||||
.with-subscription-loading{padding:10px;text-align:center}.with-subscription-loading .error{font-size:14px}
|
||||
|
||||
/*# sourceMappingURL=app.613cef07981cd95ccceb.css.map*/
|
1
priv/static/static/css/app.613cef07981cd95ccceb.css.map
Normal file
1
priv/static/static/css/app.613cef07981cd95ccceb.css.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA,uBAAuB,aAAa,kBAAkB,qBAAqB,sBAAsB,qCAAqC,8BAA8B,e;ACApK,cAAc,oBAAoB,aAAa,0BAA0B,sBAAsB,wBAAwB,kBAAkB,cAAc,eAAe,gCAAgC,aAAa,wCAAwC,0BAA0B,aAAa,gBAAgB,oBAAoB,oBAAoB,aAAa,kBAAkB,WAAW,kBAAkB,gBAAgB,gBAAgB,sBAAsB,uDAAuD,cAAc,WAAW,kBAAkB,cAAc,wBAAwB,yBAAyB,wCAAwC,iCAAiC,YAAY,kBAAkB,oBAAoB,aAAa,kBAAkB,cAAc,sCAAsC,WAAW,cAAc,kBAAkB,4BAA4B,6BAA6B,gBAAgB,oBAAoB,oBAAoB,mBAAmB,cAAc,8BAA8B,yBAAyB,qCAAqC,mDAAmD,UAAU,yDAAyD,UAAU,6CAA6C,uBAAuB,UAAU,cAAc,oCAAoC,0CAA0C,gBAAgB,mBAAmB,gBAAgB,qDAAqD,WAAW,kBAAkB,OAAO,QAAQ,SAAS,UAAU,wBAAwB,yBAAyB,wC;ACAtlD,2BAA2B,aAAa,kBAAkB,kCAAkC,e","file":"static/css/app.613cef07981cd95ccceb.css","sourcesContent":[".with-load-more-footer{padding:10px;text-align:center;border-top:1px solid;border-top-color:#222;border-top-color:var(--border, #222)}.with-load-more-footer .error{font-size:14px}",".tab-switcher{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.tab-switcher .contents{-ms-flex:1 0 auto;flex:1 0 auto;min-height:0px}.tab-switcher .contents .hidden{display:none}.tab-switcher .contents.scrollable-tabs{-ms-flex-preferred-size:0;flex-basis:0;overflow-y:auto}.tab-switcher .tabs{display:-ms-flexbox;display:flex;position:relative;width:100%;overflow-y:hidden;overflow-x:auto;padding-top:5px;box-sizing:border-box}.tab-switcher .tabs::after,.tab-switcher .tabs::before{display:block;content:\"\";-ms-flex:1 1 auto;flex:1 1 auto;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}.tab-switcher .tabs .tab-wrapper{height:28px;position:relative;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto}.tab-switcher .tabs .tab-wrapper .tab{width:100%;min-width:1px;position:relative;border-bottom-left-radius:0;border-bottom-right-radius:0;padding:6px 1em;padding-bottom:99px;margin-bottom:-93px;white-space:nowrap;color:#b9b9ba;color:var(--tabText, #b9b9ba);background-color:#182230;background-color:var(--tab, #182230)}.tab-switcher .tabs .tab-wrapper .tab:not(.active){z-index:4}.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover{z-index:6}.tab-switcher .tabs .tab-wrapper .tab.active{background:transparent;z-index:5;color:#b9b9ba;color:var(--tabActiveText, #b9b9ba)}.tab-switcher .tabs .tab-wrapper .tab img{max-height:26px;vertical-align:top;margin-top:-5px}.tab-switcher .tabs .tab-wrapper:not(.active)::after{content:\"\";position:absolute;left:0;right:0;bottom:0;z-index:7;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}",".with-subscription-loading{padding:10px;text-align:center}.with-subscription-loading .error{font-size:14px}"],"sourceRoot":""}
|
|
@ -1,11 +1,11 @@
|
|||
/*!
|
||||
* Cropper.js v1.4.3
|
||||
* Cropper.js v1.5.6
|
||||
* https://fengyuanchen.github.io/cropperjs
|
||||
*
|
||||
* Copyright 2015-present Chen Fengyuan
|
||||
* Released under the MIT license
|
||||
*
|
||||
* Date: 2018-10-24T13:07:11.429Z
|
||||
* Date: 2019-10-04T04:33:44.164Z
|
||||
*/
|
||||
|
||||
.cropper-container {
|
||||
|
@ -16,7 +16,6 @@ .cropper-container {
|
|||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
@ -56,14 +55,14 @@ .cropper-drag-box {
|
|||
|
||||
.cropper-modal {
|
||||
background-color: #000;
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.cropper-view-box {
|
||||
display: block;
|
||||
height: 100%;
|
||||
outline-color: rgba(51, 153, 255, 0.75);
|
||||
outline: 1px solid #39f;
|
||||
outline-color: rgba(51, 153, 255, 0.75);
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -71,7 +70,7 @@ .cropper-view-box {
|
|||
.cropper-dashed {
|
||||
border: 0 dashed #eee;
|
||||
display: block;
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
@ -97,28 +96,28 @@ .cropper-center {
|
|||
display: block;
|
||||
height: 0;
|
||||
left: 50%;
|
||||
opacity: .75;
|
||||
opacity: 0.75;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.cropper-center:before,
|
||||
.cropper-center:after {
|
||||
.cropper-center::before,
|
||||
.cropper-center::after {
|
||||
background-color: #eee;
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.cropper-center:before {
|
||||
.cropper-center::before {
|
||||
height: 1px;
|
||||
left: -3px;
|
||||
top: 0;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
.cropper-center:after {
|
||||
.cropper-center::after {
|
||||
height: 7px;
|
||||
left: 0;
|
||||
top: -3px;
|
||||
|
@ -130,7 +129,7 @@ .cropper-line,
|
|||
.cropper-point {
|
||||
display: block;
|
||||
height: 100%;
|
||||
opacity: .1;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -176,7 +175,7 @@ .cropper-line.line-s {
|
|||
.cropper-point {
|
||||
background-color: #39f;
|
||||
height: 5px;
|
||||
opacity: .75;
|
||||
opacity: 0.75;
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
|
@ -252,12 +251,12 @@ @media (min-width: 992px) {
|
|||
@media (min-width: 1200px) {
|
||||
.cropper-point.point-se {
|
||||
height: 5px;
|
||||
opacity: .75;
|
||||
opacity: 0.75;
|
||||
width: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.cropper-point.point-se:before {
|
||||
.cropper-point.point-se::before {
|
||||
background-color: #39f;
|
||||
bottom: -50%;
|
||||
content: ' ';
|
||||
|
@ -304,4 +303,4 @@ .cropper-disabled .cropper-point {
|
|||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=vendors~app.b2603a50868c68a1c192.css.map*/
|
||||
/*# sourceMappingURL=vendors~app.18fea621d430000acc27.css.map*/
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -78,6 +78,10 @@
|
|||
|
||||
<glyph glyph-name="link" unicode="" d="M813 178q0 23-16 38l-116 116q-16 16-38 16-24 0-40-18 1-1 10-10t12-12 9-11 7-14 2-15q0-23-16-38t-38-16q-8 0-15 2t-14 7-11 9-12 12-10 10q-19-17-19-40 0-23 16-38l115-116q15-15 38-15 22 0 38 15l82 81q16 16 16 37z m-393 394q0 22-15 38l-115 115q-16 16-38 16-22 0-38-15l-82-82q-16-15-16-37 0-22 16-38l116-116q15-15 38-15 23 0 40 17-2 2-11 11t-12 12-8 10-7 14-2 16q0 22 15 38t38 15q9 0 16-2t14-7 11-8 12-12 10-11q18 17 18 41z m500-394q0-66-48-113l-82-81q-46-47-113-47-68 0-114 48l-115 115q-46 47-46 114 0 68 49 116l-49 49q-48-49-116-49-67 0-114 47l-116 116q-47 47-47 114t47 113l82 82q47 46 114 46 67 0 114-47l115-116q46-46 46-113 0-69-49-117l49-49q48 49 116 49 67 0 114-47l116-116q47-47 47-114z" horiz-adv-x="928.6" />
|
||||
|
||||
<glyph glyph-name="user" unicode="" d="M714 76q0-60-35-104t-84-44h-476q-49 0-84 44t-35 104q0 48 5 90t17 85 33 73 52 50 76 19q73-72 174-72t175 72q42 0 75-19t52-50 33-73 18-85 4-90z m-143 495q0-88-62-151t-152-63-151 63-63 151 63 152 151 63 152-63 62-152z" horiz-adv-x="714.3" />
|
||||
|
||||
<glyph glyph-name="ok" unicode="" d="M933 541q0-22-16-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q16-15 16-38z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="spin3" unicode="" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="spin4" unicode="" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
Binary file not shown.
BIN
priv/static/static/font/fontello.1588419330867.woff
Normal file
BIN
priv/static/static/font/fontello.1588419330867.woff
Normal file
Binary file not shown.
BIN
priv/static/static/font/fontello.1588419330867.woff2
Normal file
BIN
priv/static/static/font/fontello.1588419330867.woff2
Normal file
Binary file not shown.
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: "Icons";
|
||||
src: url("./font/fontello.1587147224637.eot");
|
||||
src: url("./font/fontello.1587147224637.eot") format("embedded-opentype"),
|
||||
url("./font/fontello.1587147224637.woff2") format("woff2"),
|
||||
url("./font/fontello.1587147224637.woff") format("woff"),
|
||||
url("./font/fontello.1587147224637.ttf") format("truetype"),
|
||||
url("./font/fontello.1587147224637.svg") format("svg");
|
||||
src: url("./font/fontello.1588419330867.eot");
|
||||
src: url("./font/fontello.1588419330867.eot") format("embedded-opentype"),
|
||||
url("./font/fontello.1588419330867.woff2") format("woff2"),
|
||||
url("./font/fontello.1588419330867.woff") format("woff"),
|
||||
url("./font/fontello.1588419330867.ttf") format("truetype"),
|
||||
url("./font/fontello.1588419330867.svg") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -136,3 +136,7 @@ .icon-login::before { content: "\e820"; }
|
|||
.icon-arrow-curved::before { content: "\e822"; }
|
||||
|
||||
.icon-link::before { content: "\e823"; }
|
||||
|
||||
.icon-user::before { content: "\e824"; }
|
||||
|
||||
.icon-ok::before { content: "\e827"; }
|
|
@ -345,6 +345,18 @@
|
|||
"css": "link",
|
||||
"code": 59427,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "8b80d36d4ef43889db10bc1f0dc9a862",
|
||||
"css": "user",
|
||||
"code": 59428,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "12f4ece88e46abd864e40b35e05b11cd",
|
||||
"css": "ok",
|
||||
"code": 59431,
|
||||
"src": "fontawesome"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
BIN
priv/static/static/js/2.1c407059cd79fca99e19.js
Normal file
BIN
priv/static/static/js/2.1c407059cd79fca99e19.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/2.1c407059cd79fca99e19.js.map
Normal file
BIN
priv/static/static/js/2.1c407059cd79fca99e19.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/app.fa89b90e606f4facd209.js
Normal file
BIN
priv/static/static/js/app.fa89b90e606f4facd209.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/app.fa89b90e606f4facd209.js.map
Normal file
BIN
priv/static/static/js/app.fa89b90e606f4facd209.js.map
Normal file
Binary file not shown.
BIN
priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js
Normal file
BIN
priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js.map
Normal file
BIN
priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -61,7 +61,7 @@ test "new and validate" do
|
|||
|
||||
assert is_binary(answer)
|
||||
assert :ok = Native.validate(token, answer, answer)
|
||||
assert {:error, "Invalid CAPTCHA"} == Native.validate(token, answer, answer <> "foobar")
|
||||
assert {:error, :invalid} == Native.validate(token, answer, answer <> "foobar")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -78,6 +78,7 @@ test "validate" do
|
|||
|
||||
assert is_binary(answer)
|
||||
assert :ok = Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", answer)
|
||||
Cachex.del(:used_captcha_cache, token)
|
||||
end
|
||||
|
||||
test "doesn't validate invalid answer" do
|
||||
|
@ -92,7 +93,7 @@ test "doesn't validate invalid answer" do
|
|||
|
||||
assert is_binary(answer)
|
||||
|
||||
assert {:error, "Invalid answer data"} =
|
||||
assert {:error, :invalid_answer_data} =
|
||||
Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", answer <> "foobar")
|
||||
end
|
||||
|
||||
|
@ -108,7 +109,7 @@ test "nil answer_data" do
|
|||
|
||||
assert is_binary(answer)
|
||||
|
||||
assert {:error, "Invalid answer data"} =
|
||||
assert {:error, :invalid_answer_data} =
|
||||
Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", nil)
|
||||
end
|
||||
end
|
||||
|
|
112
test/fixtures/tesla_mock/craigmaloney.json
vendored
Normal file
112
test/fixtures/tesla_mock/craigmaloney.json
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"CacheFile": "pt:CacheFile",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Infohash": "pt:Infohash",
|
||||
"RsaSignature2017": "https://w3id.org/security#RsaSignature2017",
|
||||
"category": "sc:category",
|
||||
"commentsEnabled": {
|
||||
"@id": "pt:commentsEnabled",
|
||||
"@type": "sc:Boolean"
|
||||
},
|
||||
"downloadEnabled": {
|
||||
"@id": "pt:downloadEnabled",
|
||||
"@type": "sc:Boolean"
|
||||
},
|
||||
"expires": "sc:expires",
|
||||
"fps": {
|
||||
"@id": "pt:fps",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"language": "sc:inLanguage",
|
||||
"licence": "sc:license",
|
||||
"originallyPublishedAt": "sc:datePublished",
|
||||
"position": {
|
||||
"@id": "pt:position",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"pt": "https://joinpeertube.org/ns#",
|
||||
"sc": "http://schema.org#",
|
||||
"sensitive": "as:sensitive",
|
||||
"size": {
|
||||
"@id": "pt:size",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"startTimestamp": {
|
||||
"@id": "pt:startTimestamp",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"state": {
|
||||
"@id": "pt:state",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"stopTimestamp": {
|
||||
"@id": "pt:stopTimestamp",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"subtitleLanguage": "sc:subtitleLanguage",
|
||||
"support": {
|
||||
"@id": "pt:support",
|
||||
"@type": "sc:Text"
|
||||
},
|
||||
"uuid": "sc:identifier",
|
||||
"views": {
|
||||
"@id": "pt:views",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"waitTranscoding": {
|
||||
"@id": "pt:waitTranscoding",
|
||||
"@type": "sc:Boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comments": {
|
||||
"@id": "as:comments",
|
||||
"@type": "@id"
|
||||
},
|
||||
"dislikes": {
|
||||
"@id": "as:dislikes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"likes": {
|
||||
"@id": "as:likes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"playlists": {
|
||||
"@id": "pt:playlists",
|
||||
"@type": "@id"
|
||||
},
|
||||
"shares": {
|
||||
"@id": "as:shares",
|
||||
"@type": "@id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://peertube.social/inbox"
|
||||
},
|
||||
"followers": "https://peertube.social/accounts/craigmaloney/followers",
|
||||
"following": "https://peertube.social/accounts/craigmaloney/following",
|
||||
"icon": {
|
||||
"mediaType": "image/png",
|
||||
"type": "Image",
|
||||
"url": "https://peertube.social/lazy-static/avatars/87bd694b-95bc-4066-83f4-bddfcd2b9caa.png"
|
||||
},
|
||||
"id": "https://peertube.social/accounts/craigmaloney",
|
||||
"inbox": "https://peertube.social/accounts/craigmaloney/inbox",
|
||||
"name": "Craig Maloney",
|
||||
"outbox": "https://peertube.social/accounts/craigmaloney/outbox",
|
||||
"playlists": "https://peertube.social/accounts/craigmaloney/playlists",
|
||||
"preferredUsername": "craigmaloney",
|
||||
"publicKey": {
|
||||
"id": "https://peertube.social/accounts/craigmaloney#main-key",
|
||||
"owner": "https://peertube.social/accounts/craigmaloney",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9qvGIYUW01yc8CCsrwxK\n5OXlV5s7EbNWY8tJr/p1oGuELZwAnG2XKxtdbvgcCT+YxL5uRXIdCFIIIKrzRFr/\nHfS0mOgNT9u3gu+SstCNgtatciT0RVP77yiC3b2NHq1NRRvvVhzQb4cpIWObIxqh\nb2ypDClTc7XaKtgmQCbwZlGyZMT+EKz/vustD6BlpGsglRkm7iES6s1PPGb1BU+n\nS94KhbS2DOFiLcXCVWt0QarokIIuKznp4+xP1axKyP+SkT5AHx08Nd5TYFb2C1Jl\nz0WD/1q0mAN62m7QrA3SQPUgB+wWD+S3Nzf7FwNPiP4srbBgxVEUnji/r9mQ6BXC\nrQIDAQAB\n-----END PUBLIC KEY-----"
|
||||
},
|
||||
"summary": null,
|
||||
"type": "Person",
|
||||
"url": "https://peertube.social/accounts/craigmaloney"
|
||||
}
|
234
test/fixtures/tesla_mock/peertube-social.json
vendored
Normal file
234
test/fixtures/tesla_mock/peertube-social.json
vendored
Normal file
|
@ -0,0 +1,234 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"CacheFile": "pt:CacheFile",
|
||||
"Hashtag": "as:Hashtag",
|
||||
"Infohash": "pt:Infohash",
|
||||
"RsaSignature2017": "https://w3id.org/security#RsaSignature2017",
|
||||
"category": "sc:category",
|
||||
"commentsEnabled": {
|
||||
"@id": "pt:commentsEnabled",
|
||||
"@type": "sc:Boolean"
|
||||
},
|
||||
"downloadEnabled": {
|
||||
"@id": "pt:downloadEnabled",
|
||||
"@type": "sc:Boolean"
|
||||
},
|
||||
"expires": "sc:expires",
|
||||
"fps": {
|
||||
"@id": "pt:fps",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"language": "sc:inLanguage",
|
||||
"licence": "sc:license",
|
||||
"originallyPublishedAt": "sc:datePublished",
|
||||
"position": {
|
||||
"@id": "pt:position",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"pt": "https://joinpeertube.org/ns#",
|
||||
"sc": "http://schema.org#",
|
||||
"sensitive": "as:sensitive",
|
||||
"size": {
|
||||
"@id": "pt:size",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"startTimestamp": {
|
||||
"@id": "pt:startTimestamp",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"state": {
|
||||
"@id": "pt:state",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"stopTimestamp": {
|
||||
"@id": "pt:stopTimestamp",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"subtitleLanguage": "sc:subtitleLanguage",
|
||||
"support": {
|
||||
"@id": "pt:support",
|
||||
"@type": "sc:Text"
|
||||
},
|
||||
"uuid": "sc:identifier",
|
||||
"views": {
|
||||
"@id": "pt:views",
|
||||
"@type": "sc:Number"
|
||||
},
|
||||
"waitTranscoding": {
|
||||
"@id": "pt:waitTranscoding",
|
||||
"@type": "sc:Boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comments": {
|
||||
"@id": "as:comments",
|
||||
"@type": "@id"
|
||||
},
|
||||
"dislikes": {
|
||||
"@id": "as:dislikes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"likes": {
|
||||
"@id": "as:likes",
|
||||
"@type": "@id"
|
||||
},
|
||||
"playlists": {
|
||||
"@id": "pt:playlists",
|
||||
"@type": "@id"
|
||||
},
|
||||
"shares": {
|
||||
"@id": "as:shares",
|
||||
"@type": "@id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributedTo": [
|
||||
{
|
||||
"id": "https://peertube.social/accounts/craigmaloney",
|
||||
"type": "Person"
|
||||
},
|
||||
{
|
||||
"id": "https://peertube.social/video-channels/9909c7d9-6b5b-4aae-9164-c1af7229c91c",
|
||||
"type": "Group"
|
||||
}
|
||||
],
|
||||
"category": {
|
||||
"identifier": "15",
|
||||
"name": "Science & Technology"
|
||||
},
|
||||
"cc": [
|
||||
"https://peertube.social/accounts/craigmaloney/followers"
|
||||
],
|
||||
"comments": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/comments",
|
||||
"commentsEnabled": true,
|
||||
"content": "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've...",
|
||||
"dislikes": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/dislikes",
|
||||
"downloadEnabled": true,
|
||||
"duration": "PT5151S",
|
||||
"icon": {
|
||||
"height": 122,
|
||||
"mediaType": "image/jpeg",
|
||||
"type": "Image",
|
||||
"url": "https://peertube.social/static/thumbnails/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe.jpg",
|
||||
"width": 223
|
||||
},
|
||||
"id": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe",
|
||||
"language": {
|
||||
"identifier": "en",
|
||||
"name": "English"
|
||||
},
|
||||
"licence": {
|
||||
"identifier": "1",
|
||||
"name": "Attribution"
|
||||
},
|
||||
"likes": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/likes",
|
||||
"mediaType": "text/markdown",
|
||||
"name": "Twenty Years in Jail: FreeBSD's Jails, Then and Now",
|
||||
"originallyPublishedAt": "2019-08-13T00:00:00.000Z",
|
||||
"published": "2020-02-12T01:06:08.054Z",
|
||||
"sensitive": false,
|
||||
"shares": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/announces",
|
||||
"state": 1,
|
||||
"subtitleLanguage": [],
|
||||
"support": "Learn more at http://mug.org",
|
||||
"tag": [
|
||||
{
|
||||
"name": "linux",
|
||||
"type": "Hashtag"
|
||||
},
|
||||
{
|
||||
"name": "mug.org",
|
||||
"type": "Hashtag"
|
||||
},
|
||||
{
|
||||
"name": "open",
|
||||
"type": "Hashtag"
|
||||
},
|
||||
{
|
||||
"name": "oss",
|
||||
"type": "Hashtag"
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"type": "Hashtag"
|
||||
}
|
||||
],
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Video",
|
||||
"updated": "2020-02-15T15:01:09.474Z",
|
||||
"url": [
|
||||
{
|
||||
"href": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe",
|
||||
"mediaType": "text/html",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"fps": 30,
|
||||
"height": 240,
|
||||
"href": "https://peertube.social/static/webseed/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.mp4",
|
||||
"mediaType": "video/mp4",
|
||||
"size": 119465800,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"height": 240,
|
||||
"href": "https://peertube.social/static/torrents/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.torrent",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"height": 240,
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.social%2Fstatic%2Ftorrents%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.torrent&xt=urn:btih:b3365331a8543bf48d09add56d7fe4b1cbbb5659&dn=Twenty+Years+in+Jail%3A+FreeBSD's+Jails%2C+Then+and+Now&tr=wss%3A%2F%2Fpeertube.social%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.social%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.social%2Fstatic%2Fwebseed%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.mp4",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"fps": 30,
|
||||
"height": 360,
|
||||
"href": "https://peertube.social/static/webseed/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.mp4",
|
||||
"mediaType": "video/mp4",
|
||||
"size": 143930318,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"height": 360,
|
||||
"href": "https://peertube.social/static/torrents/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.torrent",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"height": 360,
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.social%2Fstatic%2Ftorrents%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.torrent&xt=urn:btih:0d37b23c98cb0d89e28b5dc8f49b3c97a041e569&dn=Twenty+Years+in+Jail%3A+FreeBSD's+Jails%2C+Then+and+Now&tr=wss%3A%2F%2Fpeertube.social%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.social%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.social%2Fstatic%2Fwebseed%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.mp4",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"fps": 30,
|
||||
"height": 480,
|
||||
"href": "https://peertube.social/static/webseed/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.mp4",
|
||||
"mediaType": "video/mp4",
|
||||
"size": 130530754,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"height": 480,
|
||||
"href": "https://peertube.social/static/torrents/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.torrent",
|
||||
"mediaType": "application/x-bittorrent",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"height": 480,
|
||||
"href": "magnet:?xs=https%3A%2F%2Fpeertube.social%2Fstatic%2Ftorrents%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.torrent&xt=urn:btih:3a13ff822ad9494165eff6167183ddaaabc1372a&dn=Twenty+Years+in+Jail%3A+FreeBSD's+Jails%2C+Then+and+Now&tr=wss%3A%2F%2Fpeertube.social%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.social%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.social%2Fstatic%2Fwebseed%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.mp4",
|
||||
"mediaType": "application/x-bittorrent;x-scheme-handler/magnet",
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"uuid": "278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe",
|
||||
"views": 2,
|
||||
"waitTranscoding": false
|
||||
}
|
|
@ -315,9 +315,7 @@ test "it creates `follow` notification for approved Follow activity" do
|
|||
})
|
||||
end
|
||||
|
||||
test "if `follow_request` notifications are enabled, " <>
|
||||
"it creates `follow_request` notification for pending Follow activity" do
|
||||
clear_config([:notifications, :enable_follow_request_notifications], true)
|
||||
test "it creates `follow_request` notification for pending Follow activity" do
|
||||
user = insert(:user)
|
||||
followed_user = insert(:user, locked: true)
|
||||
|
||||
|
@ -336,21 +334,6 @@ test "if `follow_request` notifications are enabled, " <>
|
|||
assert %{type: "follow"} = NotificationView.render("show.json", render_opts)
|
||||
end
|
||||
|
||||
test "if `follow_request` notifications are disabled, " <>
|
||||
"it does NOT create `follow*` notification for pending Follow activity" do
|
||||
clear_config([:notifications, :enable_follow_request_notifications], false)
|
||||
user = insert(:user)
|
||||
followed_user = insert(:user, locked: true)
|
||||
|
||||
{:ok, _, _, _activity} = CommonAPI.follow(user, followed_user)
|
||||
refute FollowingRelationship.following?(user, followed_user)
|
||||
assert [] = Notification.for_user(followed_user)
|
||||
|
||||
# After request is accepted, no new notifications are generated:
|
||||
assert {:ok, _} = CommonAPI.accept_follow_request(user, followed_user)
|
||||
assert [] = Notification.for_user(followed_user)
|
||||
end
|
||||
|
||||
test "it doesn't create a notification for follow-unfollow-follow chains" do
|
||||
user = insert(:user)
|
||||
followed_user = insert(:user, locked: false)
|
||||
|
@ -367,7 +350,6 @@ test "it doesn't create a notification for follow-unfollow-follow chains" do
|
|||
end
|
||||
|
||||
test "dismisses the notification on follow request rejection" do
|
||||
clear_config([:notifications, :enable_follow_request_notifications], true)
|
||||
user = insert(:user, locked: true)
|
||||
follower = insert(:user)
|
||||
{:ok, _, _, _follow_activity} = CommonAPI.follow(follower, user)
|
||||
|
|
|
@ -44,7 +44,8 @@ test "it returns key" do
|
|||
|
||||
test "it returns error when not found user" do
|
||||
assert capture_log(fn ->
|
||||
assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) == {:error, :error}
|
||||
assert Signature.fetch_public_key(make_fake_conn("https://test-ap-id")) ==
|
||||
{:error, :error}
|
||||
end) =~ "[error] Could not decode user"
|
||||
end
|
||||
|
||||
|
@ -64,7 +65,7 @@ test "it returns key" do
|
|||
|
||||
test "it returns error when not found user" do
|
||||
assert capture_log(fn ->
|
||||
{:error, _} = Signature.refetch_public_key(make_fake_conn("test-ap_id"))
|
||||
{:error, _} = Signature.refetch_public_key(make_fake_conn("https://test-ap_id"))
|
||||
end) =~ "[error] Could not decode user"
|
||||
end
|
||||
end
|
||||
|
@ -100,12 +101,21 @@ test "it returns error" do
|
|||
describe "key_id_to_actor_id/1" do
|
||||
test "it properly deduces the actor id for misskey" do
|
||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") ==
|
||||
"https://example.com/users/1234"
|
||||
{:ok, "https://example.com/users/1234"}
|
||||
end
|
||||
|
||||
test "it properly deduces the actor id for mastodon and pleroma" do
|
||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") ==
|
||||
"https://example.com/users/1234"
|
||||
{:ok, "https://example.com/users/1234"}
|
||||
end
|
||||
|
||||
test "it calls webfinger for 'acct:' accounts" do
|
||||
with_mock(Pleroma.Web.WebFinger,
|
||||
finger: fn _ -> %{"ap_id" => "https://gensokyo.2hu/users/raymoo"} end
|
||||
) do
|
||||
assert Signature.key_id_to_actor_id("acct:raymoo@gensokyo.2hu") ==
|
||||
{:ok, "https://gensokyo.2hu/users/raymoo"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,12 +6,16 @@ defmodule Pleroma.Captcha.Mock do
|
|||
alias Pleroma.Captcha.Service
|
||||
@behaviour Service
|
||||
|
||||
@solution "63615261b77f5354fb8c4e4986477555"
|
||||
|
||||
def solution, do: @solution
|
||||
|
||||
@impl Service
|
||||
def new,
|
||||
do: %{
|
||||
type: :mock,
|
||||
token: "afa1815e14e29355e6c8f6b143a39fa2",
|
||||
answer_data: "63615261b77f5354fb8c4e4986477555",
|
||||
answer_data: @solution,
|
||||
url: "https://example.org/captcha.png"
|
||||
}
|
||||
|
||||
|
|
|
@ -308,6 +308,22 @@ def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
|
|||
}}
|
||||
end
|
||||
|
||||
def get("https://peertube.social/accounts/craigmaloney", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/tesla_mock/craigmaloney.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", _, _, _) do
|
||||
{:ok,
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/tesla_mock/peertube-social.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _, _, [
|
||||
{"accept", "application/activity+json"}
|
||||
]) do
|
||||
|
|
|
@ -92,7 +92,7 @@ test "user is deleted" do
|
|||
assert_received {:mix_shell, :info, [message]}
|
||||
assert message =~ " deleted"
|
||||
|
||||
refute User.get_by_nickname(user.nickname)
|
||||
assert %{deactivated: true} = User.get_by_nickname(user.nickname)
|
||||
end
|
||||
|
||||
test "no user to delete" do
|
||||
|
|
|
@ -1135,16 +1135,7 @@ test ".delete_user_activities deletes all create activities", %{user: user} do
|
|||
refute Activity.get_by_id(activity.id)
|
||||
end
|
||||
|
||||
test "it deletes deactivated user" do
|
||||
{:ok, user} = insert(:user, deactivated: true) |> User.set_cache()
|
||||
|
||||
{:ok, job} = User.delete(user)
|
||||
{:ok, _user} = ObanHelpers.perform(job)
|
||||
|
||||
refute User.get_by_id(user.id)
|
||||
end
|
||||
|
||||
test "it deletes a user, all follow relationships and all activities", %{user: user} do
|
||||
test "it deactivates a user, all follow relationships and all activities", %{user: user} do
|
||||
follower = insert(:user)
|
||||
{:ok, follower} = User.follow(follower, user)
|
||||
|
||||
|
@ -1164,8 +1155,7 @@ test "it deletes a user, all follow relationships and all activities", %{user: u
|
|||
follower = User.get_cached_by_id(follower.id)
|
||||
|
||||
refute User.following?(follower, user)
|
||||
refute User.get_by_id(user.id)
|
||||
assert {:ok, nil} == Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
|
||||
assert %{deactivated: true} = User.get_by_id(user.id)
|
||||
|
||||
user_activities =
|
||||
user.ap_id
|
||||
|
|
|
@ -1055,12 +1055,12 @@ test "it works for more than 10 users", %{conn: conn} do
|
|||
assert result["totalItems"] == 15
|
||||
end
|
||||
|
||||
test "returns 403 if requester is not logged in", %{conn: conn} do
|
||||
test "does not require authentication", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn
|
||||
|> get("/users/#{user.nickname}/followers")
|
||||
|> json_response(403)
|
||||
|> json_response(200)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1152,12 +1152,12 @@ test "it works for more than 10 users", %{conn: conn} do
|
|||
assert result["totalItems"] == 15
|
||||
end
|
||||
|
||||
test "returns 403 if requester is not logged in", %{conn: conn} do
|
||||
test "does not require authentication", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn
|
||||
|> get("/users/#{user.nickname}/following")
|
||||
|> json_response(403)
|
||||
|> json_response(200)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -872,7 +872,8 @@ test "it fails for incoming deletes with spoofed origin" do
|
|||
|
||||
@tag capture_log: true
|
||||
test "it works for incoming user deletes" do
|
||||
%{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
|
||||
%{ap_id: ap_id} =
|
||||
insert(:user, ap_id: "http://mastodon.example.org/users/admin", local: false)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete-user.json")
|
||||
|
@ -1221,6 +1222,35 @@ test "it rejects activities without a valid ID" do
|
|||
:error = Transmogrifier.handle_incoming(data)
|
||||
end
|
||||
|
||||
test "skip converting the content when it is nil" do
|
||||
object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
|
||||
|
||||
{:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
|
||||
|
||||
result =
|
||||
Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
|
||||
|
||||
assert result["content"] == nil
|
||||
end
|
||||
|
||||
test "it converts content of object to html" do
|
||||
object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
|
||||
|
||||
{:ok, %{"content" => content_markdown}} =
|
||||
Fetcher.fetch_and_contain_remote_object_from_id(object_id)
|
||||
|
||||
{:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
|
||||
Fetcher.fetch_object_from_id(object_id)
|
||||
|
||||
assert content_markdown ==
|
||||
"Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..."
|
||||
|
||||
assert content ==
|
||||
"<p>Support this and our other Michigan!/usr/group videos and meetings. Learn more at <a href=\"http://mug.org/membership\">http://mug.org/membership</a></p><p>Twenty Years in Jail: FreeBSD’s Jails, Then and Now</p><p>Jails started as a limited virtualization system, but over the last two years they’ve…</p>"
|
||||
|
||||
assert object.data["mediaType"] == "text/html"
|
||||
end
|
||||
|
||||
test "it remaps video URLs as attachments if necessary" do
|
||||
{:ok, object} =
|
||||
Fetcher.fetch_object_from_id(
|
||||
|
|
|
@ -1347,9 +1347,9 @@ test "returns report by its id", %{conn: conn} do
|
|||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
response =
|
||||
|
@ -1374,16 +1374,16 @@ test "returns 404 when report id is invalid", %{conn: conn} do
|
|||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
{:ok, %{id: second_report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel very offended",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel very offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
%{
|
||||
|
@ -1523,9 +1523,9 @@ test "returns reports", %{conn: conn} do
|
|||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
response =
|
||||
|
@ -1547,15 +1547,15 @@ test "returns reports with specified state", %{conn: conn} do
|
|||
|
||||
{:ok, %{id: first_report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
{:ok, %{id: second_report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I don't like this user"
|
||||
account_id: target_user.id,
|
||||
comment: "I don't like this user"
|
||||
})
|
||||
|
||||
CommonAPI.update_report_state(second_report_id, "closed")
|
||||
|
@ -3431,9 +3431,9 @@ test "it resend emails for two users", %{conn: conn, admin: admin} do
|
|||
|
||||
{:ok, %{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
|
||||
|
|
|
@ -15,7 +15,7 @@ test "renders a report" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id})
|
||||
{:ok, activity} = CommonAPI.report(user, %{account_id: other_user.id})
|
||||
|
||||
expected = %{
|
||||
content: nil,
|
||||
|
@ -48,7 +48,7 @@ test "includes reported statuses" do
|
|||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "toot"})
|
||||
|
||||
{:ok, report_activity} =
|
||||
CommonAPI.report(user, %{"account_id" => other_user.id, "status_ids" => [activity.id]})
|
||||
CommonAPI.report(user, %{account_id: other_user.id, status_ids: [activity.id]})
|
||||
|
||||
other_user = Pleroma.User.get_by_id(other_user.id)
|
||||
|
||||
|
@ -81,7 +81,7 @@ test "renders report's state" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id})
|
||||
{:ok, activity} = CommonAPI.report(user, %{account_id: other_user.id})
|
||||
{:ok, activity} = CommonAPI.update_report_state(activity.id, "closed")
|
||||
|
||||
assert %{state: "closed"} =
|
||||
|
@ -94,8 +94,8 @@ test "renders report description" do
|
|||
|
||||
{:ok, activity} =
|
||||
CommonAPI.report(user, %{
|
||||
"account_id" => other_user.id,
|
||||
"comment" => "posts are too good for this instance"
|
||||
account_id: other_user.id,
|
||||
comment: "posts are too good for this instance"
|
||||
})
|
||||
|
||||
assert %{content: "posts are too good for this instance"} =
|
||||
|
@ -108,8 +108,8 @@ test "sanitizes report description" do
|
|||
|
||||
{:ok, activity} =
|
||||
CommonAPI.report(user, %{
|
||||
"account_id" => other_user.id,
|
||||
"comment" => ""
|
||||
account_id: other_user.id,
|
||||
comment: ""
|
||||
})
|
||||
|
||||
data = Map.put(activity.data, "content", "<script> alert('hecked :D:D:D:D:D:D:D') </script>")
|
||||
|
@ -125,8 +125,8 @@ test "doesn't error out when the user doesn't exists" do
|
|||
|
||||
{:ok, activity} =
|
||||
CommonAPI.report(user, %{
|
||||
"account_id" => other_user.id,
|
||||
"comment" => ""
|
||||
account_id: other_user.id,
|
||||
comment: ""
|
||||
})
|
||||
|
||||
Pleroma.User.delete(other_user)
|
||||
|
|
|
@ -485,9 +485,9 @@ test "creates a report" do
|
|||
comment = "foobar"
|
||||
|
||||
report_data = %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => comment,
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: comment,
|
||||
status_ids: [activity.id]
|
||||
}
|
||||
|
||||
note_obj = %{
|
||||
|
@ -517,9 +517,9 @@ test "updates report state" do
|
|||
|
||||
{:ok, %Activity{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
{:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
|
||||
|
@ -538,9 +538,9 @@ test "does not update report state when state is unsupported" do
|
|||
|
||||
{:ok, %Activity{id: report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
|
||||
|
@ -552,16 +552,16 @@ test "updates state of multiple reports" do
|
|||
|
||||
{:ok, %Activity{id: first_report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel offended",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel offended",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
{:ok, %Activity{id: second_report_id}} =
|
||||
CommonAPI.report(reporter, %{
|
||||
"account_id" => target_user.id,
|
||||
"comment" => "I feel very offended!",
|
||||
"status_ids" => [activity.id]
|
||||
account_id: target_user.id,
|
||||
comment: "I feel very offended!",
|
||||
status_ids: [activity.id]
|
||||
})
|
||||
|
||||
{:ok, report_ids} =
|
||||
|
@ -697,6 +697,14 @@ test "after rejection, it sets all existing pending follow request states to 're
|
|||
assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
|
||||
assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
|
||||
end
|
||||
|
||||
test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
|
||||
user = insert(:user, locked: true)
|
||||
not_follower = insert(:user)
|
||||
CommonAPI.accept_follow_request(not_follower, user)
|
||||
|
||||
assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "vote/3" do
|
||||
|
|
|
@ -925,7 +925,8 @@ test "returns bad_request if missing email params when :account_activation_requi
|
|||
|> Map.put(:remote_ip, {127, 0, 0, 5})
|
||||
|> post("/api/v1/accounts", Map.delete(valid_params, :email))
|
||||
|
||||
assert json_response_and_validate_schema(res, 400) == %{"error" => "Missing parameters"}
|
||||
assert json_response_and_validate_schema(res, 400) ==
|
||||
%{"error" => "Missing parameter: email"}
|
||||
|
||||
res =
|
||||
conn
|
||||
|
@ -1093,6 +1094,91 @@ test "respects rate limit setting", %{conn: conn} do
|
|||
end
|
||||
end
|
||||
|
||||
describe "create account with enabled captcha" do
|
||||
setup %{conn: conn} do
|
||||
app_token = insert(:oauth_token, user: nil)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("authorization", "Bearer " <> app_token.token)
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|
||||
[conn: conn]
|
||||
end
|
||||
|
||||
setup do: clear_config([Pleroma.Captcha, :enabled], true)
|
||||
|
||||
test "creates an account and returns 200 if captcha is valid", %{conn: conn} do
|
||||
%{token: token, answer_data: answer_data} = Pleroma.Captcha.new()
|
||||
|
||||
params = %{
|
||||
username: "lain",
|
||||
email: "lain@example.org",
|
||||
password: "PlzDontHackLain",
|
||||
agreement: true,
|
||||
captcha_solution: Pleroma.Captcha.Mock.solution(),
|
||||
captcha_token: token,
|
||||
captcha_answer_data: answer_data
|
||||
}
|
||||
|
||||
assert %{
|
||||
"access_token" => access_token,
|
||||
"created_at" => _,
|
||||
"scope" => ["read"],
|
||||
"token_type" => "Bearer"
|
||||
} =
|
||||
conn
|
||||
|> post("/api/v1/accounts", params)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert Token |> Repo.get_by(token: access_token) |> Repo.preload(:user) |> Map.get(:user)
|
||||
|
||||
Cachex.del(:used_captcha_cache, token)
|
||||
end
|
||||
|
||||
test "returns 400 if any captcha field is not provided", %{conn: conn} do
|
||||
captcha_fields = [:captcha_solution, :captcha_token, :captcha_answer_data]
|
||||
|
||||
valid_params = %{
|
||||
username: "lain",
|
||||
email: "lain@example.org",
|
||||
password: "PlzDontHackLain",
|
||||
agreement: true,
|
||||
captcha_solution: "xx",
|
||||
captcha_token: "xx",
|
||||
captcha_answer_data: "xx"
|
||||
}
|
||||
|
||||
for field <- captcha_fields do
|
||||
expected = %{
|
||||
"error" => "{\"captcha\":[\"Invalid CAPTCHA (Missing parameter: #{field})\"]}"
|
||||
}
|
||||
|
||||
assert expected ==
|
||||
conn
|
||||
|> post("/api/v1/accounts", Map.delete(valid_params, field))
|
||||
|> json_response_and_validate_schema(:bad_request)
|
||||
end
|
||||
end
|
||||
|
||||
test "returns an error if captcha is invalid", %{conn: conn} do
|
||||
params = %{
|
||||
username: "lain",
|
||||
email: "lain@example.org",
|
||||
password: "PlzDontHackLain",
|
||||
agreement: true,
|
||||
captcha_solution: "cofe",
|
||||
captcha_token: "cofe",
|
||||
captcha_answer_data: "cofe"
|
||||
}
|
||||
|
||||
assert %{"error" => "{\"captcha\":[\"Invalid answer data\"]}"} ==
|
||||
conn
|
||||
|> post("/api/v1/accounts", params)
|
||||
|> json_response_and_validate_schema(:bad_request)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/v1/accounts/:id/lists - account_lists" do
|
||||
test "returns lists to which the account belongs" do
|
||||
%{user: user, conn: conn} = oauth_access(["read:lists"])
|
||||
|
|
|
@ -34,6 +34,10 @@ test "get instance information", %{conn: conn} do
|
|||
"banner_upload_limit" => _
|
||||
} = result
|
||||
|
||||
assert result["pleroma"]["metadata"]["features"]
|
||||
assert result["pleroma"]["metadata"]["federation"]
|
||||
assert result["pleroma"]["vapid_public_key"]
|
||||
|
||||
assert email == from_config_email
|
||||
end
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ test "does NOT render account/pleroma/relationship if this is disabled by defaul
|
|||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert Enum.all?(response, fn n ->
|
||||
get_in(n, ["account", "pleroma", "relationship"]) == %{}
|
||||
|
@ -50,7 +50,9 @@ test "list of notifications" do
|
|||
user.ap_id
|
||||
}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
|
||||
|
||||
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
|
||||
assert [%{"status" => %{"content" => response}} | _rest] =
|
||||
json_response_and_validate_schema(conn, 200)
|
||||
|
||||
assert response == expected_response
|
||||
end
|
||||
|
||||
|
@ -69,7 +71,7 @@ test "getting a single notification" do
|
|||
user.ap_id
|
||||
}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
|
||||
|
||||
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
|
||||
assert %{"status" => %{"content" => response}} = json_response_and_validate_schema(conn, 200)
|
||||
assert response == expected_response
|
||||
end
|
||||
|
||||
|
@ -84,9 +86,10 @@ test "dismissing a single notification (deprecated endpoint)" do
|
|||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/notifications/dismiss", %{"id" => to_string(notification.id)})
|
||||
|
||||
assert %{} = json_response(conn, 200)
|
||||
assert %{} = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "dismissing a single notification" do
|
||||
|
@ -102,7 +105,7 @@ test "dismissing a single notification" do
|
|||
|> assign(:user, user)
|
||||
|> post("/api/v1/notifications/#{notification.id}/dismiss")
|
||||
|
||||
assert %{} = json_response(conn, 200)
|
||||
assert %{} = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "clearing all notifications" do
|
||||
|
@ -115,11 +118,11 @@ test "clearing all notifications" do
|
|||
|
||||
ret_conn = post(conn, "/api/v1/notifications/clear")
|
||||
|
||||
assert %{} = json_response(ret_conn, 200)
|
||||
assert %{} = json_response_and_validate_schema(ret_conn, 200)
|
||||
|
||||
ret_conn = get(conn, "/api/v1/notifications")
|
||||
|
||||
assert all = json_response(ret_conn, 200)
|
||||
assert all = json_response_and_validate_schema(ret_conn, 200)
|
||||
assert all == []
|
||||
end
|
||||
|
||||
|
@ -143,7 +146,7 @@ test "paginates notifications using min_id, since_id, max_id, and limit" do
|
|||
result =
|
||||
conn
|
||||
|> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
|
||||
|
||||
|
@ -151,7 +154,7 @@ test "paginates notifications using min_id, since_id, max_id, and limit" do
|
|||
result =
|
||||
conn
|
||||
|> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
|
||||
|
||||
|
@ -159,7 +162,7 @@ test "paginates notifications using min_id, since_id, max_id, and limit" do
|
|||
result =
|
||||
conn
|
||||
|> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
|
||||
end
|
||||
|
@ -181,36 +184,28 @@ test "filters notifications for mentions" do
|
|||
{:ok, private_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"})
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["public", "unlisted", "private"]
|
||||
})
|
||||
query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "private"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?" <> query)
|
||||
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
|
||||
assert id == direct_activity.id
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["public", "unlisted", "direct"]
|
||||
})
|
||||
query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "direct"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?" <> query)
|
||||
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
|
||||
assert id == private_activity.id
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["public", "private", "direct"]
|
||||
})
|
||||
query = params_to_query(%{exclude_visibilities: ["public", "private", "direct"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?" <> query)
|
||||
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
|
||||
assert id == unlisted_activity.id
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{
|
||||
exclude_visibilities: ["unlisted", "private", "direct"]
|
||||
})
|
||||
query = params_to_query(%{exclude_visibilities: ["unlisted", "private", "direct"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?" <> query)
|
||||
|
||||
assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200)
|
||||
assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
|
||||
assert id == public_activity.id
|
||||
end
|
||||
|
||||
|
@ -237,8 +232,8 @@ test "filters notifications for Like activities" do
|
|||
|
||||
activity_ids =
|
||||
conn
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["direct"]})
|
||||
|> json_response(200)
|
||||
|> get("/api/v1/notifications?exclude_visibilities[]=direct")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
assert public_activity.id in activity_ids
|
||||
|
@ -248,8 +243,8 @@ test "filters notifications for Like activities" do
|
|||
|
||||
activity_ids =
|
||||
conn
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]})
|
||||
|> json_response(200)
|
||||
|> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
assert public_activity.id in activity_ids
|
||||
|
@ -259,8 +254,8 @@ test "filters notifications for Like activities" do
|
|||
|
||||
activity_ids =
|
||||
conn
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["private"]})
|
||||
|> json_response(200)
|
||||
|> get("/api/v1/notifications?exclude_visibilities[]=private")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
assert public_activity.id in activity_ids
|
||||
|
@ -270,8 +265,8 @@ test "filters notifications for Like activities" do
|
|||
|
||||
activity_ids =
|
||||
conn
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["public"]})
|
||||
|> json_response(200)
|
||||
|> get("/api/v1/notifications?exclude_visibilities[]=public")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
refute public_activity.id in activity_ids
|
||||
|
@ -295,8 +290,8 @@ test "filters notifications for Announce activities" do
|
|||
|
||||
activity_ids =
|
||||
conn
|
||||
|> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]})
|
||||
|> json_response(200)
|
||||
|> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|> Enum.map(& &1["status"]["id"])
|
||||
|
||||
assert public_activity.id in activity_ids
|
||||
|
@ -319,25 +314,27 @@ test "filters notifications using exclude_types" do
|
|||
reblog_notification_id = get_notification_id_by_activity(reblog_activity)
|
||||
follow_notification_id = get_notification_id_by_activity(follow_activity)
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
|
||||
query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?" <> query)
|
||||
|
||||
assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
|
||||
query = params_to_query(%{exclude_types: ["favourite", "reblog", "follow"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?" <> query)
|
||||
|
||||
assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^mention_notification_id}] =
|
||||
json_response_and_validate_schema(conn_res, 200)
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
|
||||
query = params_to_query(%{exclude_types: ["reblog", "follow", "mention"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?" <> query)
|
||||
|
||||
assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^favorite_notification_id}] =
|
||||
json_response_and_validate_schema(conn_res, 200)
|
||||
|
||||
conn_res =
|
||||
get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
|
||||
query = params_to_query(%{exclude_types: ["follow", "mention", "favourite"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?" <> query)
|
||||
|
||||
assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
|
||||
end
|
||||
|
||||
test "filters notifications using include_types" do
|
||||
|
@ -355,32 +352,34 @@ test "filters notifications using include_types" do
|
|||
reblog_notification_id = get_notification_id_by_activity(reblog_activity)
|
||||
follow_notification_id = get_notification_id_by_activity(follow_activity)
|
||||
|
||||
conn_res = get(conn, "/api/v1/notifications", %{include_types: ["follow"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?include_types[]=follow")
|
||||
|
||||
assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
|
||||
|
||||
conn_res = get(conn, "/api/v1/notifications", %{include_types: ["mention"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?include_types[]=mention")
|
||||
|
||||
assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^mention_notification_id}] =
|
||||
json_response_and_validate_schema(conn_res, 200)
|
||||
|
||||
conn_res = get(conn, "/api/v1/notifications", %{include_types: ["favourite"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?include_types[]=favourite")
|
||||
|
||||
assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^favorite_notification_id}] =
|
||||
json_response_and_validate_schema(conn_res, 200)
|
||||
|
||||
conn_res = get(conn, "/api/v1/notifications", %{include_types: ["reblog"]})
|
||||
conn_res = get(conn, "/api/v1/notifications?include_types[]=reblog")
|
||||
|
||||
assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
|
||||
assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
|
||||
|
||||
result = conn |> get("/api/v1/notifications") |> json_response(200)
|
||||
result = conn |> get("/api/v1/notifications") |> json_response_and_validate_schema(200)
|
||||
|
||||
assert length(result) == 4
|
||||
|
||||
query = params_to_query(%{include_types: ["follow", "mention", "favourite", "reblog"]})
|
||||
|
||||
result =
|
||||
conn
|
||||
|> get("/api/v1/notifications", %{
|
||||
include_types: ["follow", "mention", "favourite", "reblog"]
|
||||
})
|
||||
|> json_response(200)
|
||||
|> get("/api/v1/notifications?" <> query)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert length(result) == 4
|
||||
end
|
||||
|
@ -402,7 +401,7 @@ test "destroy multiple" do
|
|||
result =
|
||||
conn
|
||||
|> get("/api/v1/notifications")
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
|
||||
|
||||
|
@ -414,22 +413,19 @@ test "destroy multiple" do
|
|||
result =
|
||||
conn2
|
||||
|> get("/api/v1/notifications")
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
|
||||
|
||||
conn_destroy =
|
||||
conn
|
||||
|> delete("/api/v1/notifications/destroy_multiple", %{
|
||||
"ids" => [notification1_id, notification2_id]
|
||||
})
|
||||
query = params_to_query(%{ids: [notification1_id, notification2_id]})
|
||||
conn_destroy = delete(conn, "/api/v1/notifications/destroy_multiple?" <> query)
|
||||
|
||||
assert json_response(conn_destroy, 200) == %{}
|
||||
assert json_response_and_validate_schema(conn_destroy, 200) == %{}
|
||||
|
||||
result =
|
||||
conn2
|
||||
|> get("/api/v1/notifications")
|
||||
|> json_response(:ok)
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
|
||||
end
|
||||
|
@ -443,13 +439,13 @@ test "doesn't see notifications after muting user with notifications" do
|
|||
|
||||
ret_conn = get(conn, "/api/v1/notifications")
|
||||
|
||||
assert length(json_response(ret_conn, 200)) == 1
|
||||
assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
|
||||
|
||||
{:ok, _user_relationships} = User.mute(user, user2)
|
||||
|
||||
conn = get(conn, "/api/v1/notifications")
|
||||
|
||||
assert json_response(conn, 200) == []
|
||||
assert json_response_and_validate_schema(conn, 200) == []
|
||||
end
|
||||
|
||||
test "see notifications after muting user without notifications" do
|
||||
|
@ -461,13 +457,13 @@ test "see notifications after muting user without notifications" do
|
|||
|
||||
ret_conn = get(conn, "/api/v1/notifications")
|
||||
|
||||
assert length(json_response(ret_conn, 200)) == 1
|
||||
assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
|
||||
|
||||
{:ok, _user_relationships} = User.mute(user, user2, false)
|
||||
|
||||
conn = get(conn, "/api/v1/notifications")
|
||||
|
||||
assert length(json_response(conn, 200)) == 1
|
||||
assert length(json_response_and_validate_schema(conn, 200)) == 1
|
||||
end
|
||||
|
||||
test "see notifications after muting user with notifications and with_muted parameter" do
|
||||
|
@ -479,13 +475,13 @@ test "see notifications after muting user with notifications and with_muted para
|
|||
|
||||
ret_conn = get(conn, "/api/v1/notifications")
|
||||
|
||||
assert length(json_response(ret_conn, 200)) == 1
|
||||
assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
|
||||
|
||||
{:ok, _user_relationships} = User.mute(user, user2)
|
||||
|
||||
conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
|
||||
conn = get(conn, "/api/v1/notifications?with_muted=true")
|
||||
|
||||
assert length(json_response(conn, 200)) == 1
|
||||
assert length(json_response_and_validate_schema(conn, 200)) == 1
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
|
@ -512,7 +508,7 @@ test "see move notifications" do
|
|||
|
||||
conn = get(conn, "/api/v1/notifications")
|
||||
|
||||
assert length(json_response(conn, 200)) == 1
|
||||
assert length(json_response_and_validate_schema(conn, 200)) == 1
|
||||
end
|
||||
|
||||
describe "link headers" do
|
||||
|
@ -538,10 +534,10 @@ test "preserves parameters in link headers" do
|
|||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications", %{media_only: true})
|
||||
|> get("/api/v1/notifications?limit=5")
|
||||
|
||||
assert [link_header] = get_resp_header(conn, "link")
|
||||
assert link_header =~ ~r/media_only=true/
|
||||
assert link_header =~ ~r/limit=5/
|
||||
assert link_header =~ ~r/min_id=#{notification2.id}/
|
||||
assert link_header =~ ~r/max_id=#{notification1.id}/
|
||||
end
|
||||
|
@ -560,14 +556,14 @@ test "account_id" do
|
|||
assert [%{"account" => %{"id" => ^account_id}}] =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications", %{account_id: account_id})
|
||||
|> json_response(200)
|
||||
|> get("/api/v1/notifications?account_id=#{account_id}")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert %{"error" => "Account is not found"} =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications", %{account_id: "cofe"})
|
||||
|> json_response(404)
|
||||
|> get("/api/v1/notifications?account_id=cofe")
|
||||
|> json_response_and_validate_schema(404)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -577,4 +573,11 @@ defp get_notification_id_by_activity(%{id: id}) do
|
|||
|> Map.get(:id)
|
||||
|> to_string()
|
||||
end
|
||||
|
||||
defp params_to_query(%{} = params) do
|
||||
Enum.map_join(params, "&", fn
|
||||
{k, v} when is_list(v) -> Enum.map_join(v, "&", &"#{k}[]=#{&1}")
|
||||
{k, v} -> k <> "=" <> v
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,8 +22,9 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do
|
|||
test "submit a basic report", %{conn: conn, target_user: target_user} do
|
||||
assert %{"action_taken" => false, "id" => _} =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/reports", %{"account_id" => target_user.id})
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
|
||||
test "submit a report with statuses and comment", %{
|
||||
|
@ -33,23 +34,25 @@ test "submit a report with statuses and comment", %{
|
|||
} do
|
||||
assert %{"action_taken" => false, "id" => _} =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/reports", %{
|
||||
"account_id" => target_user.id,
|
||||
"status_ids" => [activity.id],
|
||||
"comment" => "bad status!",
|
||||
"forward" => "false"
|
||||
})
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
|
||||
test "account_id is required", %{
|
||||
conn: conn,
|
||||
activity: activity
|
||||
} do
|
||||
assert %{"error" => "Valid `account_id` required"} =
|
||||
assert %{"error" => "Missing field: account_id."} =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/reports", %{"status_ids" => [activity.id]})
|
||||
|> json_response(400)
|
||||
|> json_response_and_validate_schema(400)
|
||||
end
|
||||
|
||||
test "comment must be up to the size specified in the config", %{
|
||||
|
@ -63,17 +66,21 @@ test "comment must be up to the size specified in the config", %{
|
|||
|
||||
assert ^error =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
|
||||
|> json_response(400)
|
||||
|> json_response_and_validate_schema(400)
|
||||
end
|
||||
|
||||
test "returns error when account is not exist", %{
|
||||
conn: conn,
|
||||
activity: activity
|
||||
} do
|
||||
conn = post(conn, "/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"})
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"})
|
||||
|
||||
assert json_response(conn, 400) == %{"error" => "Account not found"}
|
||||
assert json_response_and_validate_schema(conn, 400) == %{"error" => "Account not found"}
|
||||
end
|
||||
|
||||
test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_user} do
|
||||
|
@ -81,7 +88,8 @@ test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_
|
|||
|
||||
assert %{"action_taken" => false, "id" => _} =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/reports", %{"account_id" => target_user.id})
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.Config
|
||||
|
||||
setup do: oauth_access(["read"])
|
||||
|
||||
test "returns empty result", %{conn: conn} do
|
||||
|
|
|
@ -18,7 +18,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
|
|||
|
||||
test "it registers a new user and returns the user." do
|
||||
data = %{
|
||||
:nickname => "lain",
|
||||
:username => "lain",
|
||||
:email => "lain@wired.jp",
|
||||
:fullname => "lain iwakura",
|
||||
:password => "bear",
|
||||
|
@ -35,7 +35,7 @@ test "it registers a new user and returns the user." do
|
|||
|
||||
test "it registers a new user with empty string in bio and returns the user." do
|
||||
data = %{
|
||||
:nickname => "lain",
|
||||
:username => "lain",
|
||||
:email => "lain@wired.jp",
|
||||
:fullname => "lain iwakura",
|
||||
:bio => "",
|
||||
|
@ -60,7 +60,7 @@ test "it sends confirmation email if :account_activation_required is specified i
|
|||
end
|
||||
|
||||
data = %{
|
||||
:nickname => "lain",
|
||||
:username => "lain",
|
||||
:email => "lain@wired.jp",
|
||||
:fullname => "lain iwakura",
|
||||
:bio => "",
|
||||
|
@ -87,7 +87,7 @@ test "it sends confirmation email if :account_activation_required is specified i
|
|||
|
||||
test "it registers a new user and parses mentions in the bio" do
|
||||
data1 = %{
|
||||
:nickname => "john",
|
||||
:username => "john",
|
||||
:email => "john@gmail.com",
|
||||
:fullname => "John Doe",
|
||||
:bio => "test",
|
||||
|
@ -98,7 +98,7 @@ test "it registers a new user and parses mentions in the bio" do
|
|||
{:ok, user1} = TwitterAPI.register_user(data1)
|
||||
|
||||
data2 = %{
|
||||
:nickname => "lain",
|
||||
:username => "lain",
|
||||
:email => "lain@wired.jp",
|
||||
:fullname => "lain iwakura",
|
||||
:bio => "@john test",
|
||||
|
@ -123,7 +123,7 @@ test "returns user on success" do
|
|||
{:ok, invite} = UserInviteToken.create_invite()
|
||||
|
||||
data = %{
|
||||
:nickname => "vinny",
|
||||
:username => "vinny",
|
||||
:email => "pasta@pizza.vs",
|
||||
:fullname => "Vinny Vinesauce",
|
||||
:bio => "streamer",
|
||||
|
@ -145,7 +145,7 @@ test "returns user on success" do
|
|||
|
||||
test "returns error on invalid token" do
|
||||
data = %{
|
||||
:nickname => "GrimReaper",
|
||||
:username => "GrimReaper",
|
||||
:email => "death@reapers.afterlife",
|
||||
:fullname => "Reaper Grim",
|
||||
:bio => "Your time has come",
|
||||
|
@ -165,7 +165,7 @@ test "returns error on expired token" do
|
|||
UserInviteToken.update_invite!(invite, used: true)
|
||||
|
||||
data = %{
|
||||
:nickname => "GrimReaper",
|
||||
:username => "GrimReaper",
|
||||
:email => "death@reapers.afterlife",
|
||||
:fullname => "Reaper Grim",
|
||||
:bio => "Your time has come",
|
||||
|
@ -186,7 +186,7 @@ test "returns error on expired token" do
|
|||
|
||||
setup do
|
||||
data = %{
|
||||
:nickname => "vinny",
|
||||
:username => "vinny",
|
||||
:email => "pasta@pizza.vs",
|
||||
:fullname => "Vinny Vinesauce",
|
||||
:bio => "streamer",
|
||||
|
@ -250,7 +250,7 @@ test "returns user on success, after him registration fails" do
|
|||
UserInviteToken.update_invite!(invite, uses: 99)
|
||||
|
||||
data = %{
|
||||
:nickname => "vinny",
|
||||
:username => "vinny",
|
||||
:email => "pasta@pizza.vs",
|
||||
:fullname => "Vinny Vinesauce",
|
||||
:bio => "streamer",
|
||||
|
@ -269,7 +269,7 @@ test "returns user on success, after him registration fails" do
|
|||
AccountView.render("show.json", %{user: fetched_user})
|
||||
|
||||
data = %{
|
||||
:nickname => "GrimReaper",
|
||||
:username => "GrimReaper",
|
||||
:email => "death@reapers.afterlife",
|
||||
:fullname => "Reaper Grim",
|
||||
:bio => "Your time has come",
|
||||
|
@ -292,7 +292,7 @@ test "returns user on success" do
|
|||
{:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100})
|
||||
|
||||
data = %{
|
||||
:nickname => "vinny",
|
||||
:username => "vinny",
|
||||
:email => "pasta@pizza.vs",
|
||||
:fullname => "Vinny Vinesauce",
|
||||
:bio => "streamer",
|
||||
|
@ -317,7 +317,7 @@ test "error after max uses" do
|
|||
UserInviteToken.update_invite!(invite, uses: 99)
|
||||
|
||||
data = %{
|
||||
:nickname => "vinny",
|
||||
:username => "vinny",
|
||||
:email => "pasta@pizza.vs",
|
||||
:fullname => "Vinny Vinesauce",
|
||||
:bio => "streamer",
|
||||
|
@ -335,7 +335,7 @@ test "error after max uses" do
|
|||
AccountView.render("show.json", %{user: fetched_user})
|
||||
|
||||
data = %{
|
||||
:nickname => "GrimReaper",
|
||||
:username => "GrimReaper",
|
||||
:email => "death@reapers.afterlife",
|
||||
:fullname => "Reaper Grim",
|
||||
:bio => "Your time has come",
|
||||
|
@ -355,7 +355,7 @@ test "returns error on overdue date" do
|
|||
UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100})
|
||||
|
||||
data = %{
|
||||
:nickname => "GrimReaper",
|
||||
:username => "GrimReaper",
|
||||
:email => "death@reapers.afterlife",
|
||||
:fullname => "Reaper Grim",
|
||||
:bio => "Your time has come",
|
||||
|
@ -377,7 +377,7 @@ test "returns error on with overdue date and after max" do
|
|||
UserInviteToken.update_invite!(invite, uses: 100)
|
||||
|
||||
data = %{
|
||||
:nickname => "GrimReaper",
|
||||
:username => "GrimReaper",
|
||||
:email => "death@reapers.afterlife",
|
||||
:fullname => "Reaper Grim",
|
||||
:bio => "Your time has come",
|
||||
|
@ -395,16 +395,15 @@ test "returns error on with overdue date and after max" do
|
|||
|
||||
test "it returns the error on registration problems" do
|
||||
data = %{
|
||||
:nickname => "lain",
|
||||
:username => "lain",
|
||||
:email => "lain@wired.jp",
|
||||
:fullname => "lain iwakura",
|
||||
:bio => "close the world.",
|
||||
:password => "bear"
|
||||
:bio => "close the world."
|
||||
}
|
||||
|
||||
{:error, error_object} = TwitterAPI.register_user(data)
|
||||
{:error, error} = TwitterAPI.register_user(data)
|
||||
|
||||
assert is_binary(error_object[:error])
|
||||
assert is_binary(error)
|
||||
refute User.get_cached_by_nickname("lain")
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue