aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/plugs/http_security_plug.ex2
-rw-r--r--lib/pleroma/stats.ex18
-rw-r--r--lib/pleroma/user.ex14
-rw-r--r--lib/pleroma/user/query.ex4
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex83
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex7
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex13
-rw-r--r--lib/pleroma/web/admin_api/views/status_view.ex18
-rw-r--r--lib/pleroma/web/api_spec.ex8
-rw-r--r--lib/pleroma/web/api_spec/helpers.ex22
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex701
-rw-r--r--lib/pleroma/web/api_spec/operations/app_operation.ex66
-rw-r--r--lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex67
-rw-r--r--lib/pleroma/web/api_spec/operations/domain_block_operation.ex31
-rw-r--r--lib/pleroma/web/api_spec/render_error.ex231
-rw-r--r--lib/pleroma/web/api_spec/schemas/account.ex167
-rw-r--r--lib/pleroma/web/api_spec/schemas/account_field.ex26
-rw-r--r--lib/pleroma/web/api_spec/schemas/account_relationship.ex44
-rw-r--r--lib/pleroma/web/api_spec/schemas/actor_type.ex13
-rw-r--r--lib/pleroma/web/api_spec/schemas/api_error.ex (renamed from lib/pleroma/web/api_spec/schemas/domain_block_request.ex)13
-rw-r--r--lib/pleroma/web/api_spec/schemas/app_create_request.ex33
-rw-r--r--lib/pleroma/web/api_spec/schemas/app_create_response.ex33
-rw-r--r--lib/pleroma/web/api_spec/schemas/boolean_like.ex36
-rw-r--r--lib/pleroma/web/api_spec/schemas/custom_emoji.ex30
-rw-r--r--lib/pleroma/web/api_spec/schemas/custom_emojis_response.ex42
-rw-r--r--lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex16
-rw-r--r--lib/pleroma/web/api_spec/schemas/emoji.ex29
-rw-r--r--lib/pleroma/web/api_spec/schemas/flake_id.ex14
-rw-r--r--lib/pleroma/web/api_spec/schemas/poll.ex36
-rw-r--r--lib/pleroma/web/api_spec/schemas/status.ex226
-rw-r--r--lib/pleroma/web/api_spec/schemas/visibility_scope.ex14
-rw-r--r--lib/pleroma/web/common_api/activity_draft.ex16
-rw-r--r--lib/pleroma/web/common_api/common_api.ex23
-rw-r--r--lib/pleroma/web/common_api/utils.ex18
-rw-r--r--lib/pleroma/web/controller_helper.ex5
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex104
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/status_controller.ex3
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex22
-rw-r--r--lib/pleroma/web/mongooseim/mongoose_im_controller.ex4
-rw-r--r--lib/pleroma/web/oauth/scopes.ex6
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex108
42 files changed, 1972 insertions, 396 deletions
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 81e6b4f2a..6462797b6 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -75,7 +75,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
"default-src 'none'",
"base-uri 'self'",
"frame-ancestors 'none'",
- "img-src 'self' data: https:",
+ "img-src 'self' data: blob: https:",
"media-src 'self' https:",
"style-src 'self' 'unsafe-inline'",
"font-src 'self'",
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index 4446562ac..8d2809bbb 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -45,11 +45,11 @@ defmodule Pleroma.Stats do
end
def init(_args) do
- {:ok, get_stat_data()}
+ {:ok, calculate_stat_data()}
end
def handle_call(:force_update, _from, _state) do
- new_stats = get_stat_data()
+ new_stats = calculate_stat_data()
{:reply, new_stats, new_stats}
end
@@ -58,12 +58,12 @@ defmodule Pleroma.Stats do
end
def handle_cast(:run_update, _state) do
- new_stats = get_stat_data()
+ new_stats = calculate_stat_data()
{:noreply, new_stats}
end
- defp get_stat_data do
+ def calculate_stat_data do
peers =
from(
u in User,
@@ -77,7 +77,15 @@ defmodule Pleroma.Stats do
status_count = Repo.aggregate(User.Query.build(%{local: true}), :sum, :note_count)
- user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
+ users_query =
+ from(u in User,
+ where: u.deactivated != true,
+ where: u.local == true,
+ where: not is_nil(u.nickname),
+ where: not u.invisible
+ )
+
+ user_count = Repo.aggregate(users_query, :count, :id)
%{
peers: peers,
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 477237756..b451202b2 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -832,6 +832,7 @@ defmodule Pleroma.User do
def set_cache(%User{} = user) do
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
+ Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
{:ok, user}
end
@@ -847,9 +848,22 @@ defmodule Pleroma.User do
end
end
+ def get_user_friends_ap_ids(user) do
+ from(u in User.get_friends_query(user), select: u.ap_id)
+ |> Repo.all()
+ end
+
+ @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
+ def get_cached_user_friends_ap_ids(user) do
+ Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
+ get_user_friends_ap_ids(user)
+ end)
+ end
+
def invalidate_cache(user) do
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
Cachex.del(:user_cache, "nickname:#{user.nickname}")
+ Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
end
@spec get_cached_by_ap_id(String.t()) :: User.t() | nil
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index ec88088cf..ac77aab71 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -54,13 +54,13 @@ defmodule Pleroma.User.Query do
select: term(),
limit: pos_integer()
}
- | %{}
+ | map()
@ilike_criteria [:nickname, :name, :query]
@equal_criteria [:email]
@contains_criteria [:ap_id, :nickname]
- @spec build(criteria()) :: Query.t()
+ @spec build(Query.t(), criteria()) :: Query.t()
def build(query \\ base_query(), criteria) do
prepare_query(query, criteria)
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 4a133498e..1f4a09370 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -398,36 +398,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
- @spec like(User.t(), Object.t(), String.t() | nil, boolean()) ::
- {:ok, Activity.t(), Object.t()} | {:error, any()}
- def like(user, object, activity_id \\ nil, local \\ true) do
- with {:ok, result} <- Repo.transaction(fn -> do_like(user, object, activity_id, local) end) do
- result
- end
- end
-
- defp do_like(
- %User{ap_id: ap_id} = user,
- %Object{data: %{"id" => _}} = object,
- activity_id,
- local
- ) do
- with nil <- get_existing_like(ap_id, object),
- like_data <- make_like_data(user, object, activity_id),
- {:ok, activity} <- insert(like_data, local),
- {:ok, object} <- add_like_to_object(activity, object),
- :ok <- maybe_federate(activity) do
- {:ok, activity, object}
- else
- %Activity{} = activity ->
- {:ok, activity, object}
-
- {:error, error} ->
- Repo.rollback(error)
- end
- end
-
@spec unlike(User.t(), Object.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t(), Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}
def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do
@@ -468,6 +438,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp do_announce(user, object, activity_id, local, public) do
with true <- is_announceable?(object, user, public),
+ object <- Object.get_by_id(object.id),
announce_data <- make_announce_data(user, object, activity_id, public),
{:ok, activity} <- insert(announce_data, local),
{:ok, object} <- add_announce_to_object(activity, object),
@@ -854,7 +825,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
- when visibility not in @valid_visibilities do
+ when visibility not in [nil | @valid_visibilities] do
Logger.error("Could not exclude visibility to #{visibility}")
query
end
@@ -1061,7 +1032,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
raise "Can't use the child object without preloading!"
end
- defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
+ defp restrict_media(query, %{"only_media" => val}) when val in [true, "true", "1"] do
from(
[_activity, object] in query,
where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
@@ -1070,16 +1041,51 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_media(query, _), do: query
- defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
+ defp restrict_replies(query, %{"exclude_replies" => val}) when val in [true, "true", "1"] do
from(
[_activity, object] in query,
where: fragment("?->>'inReplyTo' is null", object.data)
)
end
+ defp restrict_replies(query, %{
+ "reply_filtering_user" => user,
+ "reply_visibility" => "self"
+ }) do
+ from(
+ [activity, object] in query,
+ where:
+ fragment(
+ "?->>'inReplyTo' is null OR ? = ANY(?)",
+ object.data,
+ ^user.ap_id,
+ activity.recipients
+ )
+ )
+ end
+
+ defp restrict_replies(query, %{
+ "reply_filtering_user" => user,
+ "reply_visibility" => "following"
+ }) do
+ from(
+ [activity, object] in query,
+ where:
+ fragment(
+ "?->>'inReplyTo' is null OR ? && array_remove(?, ?) OR ? = ?",
+ object.data,
+ ^[user.ap_id | User.get_cached_user_friends_ap_ids(user)],
+ activity.recipients,
+ activity.actor,
+ activity.actor,
+ ^user.ap_id
+ )
+ )
+ end
+
defp restrict_replies(query, _), do: query
- defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
+ defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val in [true, "true", "1"] do
from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
end
@@ -1158,7 +1164,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
)
end
- defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
+ # TODO: when all endpoints migrated to OpenAPI compare `pinned` with `true` (boolean) only,
+ # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2'
+ # and `restrict_muted/2`
+
+ defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids})
+ when pinned in [true, "true", "1"] do
from(activity in query, where: activity.id in ^ids)
end
@@ -1291,6 +1302,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> maybe_set_thread_muted_field(opts)
|> maybe_order(opts)
|> restrict_recipients(recipients, opts["user"])
+ |> restrict_replies(opts)
|> restrict_tag(opts)
|> restrict_tag_reject(opts)
|> restrict_tag_all(opts)
@@ -1305,7 +1317,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_media(opts)
|> restrict_visibility(opts)
|> restrict_thread_visibility(opts, config)
- |> restrict_replies(opts)
|> restrict_reblogs(opts)
|> restrict_pinned(opts)
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 8b9eb4a2c..d625530ec 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -12,8 +12,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Plugs.EnsureAuthenticatedPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.ObjectView
+ alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.UserView
@@ -421,7 +423,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
with %Object{} = object <- Object.normalize(params["object"]),
- {:ok, activity, _object} <- ActivityPub.like(user, object) do
+ {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
+ {_, {:ok, %Activity{} = activity, _meta}} <-
+ {:common_pipeline,
+ Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
{:ok, activity}
else
_ -> {:error, dgettext("errors", "Can't like object")}
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 6a8f1af96..5981e7545 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -15,12 +15,17 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Add like to object
# - Set up notification
def handle(%{data: %{"type" => "Like"}} = object, meta) do
- liked_object = Object.get_by_ap_id(object.data["object"])
- Utils.add_like_to_object(object, liked_object)
+ {:ok, result} =
+ Pleroma.Repo.transaction(fn ->
+ liked_object = Object.get_by_ap_id(object.data["object"])
+ Utils.add_like_to_object(object, liked_object)
- Notification.create_notifications(object)
+ Notification.create_notifications(object)
- {:ok, object, meta}
+ {:ok, object, meta}
+ end)
+
+ result
end
# Nothing to do
diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex
index 360ddc22c..3637dee24 100644
--- a/lib/pleroma/web/admin_api/views/status_view.ex
+++ b/lib/pleroma/web/admin_api/views/status_view.ex
@@ -8,15 +8,16 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
require Pleroma.Constants
alias Pleroma.User
+ alias Pleroma.Web.MastodonAPI.StatusView
def render("index.json", opts) do
safe_render_many(opts.activities, __MODULE__, "show.json", opts)
end
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
- user = get_user(activity.data["actor"])
+ user = StatusView.get_user(activity.data["actor"])
- Pleroma.Web.MastodonAPI.StatusView.render("show.json", opts)
+ StatusView.render("show.json", opts)
|> Map.merge(%{account: merge_account_views(user)})
end
@@ -26,17 +27,4 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
end
defp merge_account_views(_), do: %{}
-
- defp get_user(ap_id) do
- cond do
- user = User.get_cached_by_ap_id(ap_id) ->
- user
-
- user = User.get_by_guessed_nickname(ap_id) ->
- user
-
- true ->
- User.error_user(ap_id)
- end
- end
end
diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
index 3890489e3..b3c1e3ea2 100644
--- a/lib/pleroma/web/api_spec.ex
+++ b/lib/pleroma/web/api_spec.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.ApiSpec do
alias OpenApiSpex.OpenApi
+ alias OpenApiSpex.Operation
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
@@ -24,6 +25,13 @@ defmodule Pleroma.Web.ApiSpec do
# populate the paths from a phoenix router
paths: OpenApiSpex.Paths.from_router(Router),
components: %OpenApiSpex.Components{
+ parameters: %{
+ "accountIdOrNickname" =>
+ Operation.parameter(:id, :path, :string, "Account ID or nickname",
+ example: "123",
+ required: true
+ )
+ },
securitySchemes: %{
"oAuth" => %OpenApiSpex.SecurityScheme{
type: "oauth2",
diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
index 7348dcbee..ce40fb9e8 100644
--- a/lib/pleroma/web/api_spec/helpers.ex
+++ b/lib/pleroma/web/api_spec/helpers.ex
@@ -3,6 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Helpers do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+
def request_body(description, schema_ref, opts \\ []) do
media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
@@ -24,4 +27,23 @@ defmodule Pleroma.Web.ApiSpec.Helpers do
required: opts[:required] || false
}
end
+
+ def pagination_params do
+ [
+ Operation.parameter(:max_id, :query, :string, "Return items older than this ID"),
+ Operation.parameter(:min_id, :query, :string, "Return the oldest items newer than this ID"),
+ Operation.parameter(
+ :since_id,
+ :query,
+ :string,
+ "Return the newest items newer than this ID"
+ ),
+ Operation.parameter(
+ :limit,
+ :query,
+ %Schema{type: :integer, default: 20, maximum: 40},
+ "Limit"
+ )
+ ]
+ end
end
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
new file mode 100644
index 000000000..2efe6e901
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -0,0 +1,701 @@
+# 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.AccountOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Reference
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Account
+ alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
+ alias Pleroma.Web.ApiSpec.Schemas.ActorType
+ 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
+
+ @spec open_api_operation(atom) :: Operation.t()
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ @spec create_operation() :: Operation.t()
+ def create_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Register an account",
+ description:
+ "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
+ operationId: "AccountController.create",
+ requestBody: request_body("Parameters", create_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Account", "application/json", create_response()),
+ 400 => Operation.response("Error", "application/json", ApiError),
+ 403 => Operation.response("Error", "application/json", ApiError),
+ 429 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def verify_credentials_operation do
+ %Operation{
+ tags: ["accounts"],
+ description: "Test to make sure that the user token works.",
+ summary: "Verify account credentials",
+ operationId: "AccountController.verify_credentials",
+ security: [%{"oAuth" => ["read:accounts"]}],
+ responses: %{
+ 200 => Operation.response("Account", "application/json", Account)
+ }
+ }
+ end
+
+ def update_credentials_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Update account credentials",
+ description: "Update the user's display and preferences.",
+ operationId: "AccountController.update_credentials",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ requestBody: request_body("Parameters", update_creadentials_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Account", "application/json", Account),
+ 403 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def relationships_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Check relationships to other accounts",
+ operationId: "AccountController.relationships",
+ description: "Find out whether a given account is followed, blocked, muted, etc.",
+ security: [%{"oAuth" => ["read:follows"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :query,
+ %Schema{
+ oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
+ },
+ "Account IDs",
+ example: "123"
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Account", "application/json", array_of_relationships())
+ }
+ }
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Account",
+ operationId: "AccountController.show",
+ description: "View information about a profile.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Account", "application/json", Account),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def statuses_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Statuses",
+ operationId: "AccountController.statuses",
+ description:
+ "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
+ parameters:
+ [
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
+ Operation.parameter(:tagged, :query, :string, "With tag"),
+ Operation.parameter(
+ :only_media,
+ :query,
+ BooleanLike,
+ "Include only statuses with media attached"
+ ),
+ Operation.parameter(
+ :with_muted,
+ :query,
+ BooleanLike,
+ "Include statuses from muted acccounts."
+ ),
+ Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
+ Operation.parameter(
+ :exclude_visibilities,
+ :query,
+ %Schema{type: :array, items: VisibilityScope},
+ "Exclude visibilities"
+ )
+ ] ++ pagination_params(),
+ responses: %{
+ 200 => Operation.response("Statuses", "application/json", array_of_statuses()),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def followers_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Followers",
+ operationId: "AccountController.followers",
+ security: [%{"oAuth" => ["read:accounts"]}],
+ description:
+ "Accounts which follow the given account, if network is not hidden by the account owner.",
+ parameters:
+ [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
+ responses: %{
+ 200 => Operation.response("Accounts", "application/json", array_of_accounts())
+ }
+ }
+ end
+
+ def following_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Following",
+ operationId: "AccountController.following",
+ security: [%{"oAuth" => ["read:accounts"]}],
+ description:
+ "Accounts which the given account is following, if network is not hidden by the account owner.",
+ parameters:
+ [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
+ responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
+ }
+ end
+
+ def lists_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Lists containing this account",
+ operationId: "AccountController.lists",
+ security: [%{"oAuth" => ["read:lists"]}],
+ description: "User lists that you have added this account to.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())}
+ }
+ end
+
+ def follow_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Follow",
+ operationId: "AccountController.follow",
+ security: [%{"oAuth" => ["follow", "write:follows"]}],
+ description: "Follow the given account",
+ parameters: [
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ Operation.parameter(
+ :reblogs,
+ :query,
+ BooleanLike,
+ "Receive this account's reblogs in home timeline? Defaults to true."
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship),
+ 400 => Operation.response("Error", "application/json", ApiError),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def unfollow_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Unfollow",
+ operationId: "AccountController.unfollow",
+ security: [%{"oAuth" => ["follow", "write:follows"]}],
+ description: "Unfollow the given account",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship),
+ 400 => Operation.response("Error", "application/json", ApiError),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def mute_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Mute",
+ operationId: "AccountController.mute",
+ security: [%{"oAuth" => ["follow", "write:mutes"]}],
+ requestBody: request_body("Parameters", mute_request()),
+ description:
+ "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).",
+ parameters: [
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ Operation.parameter(
+ :notifications,
+ :query,
+ %Schema{allOf: [BooleanLike], default: true},
+ "Mute notifications in addition to statuses? Defaults to `true`."
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def unmute_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Unmute",
+ operationId: "AccountController.unmute",
+ security: [%{"oAuth" => ["follow", "write:mutes"]}],
+ description: "Unmute the given account.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def block_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Block",
+ operationId: "AccountController.block",
+ security: [%{"oAuth" => ["follow", "write:blocks"]}],
+ description:
+ "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def unblock_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Unblock",
+ operationId: "AccountController.unblock",
+ security: [%{"oAuth" => ["follow", "write:blocks"]}],
+ description: "Unblock the given account.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def follows_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Follows",
+ operationId: "AccountController.follows",
+ security: [%{"oAuth" => ["follow", "write:follows"]}],
+ requestBody: request_body("Parameters", follows_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Account", "application/json", AccountRelationship),
+ 400 => Operation.response("Error", "application/json", ApiError),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+
+ def mutes_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Muted accounts",
+ operationId: "AccountController.mutes",
+ description: "Accounts the user has muted.",
+ security: [%{"oAuth" => ["follow", "read:mutes"]}],
+ responses: %{
+ 200 => Operation.response("Accounts", "application/json", array_of_accounts())
+ }
+ }
+ end
+
+ def blocks_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Blocked users",
+ operationId: "AccountController.blocks",
+ description: "View your blocks. See also accounts/:id/{block,unblock}",
+ security: [%{"oAuth" => ["read:blocks"]}],
+ responses: %{
+ 200 => Operation.response("Accounts", "application/json", array_of_accounts())
+ }
+ }
+ end
+
+ def endorsements_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Endorsements",
+ operationId: "AccountController.endorsements",
+ description: "Not implemented",
+ security: [%{"oAuth" => ["read:accounts"]}],
+ responses: %{
+ 200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
+ }
+ }
+ end
+
+ def identity_proofs_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Identity proofs",
+ operationId: "AccountController.identity_proofs",
+ description: "Not implemented",
+ responses: %{
+ 200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
+ }
+ }
+ end
+
+ defp create_request do
+ %Schema{
+ title: "AccountCreateRequest",
+ description: "POST body for creating an account",
+ type: :object,
+ properties: %{
+ reason: %Schema{
+ type: :string,
+ description:
+ "Text that will be reviewed by moderators if registrations require manual approval"
+ },
+ username: %Schema{type: :string, description: "The desired username for the account"},
+ email: %Schema{
+ type: :string,
+ description:
+ "The email address to be used for login. Required when `account_activation_required` is enabled.",
+ format: :email
+ },
+ password: %Schema{
+ type: :string,
+ description: "The password to be used for login",
+ format: :password
+ },
+ agreement: %Schema{
+ type: :boolean,
+ description:
+ "Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE."
+ },
+ locale: %Schema{
+ type: :string,
+ description: "The language of the confirmation email that will be sent"
+ },
+ # Pleroma-specific properties:
+ fullname: %Schema{type: :string, description: "Full name"},
+ bio: %Schema{type: :string, description: "Bio", default: ""},
+ captcha_solution: %Schema{
+ type: :string,
+ description: "Provider-specific captcha solution"
+ },
+ captcha_token: %Schema{type: :string, description: "Provider-specific captcha token"},
+ captcha_answer_data: %Schema{type: :string, description: "Provider-specific captcha data"},
+ token: %Schema{
+ type: :string,
+ description: "Invite token required when the registrations aren't public"
+ }
+ },
+ required: [:username, :password, :agreement],
+ example: %{
+ "username" => "cofe",
+ "email" => "cofe@example.com",
+ "password" => "secret",
+ "agreement" => "true",
+ "bio" => "☕️"
+ }
+ }
+ end
+
+ defp create_response do
+ %Schema{
+ title: "AccountCreateResponse",
+ description: "Response schema for an account",
+ type: :object,
+ properties: %{
+ token_type: %Schema{type: :string},
+ access_token: %Schema{type: :string},
+ scope: %Schema{type: :array, items: %Schema{type: :string}},
+ created_at: %Schema{type: :integer, format: :"date-time"}
+ },
+ example: %{
+ "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
+ "created_at" => 1_585_918_714,
+ "scope" => ["read", "write", "follow", "push"],
+ "token_type" => "Bearer"
+ }
+ }
+ end
+
+ defp update_creadentials_request do
+ %Schema{
+ title: "AccountUpdateCredentialsRequest",
+ description: "POST body for creating an account",
+ type: :object,
+ properties: %{
+ bot: %Schema{
+ type: :boolean,
+ description: "Whether the account has a bot flag."
+ },
+ display_name: %Schema{
+ type: :string,
+ description: "The display name to use for the profile."
+ },
+ note: %Schema{type: :string, description: "The account bio."},
+ avatar: %Schema{
+ type: :string,
+ description: "Avatar image encoded using multipart/form-data",
+ format: :binary
+ },
+ header: %Schema{
+ type: :string,
+ description: "Header image encoded using multipart/form-data",
+ format: :binary
+ },
+ locked: %Schema{
+ type: :boolean,
+ description: "Whether manual approval of follow requests is required."
+ },
+ fields_attributes: %Schema{
+ oneOf: [
+ %Schema{type: :array, items: attribute_field()},
+ %Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}}
+ ]
+ },
+ # NOTE: `source` field is not supported
+ #
+ # source: %Schema{
+ # type: :object,
+ # properties: %{
+ # privacy: %Schema{type: :string},
+ # sensitive: %Schema{type: :boolean},
+ # language: %Schema{type: :string}
+ # }
+ # },
+
+ # Pleroma-specific fields
+ no_rich_text: %Schema{
+ type: :boolean,
+ description: "html tags are stripped from all statuses requested from the API"
+ },
+ hide_followers: %Schema{type: :boolean, description: "user's followers will be hidden"},
+ hide_follows: %Schema{type: :boolean, description: "user's follows will be hidden"},
+ hide_followers_count: %Schema{
+ type: :boolean,
+ description: "user's follower count will be hidden"
+ },
+ hide_follows_count: %Schema{
+ type: :boolean,
+ description: "user's follow count will be hidden"
+ },
+ hide_favorites: %Schema{
+ type: :boolean,
+ description: "user's favorites timeline will be hidden"
+ },
+ show_role: %Schema{
+ type: :boolean,
+ description: "user's role (e.g admin, moderator) will be exposed to anyone in the
+ API"
+ },
+ default_scope: VisibilityScope,
+ pleroma_settings_store: %Schema{
+ type: :object,
+ description: "Opaque user settings to be saved on the backend."
+ },
+ skip_thread_containment: %Schema{
+ type: :boolean,
+ description: "Skip filtering out broken threads"
+ },
+ allow_following_move: %Schema{
+ type: :boolean,
+ description: "Allows automatically follow moved following accounts"
+ },
+ pleroma_background_image: %Schema{
+ type: :string,
+ description: "Sets the background image of the user.",
+ format: :binary
+ },
+ discoverable: %Schema{
+ type: :boolean,
+ description:
+ "Discovery of this account in search results and other services is allowed."
+ },
+ actor_type: ActorType
+ },
+ example: %{
+ bot: false,
+ display_name: "cofe",
+ note: "foobar",
+ fields_attributes: [%{name: "foo", value: "bar"}],
+ no_rich_text: false,
+ hide_followers: true,
+ hide_follows: false,
+ hide_followers_count: false,
+ hide_follows_count: false,
+ hide_favorites: false,
+ show_role: false,
+ default_scope: "private",
+ pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
+ skip_thread_containment: false,
+ allow_following_move: false,
+ discoverable: false,
+ actor_type: "Person"
+ }
+ }
+ end
+
+ defp array_of_accounts do
+ %Schema{
+ title: "ArrayOfAccounts",
+ type: :array,
+ items: Account
+ }
+ end
+
+ defp array_of_relationships do
+ %Schema{
+ title: "ArrayOfRelationships",
+ description: "Response schema for account relationships",
+ type: :array,
+ items: AccountRelationship,
+ example: [
+ %{
+ "id" => "1",
+ "following" => true,
+ "showing_reblogs" => true,
+ "followed_by" => true,
+ "blocking" => false,
+ "blocked_by" => true,
+ "muting" => false,
+ "muting_notifications" => false,
+ "requested" => false,
+ "domain_blocking" => false,
+ "subscribing" => false,
+ "endorsed" => true
+ },
+ %{
+ "id" => "2",
+ "following" => true,
+ "showing_reblogs" => true,
+ "followed_by" => true,
+ "blocking" => false,
+ "blocked_by" => true,
+ "muting" => true,
+ "muting_notifications" => false,
+ "requested" => true,
+ "domain_blocking" => false,
+ "subscribing" => false,
+ "endorsed" => false
+ },
+ %{
+ "id" => "3",
+ "following" => true,
+ "showing_reblogs" => true,
+ "followed_by" => true,
+ "blocking" => true,
+ "blocked_by" => false,
+ "muting" => true,
+ "muting_notifications" => false,
+ "requested" => false,
+ "domain_blocking" => true,
+ "subscribing" => true,
+ "endorsed" => false
+ }
+ ]
+ }
+ end
+
+ defp follows_request do
+ %Schema{
+ title: "AccountFollowsRequest",
+ description: "POST body for muting an account",
+ type: :object,
+ properties: %{
+ uri: %Schema{type: :string, format: :uri}
+ },
+ required: [:uri]
+ }
+ end
+
+ defp mute_request do
+ %Schema{
+ title: "AccountMuteRequest",
+ description: "POST body for muting an account",
+ type: :object,
+ properties: %{
+ notifications: %Schema{
+ type: :boolean,
+ description: "Mute notifications in addition to statuses? Defaults to true.",
+ default: true
+ }
+ },
+ example: %{
+ "notifications" => true
+ }
+ }
+ end
+
+ defp list do
+ %Schema{
+ title: "List",
+ description: "Response schema for a list",
+ type: :object,
+ properties: %{
+ id: %Schema{type: :string},
+ title: %Schema{type: :string}
+ },
+ example: %{
+ "id" => "123",
+ "title" => "my list"
+ }
+ }
+ end
+
+ defp array_of_lists do
+ %Schema{
+ title: "ArrayOfLists",
+ description: "Response schema for lists",
+ type: :array,
+ items: list(),
+ example: [
+ %{"id" => "123", "title" => "my list"},
+ %{"id" => "1337", "title" => "anotehr list"}
+ ]
+ }
+ end
+
+ defp array_of_statuses do
+ %Schema{
+ title: "ArrayOfStatuses",
+ type: :array,
+ items: Status
+ }
+ end
+
+ defp attribute_field do
+ %Schema{
+ title: "AccountAttributeField",
+ description: "Request schema for account custom fields",
+ type: :object,
+ properties: %{
+ name: %Schema{type: :string},
+ value: %Schema{type: :string}
+ },
+ required: [:name, :value],
+ example: %{
+ "name" => "Website",
+ "value" => "https://pleroma.com"
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex
index 26d8dbd42..f6ccd073f 100644
--- a/lib/pleroma/web/api_spec/operations/app_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/app_operation.ex
@@ -6,8 +6,6 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
- alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
- alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
@spec open_api_operation(atom) :: Operation.t()
def open_api_operation(action) do
@@ -22,9 +20,9 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
summary: "Create an application",
description: "Create a new application to obtain OAuth2 credentials",
operationId: "AppController.create",
- requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
+ requestBody: Helpers.request_body("Parameters", create_request(), required: true),
responses: %{
- 200 => Operation.response("App", "application/json", AppCreateResponse),
+ 200 => Operation.response("App", "application/json", create_response()),
422 =>
Operation.response(
"Unprocessable Entity",
@@ -51,11 +49,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
summary: "Verify your app works",
description: "Confirm that the app's OAuth2 credentials work.",
operationId: "AppController.verify_credentials",
- security: [
- %{
- "oAuth" => ["read"]
- }
- ],
+ security: [%{"oAuth" => ["read"]}],
responses: %{
200 =>
Operation.response("App", "application/json", %Schema{
@@ -93,4 +87,58 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
}
}
end
+
+ defp create_request do
+ %Schema{
+ title: "AppCreateRequest",
+ description: "POST body for creating an app",
+ type: :object,
+ properties: %{
+ client_name: %Schema{type: :string, description: "A name for your application."},
+ redirect_uris: %Schema{
+ type: :string,
+ description:
+ "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
+ },
+ scopes: %Schema{
+ type: :string,
+ description: "Space separated list of scopes",
+ default: "read"
+ },
+ website: %Schema{type: :string, description: "A URL to the homepage of your app"}
+ },
+ required: [:client_name, :redirect_uris],
+ example: %{
+ "client_name" => "My App",
+ "redirect_uris" => "https://myapp.com/auth/callback",
+ "website" => "https://myapp.com/"
+ }
+ }
+ end
+
+ defp create_response do
+ %Schema{
+ title: "AppCreateResponse",
+ description: "Response schema for an app",
+ type: :object,
+ properties: %{
+ id: %Schema{type: :string},
+ name: %Schema{type: :string},
+ client_id: %Schema{type: :string},
+ client_secret: %Schema{type: :string},
+ redirect_uri: %Schema{type: :string},
+ vapid_key: %Schema{type: :string},
+ website: %Schema{type: :string, nullable: true}
+ },
+ example: %{
+ "id" => "123",
+ "name" => "My App",
+ "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
+ "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
+ "vapid_key" =>
+ "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
+ "website" => "https://myapp.com/"
+ }
+ }
+ end
end
diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
index cf2215823..2f812ac77 100644
--- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
@@ -4,7 +4,8 @@
defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
alias OpenApiSpex.Operation
- alias Pleroma.Web.ApiSpec.Schemas.CustomEmojisResponse
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Emoji
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
@@ -18,7 +19,69 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
description: "Returns custom emojis that are available on the server.",
operationId: "CustomEmojiController.index",
responses: %{
- 200 => Operation.response("Custom Emojis", "application/json", CustomEmojisResponse)
+ 200 => Operation.response("Custom Emojis", "application/json", resposnse())
+ }
+ }
+ end
+
+ defp resposnse do
+ %Schema{
+ title: "CustomEmojisResponse",
+ description: "Response schema for custom emojis",
+ type: :array,
+ items: custom_emoji(),
+ example: [
+ %{
+ "category" => "Fun",
+ "shortcode" => "blank",
+ "static_url" => "https://lain.com/emoji/blank.png",
+ "tags" => ["Fun"],
+ "url" => "https://lain.com/emoji/blank.png",
+ "visible_in_picker" => false
+ },
+ %{
+ "category" => "Gif,Fun",
+ "shortcode" => "firefox",
+ "static_url" => "https://lain.com/emoji/Firefox.gif",
+ "tags" => ["Gif", "Fun"],
+ "url" => "https://lain.com/emoji/Firefox.gif",
+ "visible_in_picker" => true
+ },
+ %{
+ "category" => "pack:mixed",
+ "shortcode" => "sadcat",
+ "static_url" => "https://lain.com/emoji/mixed/sadcat.png",
+ "tags" => ["pack:mixed"],
+ "url" => "https://lain.com/emoji/mixed/sadcat.png",
+ "visible_in_picker" => true
+ }
+ ]
+ }
+ end
+
+ defp custom_emoji do
+ %Schema{
+ title: "CustomEmoji",
+ description: "Schema for a CustomEmoji",
+ allOf: [
+ Emoji,
+ %Schema{
+ type: :object,
+ properties: %{
+ category: %Schema{type: :string},
+ tags: %Schema{type: :array}
+ }
+ }
+ ],
+ example: %{
+ "category" => "Fun",
+ "shortcode" => "aaaa",
+ "url" =>
+ "https://files.mastodon.social/custom_emojis/images/000/007/118/original/aaaa.png",
+ "static_url" =>
+ "https://files.mastodon.social/custom_emojis/images/000/007/118/static/aaaa.png",
+ "visible_in_picker" => true,
+ "tags" => ["Gif", "Fun"]
}
}
end
diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
index dd14837c3..3b7f51ceb 100644
--- a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex
@@ -6,8 +6,6 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
- alias Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest
- alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
@@ -22,7 +20,13 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
security: [%{"oAuth" => ["follow", "read:blocks"]}],
operationId: "DomainBlockController.index",
responses: %{
- 200 => Operation.response("Domain blocks", "application/json", DomainBlocksResponse)
+ 200 =>
+ Operation.response("Domain blocks", "application/json", %Schema{
+ description: "Response schema for domain blocks",
+ type: :array,
+ items: %Schema{type: :string},
+ example: ["google.com", "facebook.com"]
+ })
}
}
end
@@ -40,7 +44,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
- prevent following new users from it (but does not remove existing follows)
""",
operationId: "DomainBlockController.create",
- requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
+ requestBody: domain_block_request(),
security: [%{"oAuth" => ["follow", "write:blocks"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
@@ -54,11 +58,28 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
summary: "Unblock a domain",
description: "Remove a domain block, if it exists in the user's array of blocked domains.",
operationId: "DomainBlockController.delete",
- requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
+ requestBody: domain_block_request(),
security: [%{"oAuth" => ["follow", "write:blocks"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
}
}
end
+
+ defp domain_block_request do
+ Helpers.request_body(
+ "Parameters",
+ %Schema{
+ type: :object,
+ properties: %{
+ domain: %Schema{type: :string}
+ },
+ required: [:domain]
+ },
+ required: true,
+ example: %{
+ "domain" => "facebook.com"
+ }
+ )
+ end
end
diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex
new file mode 100644
index 000000000..b5877ca9c
--- /dev/null
+++ b/lib/pleroma/web/api_spec/render_error.ex
@@ -0,0 +1,231 @@
+# 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.RenderError do
+ @behaviour Plug
+
+ import Plug.Conn, only: [put_status: 2]
+ import Phoenix.Controller, only: [json: 2]
+ import Pleroma.Web.Gettext
+
+ @impl Plug
+ def init(opts), do: opts
+
+ @impl Plug
+
+ def call(conn, errors) do
+ errors =
+ Enum.map(errors, fn
+ %{name: nil} = err ->
+ %OpenApiSpex.Cast.Error{err | name: List.last(err.path)}
+
+ err ->
+ err
+ end)
+
+ conn
+ |> put_status(:bad_request)
+ |> json(%{
+ error: errors |> Enum.map(&message/1) |> Enum.join(" "),
+ errors: errors |> Enum.map(&render_error/1)
+ })
+ end
+
+ defp render_error(error) do
+ pointer = OpenApiSpex.path_to_string(error)
+
+ %{
+ title: "Invalid value",
+ source: %{
+ pointer: pointer
+ },
+ message: OpenApiSpex.Cast.Error.message(error)
+ }
+ end
+
+ defp message(%{reason: :invalid_schema_type, type: type, name: name}) do
+ gettext("%{name} - Invalid schema.type. Got: %{type}.",
+ name: name,
+ type: inspect(type)
+ )
+ end
+
+ defp message(%{reason: :null_value, name: name} = error) do
+ case error.type do
+ nil ->
+ gettext("%{name} - null value.", name: name)
+
+ type ->
+ gettext("%{name} - null value where %{type} expected.",
+ name: name,
+ type: type
+ )
+ end
+ end
+
+ defp message(%{reason: :all_of, meta: %{invalid_schema: invalid_schema}}) do
+ gettext(
+ "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed.",
+ invalid_schema: invalid_schema
+ )
+ end
+
+ defp message(%{reason: :any_of, meta: %{failed_schemas: failed_schemas}}) do
+ gettext("Failed to cast value using any of: %{failed_schemas}.",
+ failed_schemas: failed_schemas
+ )
+ end
+
+ defp message(%{reason: :one_of, meta: %{failed_schemas: failed_schemas}}) do
+ gettext("Failed to cast value to one of: %{failed_schemas}.", failed_schemas: failed_schemas)
+ end
+
+ defp message(%{reason: :min_length, length: length, name: name}) do
+ gettext("%{name} - String length is smaller than minLength: %{length}.",
+ name: name,
+ length: length
+ )
+ end
+
+ defp message(%{reason: :max_length, length: length, name: name}) do
+ gettext("%{name} - String length is larger than maxLength: %{length}.",
+ name: name,
+ length: length
+ )
+ end
+
+ defp message(%{reason: :unique_items, name: name}) do
+ gettext("%{name} - Array items must be unique.", name: name)
+ end
+
+ defp message(%{reason: :min_items, length: min, value: array, name: name}) do
+ gettext("%{name} - Array length %{length} is smaller than minItems: %{min}.",
+ name: name,
+ length: length(array),
+ min: min
+ )
+ end
+
+ defp message(%{reason: :max_items, length: max, value: array, name: name}) do
+ gettext("%{name} - Array length %{length} is larger than maxItems: %{}.",
+ name: name,
+ length: length(array),
+ max: max
+ )
+ end
+
+ defp message(%{reason: :multiple_of, length: multiple, value: count, name: name}) do
+ gettext("%{name} - %{count} is not a multiple of %{multiple}.",
+ name: name,
+ count: count,
+ multiple: multiple
+ )
+ end
+
+ defp message(%{reason: :exclusive_max, length: max, value: value, name: name})
+ when value >= max do
+ gettext("%{name} - %{value} is larger than exclusive maximum %{max}.",
+ name: name,
+ value: value,
+ max: max
+ )
+ end
+
+ defp message(%{reason: :maximum, length: max, value: value, name: name})
+ when value > max do
+ gettext("%{name} - %{value} is larger than inclusive maximum %{max}.",
+ name: name,
+ value: value,
+ max: max
+ )
+ end
+
+ defp message(%{reason: :exclusive_multiple, length: min, value: value, name: name})
+ when value <= min do
+ gettext("%{name} - %{value} is smaller than exclusive minimum %{min}.",
+ name: name,
+ value: value,
+ min: min
+ )
+ end
+
+ defp message(%{reason: :minimum, length: min, value: value, name: name})
+ when value < min do
+ gettext("%{name} - %{value} is smaller than inclusive minimum %{min}.",
+ name: name,
+ value: value,
+ min: min
+ )
+ end
+
+ defp message(%{reason: :invalid_type, type: type, value: value, name: name}) do
+ gettext("%{name} - Invalid %{type}. Got: %{value}.",
+ name: name,
+ value: OpenApiSpex.TermType.type(value),
+ type: type
+ )
+ end
+
+ defp message(%{reason: :invalid_format, format: format, name: name}) do
+ gettext("%{name} - Invalid format. Expected %{format}.", name: name, format: inspect(format))
+ end
+
+ defp message(%{reason: :invalid_enum, name: name}) do
+ gettext("%{name} - Invalid value for enum.", name: name)
+ end
+
+ defp message(%{reason: :polymorphic_failed, type: polymorphic_type}) do
+ gettext("Failed to cast to any schema in %{polymorphic_type}",
+ polymorphic_type: polymorphic_type
+ )
+ end
+
+ defp message(%{reason: :unexpected_field, name: name}) do
+ gettext("Unexpected field: %{name}.", name: safe_string(name))
+ end
+
+ defp message(%{reason: :no_value_for_discriminator, name: field}) do
+ gettext("Value used as discriminator for `%{field}` matches no schemas.", name: field)
+ end
+
+ defp message(%{reason: :invalid_discriminator_value, name: field}) do
+ gettext("No value provided for required discriminator `%{field}`.", name: field)
+ end
+
+ defp message(%{reason: :unknown_schema, name: name}) do
+ gettext("Unknown schema: %{name}.", name: name)
+ end
+
+ defp message(%{reason: :missing_field, name: name}) do
+ gettext("Missing field: %{name}.", name: name)
+ end
+
+ defp message(%{reason: :missing_header, name: name}) do
+ gettext("Missing header: %{name}.", name: name)
+ end
+
+ defp message(%{reason: :invalid_header, name: name}) do
+ gettext("Invalid value for header: %{name}.", name: name)
+ end
+
+ defp message(%{reason: :max_properties, meta: meta}) do
+ gettext(
+ "Object property count %{property_count} is greater than maxProperties: %{max_properties}.",
+ property_count: meta.property_count,
+ max_properties: meta.max_properties
+ )
+ end
+
+ defp message(%{reason: :min_properties, meta: meta}) do
+ gettext(
+ "Object property count %{property_count} is less than minProperties: %{min_properties}",
+ property_count: meta.property_count,
+ min_properties: meta.min_properties
+ )
+ end
+
+ defp safe_string(string) do
+ to_string(string) |> String.slice(0..39)
+ end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
new file mode 100644
index 000000000..d54e2158d
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -0,0 +1,167 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Account do
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.AccountField
+ alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
+ alias Pleroma.Web.ApiSpec.Schemas.ActorType
+ alias Pleroma.Web.ApiSpec.Schemas.Emoji
+ alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+ alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "Account",
+ description: "Response schema for an account",
+ type: :object,
+ properties: %{
+ acct: %Schema{type: :string},
+ avatar_static: %Schema{type: :string, format: :uri},
+ avatar: %Schema{type: :string, format: :uri},
+ bot: %Schema{type: :boolean},
+ created_at: %Schema{type: :string, format: "date-time"},
+ display_name: %Schema{type: :string},
+ emojis: %Schema{type: :array, items: Emoji},
+ fields: %Schema{type: :array, items: AccountField},
+ follow_requests_count: %Schema{type: :integer},
+ followers_count: %Schema{type: :integer},
+ following_count: %Schema{type: :integer},
+ header_static: %Schema{type: :string, format: :uri},
+ header: %Schema{type: :string, format: :uri},
+ id: FlakeID,
+ locked: %Schema{type: :boolean},
+ note: %Schema{type: :string, format: :html},
+ statuses_count: %Schema{type: :integer},
+ url: %Schema{type: :string, format: :uri},
+ username: %Schema{type: :string},
+ pleroma: %Schema{
+ type: :object,
+ properties: %{
+ allow_following_move: %Schema{type: :boolean},
+ background_image: %Schema{type: :string, nullable: true},
+ chat_token: %Schema{type: :string},
+ confirmation_pending: %Schema{type: :boolean},
+ hide_favorites: %Schema{type: :boolean},
+ hide_followers_count: %Schema{type: :boolean},
+ hide_followers: %Schema{type: :boolean},
+ hide_follows_count: %Schema{type: :boolean},
+ hide_follows: %Schema{type: :boolean},
+ is_admin: %Schema{type: :boolean},
+ is_moderator: %Schema{type: :boolean},
+ skip_thread_containment: %Schema{type: :boolean},
+ tags: %Schema{type: :array, items: %Schema{type: :string}},
+ unread_conversation_count: %Schema{type: :integer},
+ notification_settings: %Schema{
+ type: :object,
+ properties: %{
+ followers: %Schema{type: :boolean},
+ follows: %Schema{type: :boolean},
+ non_followers: %Schema{type: :boolean},
+ non_follows: %Schema{type: :boolean},
+ privacy_option: %Schema{type: :boolean}
+ }
+ },
+ relationship: AccountRelationship,
+ settings_store: %Schema{
+ type: :object
+ }
+ }
+ },
+ source: %Schema{
+ type: :object,
+ properties: %{
+ fields: %Schema{type: :array, items: AccountField},
+ note: %Schema{type: :string},
+ privacy: VisibilityScope,
+ sensitive: %Schema{type: :boolean},
+ pleroma: %Schema{
+ type: :object,
+ properties: %{
+ actor_type: ActorType,
+ discoverable: %Schema{type: :boolean},
+ no_rich_text: %Schema{type: :boolean},
+ show_role: %Schema{type: :boolean}
+ }
+ }
+ }
+ }
+ },
+ example: %{
+ "acct" => "foobar",
+ "avatar" => "https://mypleroma.com/images/avi.png",
+ "avatar_static" => "https://mypleroma.com/images/avi.png",
+ "bot" => false,
+ "created_at" => "2020-03-24T13:05:58.000Z",
+ "display_name" => "foobar",
+ "emojis" => [],
+ "fields" => [],
+ "follow_requests_count" => 0,
+ "followers_count" => 0,
+ "following_count" => 1,
+ "header" => "https://mypleroma.com/images/banner.png",
+ "header_static" => "https://mypleroma.com/images/banner.png",
+ "id" => "9tKi3esbG7OQgZ2920",
+ "locked" => false,
+ "note" => "cofe",
+ "pleroma" => %{
+ "allow_following_move" => true,
+ "background_image" => nil,
+ "confirmation_pending" => true,
+ "hide_favorites" => true,
+ "hide_followers" => false,
+ "hide_followers_count" => false,
+ "hide_follows" => false,
+ "hide_follows_count" => false,
+ "is_admin" => false,
+ "is_moderator" => false,
+ "skip_thread_containment" => false,
+ "chat_token" =>
+ "SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
+ "unread_conversation_count" => 0,
+ "tags" => [],
+ "notification_settings" => %{
+ "followers" => true,
+ "follows" => true,
+ "non_followers" => true,
+ "non_follows" => true,
+ "privacy_option" => false
+ },
+ "relationship" => %{
+ "blocked_by" => false,
+ "blocking" => false,
+ "domain_blocking" => false,
+ "endorsed" => false,
+ "followed_by" => false,
+ "following" => false,
+ "id" => "9tKi3esbG7OQgZ2920",
+ "muting" => false,
+ "muting_notifications" => false,
+ "requested" => false,
+ "showing_reblogs" => true,
+ "subscribing" => false
+ },
+ "settings_store" => %{
+ "pleroma-fe" => %{}
+ }
+ },
+ "source" => %{
+ "fields" => [],
+ "note" => "foobar",
+ "pleroma" => %{
+ "actor_type" => "Person",
+ "discoverable" => false,
+ "no_rich_text" => false,
+ "show_role" => true
+ },
+ "privacy" => "public",
+ "sensitive" => false
+ },
+ "statuses_count" => 0,
+ "url" => "https://mypleroma.com/users/foobar",
+ "username" => "foobar"
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_field.ex b/lib/pleroma/web/api_spec/schemas/account_field.ex
new file mode 100644
index 000000000..fa97073a0
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_field.ex
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.AccountField do
+ alias OpenApiSpex.Schema
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "AccountField",
+ description: "Response schema for account custom fields",
+ type: :object,
+ properties: %{
+ name: %Schema{type: :string},
+ value: %Schema{type: :string, format: :html},
+ verified_at: %Schema{type: :string, format: :"date-time", nullable: true}
+ },
+ example: %{
+ "name" => "Website",
+ "value" =>
+ "<a href=\"https://pleroma.com\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">pleroma.com</span><span class=\"invisible\"></span></a>",
+ "verified_at" => "2019-08-29T04:14:55.571+00:00"
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
new file mode 100644
index 000000000..8b982669e
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "AccountRelationship",
+ description: "Response schema for relationship",
+ type: :object,
+ properties: %{
+ blocked_by: %Schema{type: :boolean},
+ blocking: %Schema{type: :boolean},
+ domain_blocking: %Schema{type: :boolean},
+ endorsed: %Schema{type: :boolean},
+ followed_by: %Schema{type: :boolean},
+ following: %Schema{type: :boolean},
+ id: FlakeID,
+ muting: %Schema{type: :boolean},
+ muting_notifications: %Schema{type: :boolean},
+ requested: %Schema{type: :boolean},
+ showing_reblogs: %Schema{type: :boolean},
+ subscribing: %Schema{type: :boolean}
+ },
+ example: %{
+ "blocked_by" => false,
+ "blocking" => false,
+ "domain_blocking" => false,
+ "endorsed" => false,
+ "followed_by" => false,
+ "following" => false,
+ "id" => "9tKi3esbG7OQgZ2920",
+ "muting" => false,
+ "muting_notifications" => false,
+ "requested" => false,
+ "showing_reblogs" => true,
+ "subscribing" => false
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/actor_type.ex b/lib/pleroma/web/api_spec/schemas/actor_type.ex
new file mode 100644
index 000000000..ac9b46678
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/actor_type.ex
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.ActorType do
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "ActorType",
+ type: :string,
+ enum: ["Application", "Group", "Organization", "Person", "Service"]
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/domain_block_request.ex b/lib/pleroma/web/api_spec/schemas/api_error.ex
index ee9238361..5815df94c 100644
--- a/lib/pleroma/web/api_spec/schemas/domain_block_request.ex
+++ b/lib/pleroma/web/api_spec/schemas/api_error.ex
@@ -2,19 +2,18 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest do
+defmodule Pleroma.Web.ApiSpec.Schemas.ApiError do
alias OpenApiSpex.Schema
+
require OpenApiSpex
OpenApiSpex.schema(%{
- title: "DomainBlockRequest",
+ title: "ApiError",
+ description: "Response schema for API error",
type: :object,
- properties: %{
- domain: %Schema{type: :string}
- },
- required: [:domain],
+ properties: %{error: %Schema{type: :string}},
example: %{
- "domain" => "facebook.com"
+ "error" => "Something went wrong"
}
})
end
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_request.ex b/lib/pleroma/web/api_spec/schemas/app_create_request.ex
deleted file mode 100644
index 8a83abef3..000000000
--- a/lib/pleroma/web/api_spec/schemas/app_create_request.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateRequest do
- alias OpenApiSpex.Schema
- require OpenApiSpex
-
- OpenApiSpex.schema(%{
- title: "AppCreateRequest",
- description: "POST body for creating an app",
- type: :object,
- properties: %{
- client_name: %Schema{type: :string, description: "A name for your application."},
- redirect_uris: %Schema{
- type: :string,
- description:
- "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
- },
- scopes: %Schema{
- type: :string,
- description: "Space separated list of scopes. If none is provided, defaults to `read`."
- },
- website: %Schema{type: :string, description: "A URL to the homepage of your app"}
- },
- required: [:client_name, :redirect_uris],
- example: %{
- "client_name" => "My App",
- "redirect_uris" => "https://myapp.com/auth/callback",
- "website" => "https://myapp.com/"
- }
- })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_response.ex b/lib/pleroma/web/api_spec/schemas/app_create_response.ex
deleted file mode 100644
index f290fb031..000000000
--- a/lib/pleroma/web/api_spec/schemas/app_create_response.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateResponse do
- alias OpenApiSpex.Schema
-
- require OpenApiSpex
-
- OpenApiSpex.schema(%{
- title: "AppCreateResponse",
- description: "Response schema for an app",
- type: :object,
- properties: %{
- id: %Schema{type: :string},
- name: %Schema{type: :string},
- client_id: %Schema{type: :string},
- client_secret: %Schema{type: :string},
- redirect_uri: %Schema{type: :string},
- vapid_key: %Schema{type: :string},
- website: %Schema{type: :string, nullable: true}
- },
- example: %{
- "id" => "123",
- "name" => "My App",
- "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
- "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
- "vapid_key" =>
- "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
- "website" => "https://myapp.com/"
- }
- })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/boolean_like.ex b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
new file mode 100644
index 000000000..f3bfb74da
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
+ alias OpenApiSpex.Schema
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "BooleanLike",
+ description: """
+ The following values will be treated as `false`:
+ - false
+ - 0
+ - "0",
+ - "f",
+ - "F",
+ - "false",
+ - "FALSE",
+ - "off",
+ - "OFF"
+
+ All other non-null values will be treated as `true`
+ """,
+ anyOf: [
+ %Schema{type: :boolean},
+ %Schema{type: :string},
+ %Schema{type: :integer}
+ ]
+ })
+
+ def after_cast(value, _schmea) do
+ {:ok, Pleroma.Web.ControllerHelper.truthy_param?(value)}
+ end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/custom_emoji.ex b/lib/pleroma/web/api_spec/schemas/custom_emoji.ex
deleted file mode 100644
index 5531b2081..000000000
--- a/lib/pleroma/web/api_spec/schemas/custom_emoji.ex
+++ /dev/null
@@ -1,30 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.CustomEmoji do
- alias OpenApiSpex.Schema
-
- require OpenApiSpex
-
- OpenApiSpex.schema(%{
- title: "CustomEmoji",
- description: "Response schema for an CustomEmoji",
- type: :object,
- properties: %{
- shortcode: %Schema{type: :string},
- url: %Schema{type: :string},
- static_url: %Schema{type: :string},
- visible_in_picker: %Schema{type: :boolean},
- category: %Schema{type: :string},
- tags: %Schema{type: :array}
- },
- example: %{
- "shortcode" => "aaaa",
- "url" => "https://files.mastodon.social/custom_emojis/images/000/007/118/original/aaaa.png",
- "static_url" =>
- "https://files.mastodon.social/custom_emojis/images/000/007/118/static/aaaa.png",
- "visible_in_picker" => true
- }
- })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/custom_emojis_response.ex b/lib/pleroma/web/api_spec/schemas/custom_emojis_response.ex
deleted file mode 100644
index 01582a63d..000000000
--- a/lib/pleroma/web/api_spec/schemas/custom_emojis_response.ex
+++ /dev/null
@@ -1,42 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.CustomEmojisResponse do
- alias Pleroma.Web.ApiSpec.Schemas.CustomEmoji
-
- require OpenApiSpex
-
- OpenApiSpex.schema(%{
- title: "CustomEmojisResponse",
- description: "Response schema for custom emojis",
- type: :array,
- items: CustomEmoji,
- example: [
- %{
- "category" => "Fun",
- "shortcode" => "blank",
- "static_url" => "https://lain.com/emoji/blank.png",
- "tags" => ["Fun"],
- "url" => "https://lain.com/emoji/blank.png",
- "visible_in_picker" => true
- },
- %{
- "category" => "Gif,Fun",
- "shortcode" => "firefox",
- "static_url" => "https://lain.com/emoji/Firefox.gif",
- "tags" => ["Gif", "Fun"],
- "url" => "https://lain.com/emoji/Firefox.gif",
- "visible_in_picker" => true
- },
- %{
- "category" => "pack:mixed",
- "shortcode" => "sadcat",
- "static_url" => "https://lain.com/emoji/mixed/sadcat.png",
- "tags" => ["pack:mixed"],
- "url" => "https://lain.com/emoji/mixed/sadcat.png",
- "visible_in_picker" => true
- }
- ]
- })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex b/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex
deleted file mode 100644
index d895aca4e..000000000
--- a/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse do
- require OpenApiSpex
- alias OpenApiSpex.Schema
-
- OpenApiSpex.schema(%{
- title: "DomainBlocksResponse",
- description: "Response schema for domain blocks",
- type: :array,
- items: %Schema{type: :string},
- example: ["google.com", "facebook.com"]
- })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/emoji.ex b/lib/pleroma/web/api_spec/schemas/emoji.ex
new file mode 100644
index 000000000..26f35e648
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/emoji.ex
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Emoji do
+ alias OpenApiSpex.Schema
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "Emoji",
+ description: "Response schema for an emoji",
+ type: :object,
+ properties: %{
+ shortcode: %Schema{type: :string},
+ url: %Schema{type: :string, format: :uri},
+ static_url: %Schema{type: :string, format: :uri},
+ visible_in_picker: %Schema{type: :boolean}
+ },
+ example: %{
+ "shortcode" => "fatyoshi",
+ "url" =>
+ "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png",
+ "static_url" =>
+ "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png",
+ "visible_in_picker" => true
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/flake_id.ex b/lib/pleroma/web/api_spec/schemas/flake_id.ex
new file mode 100644
index 000000000..3b5f6477a
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/flake_id.ex
@@ -0,0 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.FlakeID do
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "FlakeID",
+ description:
+ "Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings",
+ type: :string
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex
new file mode 100644
index 000000000..0474b550b
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/poll.ex
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Emoji
+ alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "Poll",
+ description: "Response schema for account custom fields",
+ type: :object,
+ properties: %{
+ id: FlakeID,
+ expires_at: %Schema{type: :string, format: "date-time"},
+ expired: %Schema{type: :boolean},
+ multiple: %Schema{type: :boolean},
+ votes_count: %Schema{type: :integer},
+ voted: %Schema{type: :boolean},
+ emojis: %Schema{type: :array, items: Emoji},
+ options: %Schema{
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ title: %Schema{type: :string},
+ votes_count: %Schema{type: :integer}
+ }
+ }
+ }
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
new file mode 100644
index 000000000..aef0588d4
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -0,0 +1,226 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Status do
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Account
+ alias Pleroma.Web.ApiSpec.Schemas.Emoji
+ alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+ alias Pleroma.Web.ApiSpec.Schemas.Poll
+ alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "Status",
+ description: "Response schema for a status",
+ type: :object,
+ properties: %{
+ account: Account,
+ application: %Schema{
+ type: :object,
+ properties: %{
+ name: %Schema{type: :string},
+ website: %Schema{type: :string, nullable: true, format: :uri}
+ }
+ },
+ bookmarked: %Schema{type: :boolean},
+ card: %Schema{
+ type: :object,
+ nullable: true,
+ properties: %{
+ type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
+ provider_name: %Schema{type: :string, nullable: true},
+ provider_url: %Schema{type: :string, format: :uri},
+ url: %Schema{type: :string, format: :uri},
+ image: %Schema{type: :string, nullable: true, format: :uri},
+ title: %Schema{type: :string},
+ description: %Schema{type: :string}
+ }
+ },
+ content: %Schema{type: :string, format: :html},
+ created_at: %Schema{type: :string, format: "date-time"},
+ emojis: %Schema{type: :array, items: Emoji},
+ favourited: %Schema{type: :boolean},
+ favourites_count: %Schema{type: :integer},
+ id: FlakeID,
+ in_reply_to_account_id: %Schema{type: :string, nullable: true},
+ in_reply_to_id: %Schema{type: :string, nullable: true},
+ language: %Schema{type: :string, nullable: true},
+ media_attachments: %Schema{
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ id: %Schema{type: :string},
+ url: %Schema{type: :string, format: :uri},
+ remote_url: %Schema{type: :string, format: :uri},
+ preview_url: %Schema{type: :string, format: :uri},
+ text_url: %Schema{type: :string, format: :uri},
+ description: %Schema{type: :string},
+ type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]},
+ pleroma: %Schema{
+ type: :object,
+ properties: %{mime_type: %Schema{type: :string}}
+ }
+ }
+ }
+ },
+ mentions: %Schema{
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ id: %Schema{type: :string},
+ acct: %Schema{type: :string},
+ username: %Schema{type: :string},
+ url: %Schema{type: :string, format: :uri}
+ }
+ }
+ },
+ muted: %Schema{type: :boolean},
+ pinned: %Schema{type: :boolean},
+ pleroma: %Schema{
+ type: :object,
+ properties: %{
+ content: %Schema{type: :object, additionalProperties: %Schema{type: :string}},
+ conversation_id: %Schema{type: :integer},
+ direct_conversation_id: %Schema{type: :string, nullable: true},
+ emoji_reactions: %Schema{
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ name: %Schema{type: :string},
+ count: %Schema{type: :integer},
+ me: %Schema{type: :boolean}
+ }
+ }
+ },
+ expires_at: %Schema{type: :string, format: "date-time", nullable: true},
+ in_reply_to_account_acct: %Schema{type: :string, nullable: true},
+ local: %Schema{type: :boolean},
+ spoiler_text: %Schema{type: :object, additionalProperties: %Schema{type: :string}},
+ thread_muted: %Schema{type: :boolean}
+ }
+ },
+ poll: %Schema{type: Poll, nullable: true},
+ reblog: %Schema{
+ allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
+ nullable: true
+ },
+ reblogged: %Schema{type: :boolean},
+ reblogs_count: %Schema{type: :integer},
+ replies_count: %Schema{type: :integer},
+ sensitive: %Schema{type: :boolean},
+ spoiler_text: %Schema{type: :string},
+ tags: %Schema{
+ type: :array,
+ items: %Schema{
+ type: :object,
+ properties: %{
+ name: %Schema{type: :string},
+ url: %Schema{type: :string, format: :uri}
+ }
+ }
+ },
+ uri: %Schema{type: :string, format: :uri},
+ url: %Schema{type: :string, nullable: true, format: :uri},
+ visibility: VisibilityScope
+ },
+ example: %{
+ "account" => %{
+ "acct" => "nick6",
+ "avatar" => "http://localhost:4001/images/avi.png",
+ "avatar_static" => "http://localhost:4001/images/avi.png",
+ "bot" => false,
+ "created_at" => "2020-04-07T19:48:51.000Z",
+ "display_name" => "Test テスト User 6",
+ "emojis" => [],
+ "fields" => [],
+ "followers_count" => 1,
+ "following_count" => 0,
+ "header" => "http://localhost:4001/images/banner.png",
+ "header_static" => "http://localhost:4001/images/banner.png",
+ "id" => "9toJCsKN7SmSf3aj5c",
+ "locked" => false,
+ "note" => "Tester Number 6",
+ "pleroma" => %{
+ "background_image" => nil,
+ "confirmation_pending" => false,
+ "hide_favorites" => true,
+ "hide_followers" => false,
+ "hide_followers_count" => false,
+ "hide_follows" => false,
+ "hide_follows_count" => false,
+ "is_admin" => false,
+ "is_moderator" => false,
+ "relationship" => %{
+ "blocked_by" => false,
+ "blocking" => false,
+ "domain_blocking" => false,
+ "endorsed" => false,
+ "followed_by" => false,
+ "following" => true,
+ "id" => "9toJCsKN7SmSf3aj5c",
+ "muting" => false,
+ "muting_notifications" => false,
+ "requested" => false,
+ "showing_reblogs" => true,
+ "subscribing" => false
+ },
+ "skip_thread_containment" => false,
+ "tags" => []
+ },
+ "source" => %{
+ "fields" => [],
+ "note" => "Tester Number 6",
+ "pleroma" => %{"actor_type" => "Person", "discoverable" => false},
+ "sensitive" => false
+ },
+ "statuses_count" => 1,
+ "url" => "http://localhost:4001/users/nick6",
+ "username" => "nick6"
+ },
+ "application" => %{"name" => "Web", "website" => nil},
+ "bookmarked" => false,
+ "card" => nil,
+ "content" => "foobar",
+ "created_at" => "2020-04-07T19:48:51.000Z",
+ "emojis" => [],
+ "favourited" => false,
+ "favourites_count" => 0,
+ "id" => "9toJCu5YZW7O7gfvH6",
+ "in_reply_to_account_id" => nil,
+ "in_reply_to_id" => nil,
+ "language" => nil,
+ "media_attachments" => [],
+ "mentions" => [],
+ "muted" => false,
+ "pinned" => false,
+ "pleroma" => %{
+ "content" => %{"text/plain" => "foobar"},
+ "conversation_id" => 345_972,
+ "direct_conversation_id" => nil,
+ "emoji_reactions" => [],
+ "expires_at" => nil,
+ "in_reply_to_account_acct" => nil,
+ "local" => true,
+ "spoiler_text" => %{"text/plain" => ""},
+ "thread_muted" => false
+ },
+ "poll" => nil,
+ "reblog" => nil,
+ "reblogged" => false,
+ "reblogs_count" => 0,
+ "replies_count" => 0,
+ "sensitive" => false,
+ "spoiler_text" => "",
+ "tags" => [],
+ "uri" => "http://localhost:4001/objects/0f5dad44-0e9e-4610-b377-a2631e499190",
+ "url" => "http://localhost:4001/notice/9toJCu5YZW7O7gfvH6",
+ "visibility" => "private"
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
new file mode 100644
index 000000000..8c81a4d73
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
@@ -0,0 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "VisibilityScope",
+ description: "Status visibility",
+ type: :string,
+ enum: ["public", "unlisted", "private", "direct"]
+ })
+end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index c1cd15bb2..244cf2be5 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -84,14 +84,18 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
%__MODULE__{draft | attachments: attachments}
end
- defp in_reply_to(draft) do
- case Map.get(draft.params, "in_reply_to_status_id") do
- "" -> draft
- nil -> draft
- id -> %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
- end
+ defp in_reply_to(%{params: %{"in_reply_to_status_id" => ""}} = draft), do: draft
+
+ defp in_reply_to(%{params: %{"in_reply_to_status_id" => id}} = draft) when is_binary(id) do
+ %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
end
+ defp in_reply_to(%{params: %{"in_reply_to_status_id" => %Activity{} = in_reply_to}} = draft) do
+ %__MODULE__{draft | in_reply_to: in_reply_to}
+ end
+
+ defp in_reply_to(draft), do: draft
+
defp in_reply_to_conversation(draft) do
in_reply_to_conversation = Participation.get(draft.params["in_reply_to_conversation_id"])
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index f50a909aa..d1efe0c36 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -86,8 +86,9 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def repeat(id_or_ap_id, user, params \\ %{}) do
- with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)},
+ def repeat(id, user, params \\ %{}) do
+ with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
+ {:find_activity, Activity.get_by_id(id)},
object <- Object.normalize(activity),
announce_activity <- Utils.get_existing_announce(user.ap_id, object),
public <- public_announce?(object, params) do
@@ -102,8 +103,9 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def unrepeat(id_or_ap_id, user) do
- with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do
+ def unrepeat(id, user) do
+ with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
+ {:find_activity, Activity.get_by_id(id)} do
object = Object.normalize(activity)
ActivityPub.unannounce(user, object)
else
@@ -160,8 +162,9 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def unfavorite(id_or_ap_id, user) do
- with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do
+ def unfavorite(id, user) do
+ with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
+ {:find_activity, Activity.get_by_id(id)} do
object = Object.normalize(activity)
ActivityPub.unlike(user, object)
else
@@ -332,12 +335,12 @@ defmodule Pleroma.Web.CommonAPI do
defp maybe_create_activity_expiration(result, _), do: result
- def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
+ def pin(id, %{ap_id: user_ap_id} = user) do
with %Activity{
actor: ^user_ap_id,
data: %{"type" => "Create"},
object: %Object{data: %{"type" => object_type}}
- } = activity <- get_by_id_or_ap_id(id_or_ap_id),
+ } = activity <- Activity.get_by_id_with_object(id),
true <- object_type in ["Note", "Article", "Question"],
true <- Visibility.is_public?(activity),
{:ok, _user} <- User.add_pinnned_activity(user, activity) do
@@ -348,8 +351,8 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def unpin(id_or_ap_id, user) do
- with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
+ def unpin(id, user) do
+ with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
{:ok, _user} <- User.remove_pinnned_activity(user, activity) do
{:ok, activity}
else
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 7eec5aa09..945e63e22 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -22,24 +22,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
require Logger
require Pleroma.Constants
- # This is a hack for twidere.
- def get_by_id_or_ap_id(id) do
- activity =
- with true <- FlakeId.flake_id?(id),
- %Activity{} = activity <- Activity.get_by_id_with_object(id) do
- activity
- else
- _ -> Activity.get_create_by_object_ap_id_with_object(id)
- end
-
- activity &&
- if activity.data["type"] == "Create" do
- activity
- else
- Activity.get_create_by_object_ap_id_with_object(activity.data["object"])
- end
- end
-
def attachments_from_ids(%{"media_ids" => ids, "descriptions" => desc} = _) do
attachments_from_ids_descs(ids, desc)
end
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 4780081b2..eb97ae975 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -82,8 +82,9 @@ defmodule Pleroma.Web.ControllerHelper do
end
end
- def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do
- case Pleroma.User.get_cached_by_id(id) do
+ def assign_account_by_id(conn, _) do
+ # TODO: use `conn.params[:id]` only after moving to OpenAPI
+ case Pleroma.User.get_cached_by_id(conn.params[:id] || conn.params["id"]) do
%Pleroma.User{} = account -> assign(conn, :account, account)
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index f39825e08..1eedf02d6 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -27,6 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI
+ plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+
plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:show, :statuses])
@@ -88,26 +90,26 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation
+
@doc "POST /api/v1/accounts"
- def create(
- %{assigns: %{app: app}} = conn,
- %{"username" => nickname, "password" => _, "agreement" => true} = params
- ) do
+ def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
params =
params
|> Map.take([
- "email",
- "captcha_solution",
- "captcha_token",
- "captcha_answer_data",
- "token",
- "password"
+ :email,
+ :bio,
+ :captcha_solution,
+ :captcha_token,
+ :captcha_answer_data,
+ :token,
+ :password,
+ :fullname
])
- |> Map.put("nickname", nickname)
- |> Map.put("fullname", params["fullname"] || nickname)
- |> Map.put("bio", params["bio"] || "")
- |> Map.put("confirm", params["password"])
- |> Map.put("trusted_app", app.trusted)
+ |> 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, user} <- TwitterAPI.register_user(params, need_confirmation: true),
@@ -131,7 +133,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
render_error(conn, :forbidden, "Invalid credentials")
end
- defp validate_email_param(%{"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
@@ -153,7 +155,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
@doc "PATCH /api/v1/accounts/update_credentials"
- def update_credentials(%{assigns: %{user: user}} = conn, params) do
+ def update_credentials(%{assigns: %{user: original_user}, body_params: params} = conn, _params) do
+ user = original_user
+
+ params =
+ params
+ |> Enum.filter(fn {_, value} -> not is_nil(value) end)
+ |> Enum.into(%{})
+
user_params =
[
:no_rich_text,
@@ -169,22 +178,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
:discoverable
]
|> Enum.reduce(%{}, fn key, acc ->
- add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
+ add_if_present(acc, params, key, key, &{:ok, truthy_param?(&1)})
end)
- |> add_if_present(params, "display_name", :name)
- |> add_if_present(params, "note", :bio)
- |> add_if_present(params, "avatar", :avatar)
- |> add_if_present(params, "header", :banner)
- |> add_if_present(params, "pleroma_background_image", :background)
+ |> add_if_present(params, :display_name, :name)
+ |> add_if_present(params, :note, :bio)
+ |> add_if_present(params, :avatar, :avatar)
+ |> add_if_present(params, :header, :banner)
+ |> add_if_present(params, :pleroma_background_image, :background)
|> add_if_present(
params,
- "fields_attributes",
+ :fields_attributes,
:raw_fields,
&{:ok, normalize_fields_attributes(&1)}
)
- |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store)
- |> add_if_present(params, "default_scope", :default_scope)
- |> add_if_present(params, "actor_type", :actor_type)
+ |> add_if_present(params, :pleroma_settings_store, :pleroma_settings_store)
+ |> add_if_present(params, :default_scope, :default_scope)
+ |> add_if_present(params, :actor_type, :actor_type)
changeset = User.update_changeset(user, user_params)
@@ -197,7 +206,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
with true <- Map.has_key?(params, params_field),
- {:ok, new_value} <- value_function.(params[params_field]) do
+ {:ok, new_value} <- value_function.(Map.get(params, params_field)) do
Map.put(map, map_field, new_value)
else
_ -> map
@@ -208,12 +217,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
if Enum.all?(fields, &is_tuple/1) do
Enum.map(fields, fn {_, v} -> v end)
else
- fields
+ Enum.map(fields, fn
+ %{} = field -> %{"name" => field.name, "value" => field.value}
+ field -> field
+ end)
end
end
@doc "GET /api/v1/accounts/relationships"
- def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ def relationships(%{assigns: %{user: user}} = conn, %{id: id}) do
targets = User.get_all_by_ids(List.wrap(id))
render(conn, "relationships.json", user: user, targets: targets)
@@ -223,7 +235,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
@doc "GET /api/v1/accounts/:id"
- def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
+ def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
true <- User.visible_for?(user, for_user) do
render(conn, "show.json", user: user, for: for_user)
@@ -234,12 +246,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
@doc "GET /api/v1/accounts/:id/statuses"
def statuses(%{assigns: %{user: reading_user}} = conn, params) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user),
+ with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user),
true <- User.visible_for?(user, reading_user) do
params =
params
- |> Map.put("tag", params["tagged"])
- |> Map.delete("godmode")
+ |> Map.delete(:tagged)
+ |> Enum.filter(&(not is_nil(&1)))
+ |> Map.new(fn {key, value} -> {to_string(key), value} end)
+ |> Map.put("tag", params[:tagged])
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
@@ -259,6 +273,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
@doc "GET /api/v1/accounts/:id/followers"
def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ params =
+ params
+ |> Enum.map(fn {key, value} -> {to_string(key), value} end)
+ |> Enum.into(%{})
+
followers =
cond do
for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
@@ -273,6 +292,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
@doc "GET /api/v1/accounts/:id/following"
def following(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ params =
+ params
+ |> Enum.map(fn {key, value} -> {to_string(key), value} end)
+ |> Enum.into(%{})
+
followers =
cond do
for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
@@ -299,8 +323,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
{:error, "Can not follow yourself"}
end
- def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
- with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
+ def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
+ with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
render(conn, "relationship.json", user: follower, target: followed)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
@@ -319,10 +343,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
@doc "POST /api/v1/accounts/:id/mute"
- def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
- notifications? = params |> Map.get("notifications", true) |> truthy_param?()
-
- with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do
+ def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
+ with {:ok, _user_relationships} <- User.mute(muter, muted, params.notifications) do
render(conn, "relationship.json", user: muter, target: muted)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
@@ -359,7 +381,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
@doc "POST /api/v1/follows"
- def follow_by_uri(conn, %{"uri" => uri}) do
+ def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do
%User{} = user ->
conn
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 45601ff59..9eea2e9eb 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -127,7 +127,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
def create(
%{assigns: %{user: user}} = conn,
%{"status" => _, "scheduled_at" => scheduled_at} = params
- ) do
+ )
+ when not is_nil(scheduled_at) do
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)},
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index fb6b18ed5..2d67e19da 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -44,6 +44,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.put("type", ["Create", "Announce"])
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
+ |> Map.put("reply_filtering_user", user)
|> Map.put("user", user)
recipients = [user.ap_id | User.following(user)]
@@ -109,6 +110,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.put("local_only", local_only)
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
+ |> Map.put("reply_filtering_user", user)
|> ActivityPub.fetch_public_activities()
conn
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index b5850e1ae..24167f66f 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -45,7 +45,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end)
end
- defp get_user(ap_id) do
+ def get_user(ap_id, fake_record_fallback \\ true) do
cond do
user = User.get_cached_by_ap_id(ap_id) ->
user
@@ -53,8 +53,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
user = User.get_by_guessed_nickname(ap_id) ->
user
- true ->
+ fake_record_fallback ->
+ # TODO: refactor (fake records is never a good idea)
User.error_user(ap_id)
+
+ true ->
+ nil
end
end
@@ -97,7 +101,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
UserRelationship.view_relationships_option(nil, [])
true ->
- actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
+ # Note: unresolved users are filtered out
+ actors =
+ (activities ++ parent_activities)
+ |> Enum.map(&get_user(&1.data["actor"], false))
+ |> Enum.filter(& &1)
UserRelationship.view_relationships_option(reading_user, actors,
source_mutes_only: opts[:skip_relationships]
@@ -521,11 +529,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
"""
@spec build_tags(list(any())) :: list(map())
def build_tags(object_tags) when is_list(object_tags) do
- object_tags = for tag when is_binary(tag) <- object_tags, do: tag
-
- Enum.reduce(object_tags, [], fn tag, tags ->
- tags ++ [%{name: tag, url: "/tag/#{URI.encode(tag)}"}]
- end)
+ object_tags
+ |> Enum.filter(&is_binary/1)
+ |> Enum.map(&%{name: &1, url: "/tag/#{URI.encode(&1)}"})
end
def build_tags(_), do: []
diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
index 04d823b36..1ed6ee521 100644
--- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
+++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password)
def user_exists(conn, %{"user" => username}) do
- with %User{} <- Repo.get_by(User, nickname: username, local: true) do
+ with %User{} <- Repo.get_by(User, nickname: username, local: true, deactivated: false) do
conn
|> json(true)
else
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
end
def check_password(conn, %{"user" => username, "pass" => password}) do
- with %User{password_hash: password_hash} <-
+ with %User{password_hash: password_hash, deactivated: false} <-
Repo.get_by(User, nickname: username, local: true),
true <- Pbkdf2.checkpw(password, password_hash) do
conn
diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex
index 1023f16d4..6f06f1431 100644
--- a/lib/pleroma/web/oauth/scopes.ex
+++ b/lib/pleroma/web/oauth/scopes.ex
@@ -17,12 +17,8 @@ defmodule Pleroma.Web.OAuth.Scopes do
"""
@spec fetch_scopes(map() | struct(), list()) :: list()
- def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do
- parse_scopes(scopes, default)
- end
-
def fetch_scopes(params, default) do
- parse_scopes(params["scope"] || params["scopes"], default)
+ parse_scopes(params["scope"] || params["scopes"] || params[:scopes], default)
end
def parse_scopes(scopes, _default) when is_list(scopes) do
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 7a1ba6936..cf1d9c74c 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -12,73 +12,57 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
require Pleroma.Constants
def register_user(params, opts \\ []) do
- token = params["token"]
- trusted_app? = params["trusted_app"]
-
- params = %{
- nickname: params["nickname"],
- name: params["fullname"],
- bio: User.parse_bio(params["bio"]),
- email: params["email"],
- password: params["password"],
- password_confirmation: params["confirm"],
- captcha_solution: params["captcha_solution"],
- captcha_token: params["captcha_token"],
- captcha_answer_data: params["captcha_answer_data"]
- }
-
- captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled])
- # true if captcha is disabled or enabled and valid, false otherwise
- captcha_ok =
- if trusted_app? || not captcha_enabled do
- :ok
- else
- Pleroma.Captcha.validate(
- params[:captcha_token],
- params[:captcha_solution],
- params[:captcha_answer_data]
- )
- end
-
- # Captcha invalid
- if captcha_ok != :ok do
- {:error, error} = captcha_ok
- # I have no idea how this error handling works
- {:error, %{error: Jason.encode!(%{captcha: [error]})}}
- else
- registration_process(
- params,
- %{
- registrations_open: Pleroma.Config.get([:instance, :registrations_open]),
- token: token
- },
- opts
- )
+ 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])
+
+ 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 registration_process(params, %{registrations_open: true}, opts) do
- create_user(params, opts)
+ defp validate_captcha(params) do
+ if params[:trusted_app] || not Pleroma.Config.get([Pleroma.Captcha, :enabled]) do
+ :ok
+ else
+ Pleroma.Captcha.validate(
+ params.captcha_token,
+ params.captcha_solution,
+ params.captcha_answer_data
+ )
+ end
end
- defp registration_process(params, %{token: token}, opts) do
- invite =
- unless is_nil(token) do
- Repo.get_by(UserInviteToken, %{token: token})
- end
-
- valid_invite? = invite && UserInviteToken.valid_invite?(invite)
-
- case invite do
- nil ->
- {:error, "Invalid token"}
-
- invite when valid_invite? ->
- UserInviteToken.update_usage!(invite)
- create_user(params, opts)
-
- _ ->
- {:error, "Expired token"}
+ defp create_user_with_invite(params, opts) do
+ with %{token: token} when is_binary(token) <- params,
+ %UserInviteToken{} = invite <- Repo.get_by(UserInviteToken, %{token: token}),
+ true <- UserInviteToken.valid_invite?(invite) do
+ UserInviteToken.update_usage!(invite)
+ create_user(params, opts)
+ else
+ nil -> {:error, "Invalid token"}
+ _ -> {:error, "Expired token"}
end
end