Merge branch 'develop' into issue/1276

This commit is contained in:
Maksim Pechnikov 2020-04-13 21:19:27 +03:00
commit a16b3dbcbf
66 changed files with 1102 additions and 291 deletions

View file

@ -5,7 +5,6 @@
defmodule Mix.Pleroma do
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Mix.Task.run("app.start")
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
if Pleroma.Config.get(:env) != :test do

View file

@ -67,7 +67,8 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
activities: activities,
for: user,
as: :activity
as: :activity,
skip_relationships: true
})
end
},

View file

@ -4,10 +4,16 @@
import EctoEnum
defenum(UserRelationshipTypeEnum,
defenum(Pleroma.UserRelationship.Type,
block: 1,
mute: 2,
reblog_mute: 3,
notification_mute: 4,
inverse_subscription: 5
)
defenum(Pleroma.FollowingRelationship.State,
follow_pending: 1,
follow_accept: 2,
follow_reject: 3
)

View file

@ -8,12 +8,13 @@ defmodule Pleroma.FollowingRelationship do
import Ecto.Changeset
import Ecto.Query
alias Ecto.Changeset
alias FlakeId.Ecto.CompatType
alias Pleroma.Repo
alias Pleroma.User
schema "following_relationships" do
field(:state, :string, default: "accept")
field(:state, Pleroma.FollowingRelationship.State, default: :follow_pending)
belongs_to(:follower, User, type: CompatType)
belongs_to(:following, User, type: CompatType)
@ -27,6 +28,18 @@ defmodule Pleroma.FollowingRelationship do
|> put_assoc(:follower, attrs.follower)
|> put_assoc(:following, attrs.following)
|> validate_required([:state, :follower, :following])
|> unique_constraint(:follower_id,
name: :following_relationships_follower_id_following_id_index
)
|> validate_not_self_relationship()
end
def state_to_enum(state) when state in ["pending", "accept", "reject"] do
String.to_existing_atom("follow_#{state}")
end
def state_to_enum(state) do
raise "State is not convertible to Pleroma.FollowingRelationship.State: #{state}"
end
def get(%User{} = follower, %User{} = following) do
@ -35,7 +48,7 @@ defmodule Pleroma.FollowingRelationship do
|> Repo.one()
end
def update(follower, following, "reject"), do: unfollow(follower, following)
def update(follower, following, :follow_reject), do: unfollow(follower, following)
def update(%User{} = follower, %User{} = following, state) do
case get(follower, following) do
@ -50,7 +63,7 @@ defmodule Pleroma.FollowingRelationship do
end
end
def follow(%User{} = follower, %User{} = following, state \\ "accept") do
def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do
%__MODULE__{}
|> changeset(%{follower: follower, following: following, state: state})
|> Repo.insert(on_conflict: :nothing)
@ -80,7 +93,7 @@ defmodule Pleroma.FollowingRelationship do
def get_follow_requests(%User{id: id}) do
__MODULE__
|> join(:inner, [r], f in assoc(r, :follower))
|> where([r], r.state == "pending")
|> where([r], r.state == ^:follow_pending)
|> where([r], r.following_id == ^id)
|> select([r, f], f)
|> Repo.all()
@ -88,7 +101,7 @@ defmodule Pleroma.FollowingRelationship do
def following?(%User{id: follower_id}, %User{id: followed_id}) do
__MODULE__
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept)
|> Repo.exists?()
end
@ -97,7 +110,7 @@ defmodule Pleroma.FollowingRelationship do
__MODULE__
|> join(:inner, [r], u in User, on: r.following_id == u.id)
|> where([r], r.follower_id == ^user.id)
|> where([r], r.state == "accept")
|> where([r], r.state == ^:follow_accept)
|> select([r, u], u.follower_address)
|> Repo.all()
@ -157,4 +170,30 @@ defmodule Pleroma.FollowingRelationship do
fr -> fr.follower_id == follower.id and fr.following_id == following.id
end)
end
defp validate_not_self_relationship(%Changeset{} = changeset) do
changeset
|> validate_follower_id_following_id_inequality()
|> validate_following_id_follower_id_inequality()
end
defp validate_follower_id_following_id_inequality(%Changeset{} = changeset) do
validate_change(changeset, :follower_id, fn _, follower_id ->
if follower_id == get_field(changeset, :following_id) do
[source_id: "can't be equal to following_id"]
else
[]
end
end)
end
defp validate_following_id_follower_id_inequality(%Changeset{} = changeset) do
validate_change(changeset, :following_id, fn _, following_id ->
if following_id == get_field(changeset, :follower_id) do
[target_id: "can't be equal to follower_id"]
else
[]
end
end)
end
end

View file

@ -35,9 +35,19 @@ defmodule Pleroma.Formatter do
nickname_text = get_nickname_text(nickname, opts)
link =
~s(<span class="h-card"><a data-user="#{id}" class="u-url mention" href="#{ap_id}" rel="ugc">@<span>#{
nickname_text
}</span></a></span>)
Phoenix.HTML.Tag.content_tag(
:span,
Phoenix.HTML.Tag.content_tag(
:a,
["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
"data-user": id,
class: "u-url mention",
href: ap_id,
rel: "ugc"
),
class: "h-card"
)
|> Phoenix.HTML.safe_to_string()
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
@ -49,7 +59,15 @@ defmodule Pleroma.Formatter do
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
tag = String.downcase(tag)
url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
link = ~s(<a class="hashtag" data-tag="#{tag}" href="#{url}" rel="tag ugc">#{tag_text}</a>)
link =
Phoenix.HTML.Tag.content_tag(:a, tag_text,
class: "hashtag",
"data-tag": tag,
href: url,
rel: "tag ugc"
)
|> Phoenix.HTML.safe_to_string()
{link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
end

View file

@ -49,8 +49,10 @@ defmodule Pleroma.Gun.Conn do
key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
max_connections = pool_opts[:max_connections] || 250
conn_pid =
if Connections.count(name) < opts[:max_connection] do
if Connections.count(name) < max_connections do
do_open(uri, opts)
else
close_least_used_and_do_open(name, uri, opts)

View file

@ -16,6 +16,7 @@ defmodule Pleroma.User do
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
alias Pleroma.FollowingRelationship
alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.Keys
alias Pleroma.Notification
@ -452,7 +453,7 @@ defmodule Pleroma.User do
fields =
raw_fields
|> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
|> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
changeset
|> put_change(:raw_fields, raw_fields)
@ -462,6 +463,12 @@ defmodule Pleroma.User do
end
end
defp parse_fields(value) do
value
|> Formatter.linkify(mentions_format: :full)
|> elem(0)
end
defp put_change_if_present(changeset, map_field, value_function) do
if value = get_change(changeset, map_field) do
with {:ok, new_value} <- value_function.(value) do
@ -693,7 +700,7 @@ defmodule Pleroma.User do
@spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
follow(follower, followed, "pending")
follow(follower, followed, :follow_pending)
end
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
@ -713,14 +720,14 @@ defmodule Pleroma.User do
def follow_all(follower, followeds) do
followeds
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
|> Enum.each(&follow(follower, &1, "accept"))
|> Enum.each(&follow(follower, &1, :follow_accept))
set_cache(follower)
end
defdelegate following(user), to: FollowingRelationship
def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
cond do
@ -747,7 +754,7 @@ defmodule Pleroma.User do
def unfollow(%User{} = follower, %User{} = followed) do
case get_follow_state(follower, followed) do
state when state in ["accept", "pending"] ->
state when state in [:follow_pending, :follow_accept] ->
FollowingRelationship.unfollow(follower, followed)
{:ok, followed} = update_follower_count(followed)
@ -765,6 +772,7 @@ defmodule Pleroma.User do
defdelegate following?(follower, followed), to: FollowingRelationship
@doc "Returns follow state as Pleroma.FollowingRelationship.State value"
def get_follow_state(%User{} = follower, %User{} = following) do
following_relationship = FollowingRelationship.get(follower, following)
get_follow_state(follower, following, following_relationship)
@ -778,8 +786,11 @@ defmodule Pleroma.User do
case {following_relationship, following.local} do
{nil, false} ->
case Utils.fetch_latest_follow(follower, following) do
%{data: %{"state" => state}} when state in ["pending", "accept"] -> state
_ -> nil
%Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
FollowingRelationship.state_to_enum(state)
_ ->
nil
end
{%{state: state}, _} ->
@ -1278,7 +1289,7 @@ defmodule Pleroma.User do
def blocks?(%User{} = user, %User{} = target) do
blocks_user?(user, target) ||
(!User.following?(user, target) && blocks_domain?(user, target))
(blocks_domain?(user, target) and not User.following?(user, target))
end
def blocks_user?(%User{} = user, %User{} = target) do
@ -1979,17 +1990,6 @@ defmodule Pleroma.User do
def fields(%{fields: fields}), do: fields
def sanitized_fields(%User{} = user) do
user
|> User.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
"name" => name,
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
}
end)
end
def validate_fields(changeset, remote? \\ false) do
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
limit = Pleroma.Config.get([:instance, limit_name], 0)

View file

@ -148,7 +148,7 @@ defmodule Pleroma.User.Query do
as: :relationships,
on: r.following_id == ^id and r.follower_id == u.id
)
|> where([relationships: r], r.state == "accept")
|> where([relationships: r], r.state == ^:follow_accept)
end
defp compose_query({:friends, %User{id: id}}, query) do
@ -158,7 +158,7 @@ defmodule Pleroma.User.Query do
as: :relationships,
on: r.following_id == u.id and r.follower_id == ^id
)
|> where([relationships: r], r.state == "accept")
|> where([relationships: r], r.state == ^:follow_accept)
end
defp compose_query({:recipients_from_activity, to}, query) do
@ -173,7 +173,7 @@ defmodule Pleroma.User.Query do
)
|> where(
[u, following: f, relationships: r],
u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept")
u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept)
)
|> distinct(true)
end

View file

@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do
import Ecto.Changeset
import Ecto.Query
alias Ecto.Changeset
alias Pleroma.FollowingRelationship
alias Pleroma.Repo
alias Pleroma.User
@ -16,12 +17,12 @@ defmodule Pleroma.UserRelationship do
schema "user_relationships" do
belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
field(:relationship_type, UserRelationshipTypeEnum)
field(:relationship_type, Pleroma.UserRelationship.Type)
timestamps(updated_at: false)
end
for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
# `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
# `def create_notification_mute/2`, `def create_inverse_subscription/2`
def unquote(:"create_#{relationship_type}")(source, target),
@ -40,7 +41,7 @@ defmodule Pleroma.UserRelationship do
def user_relationship_types, do: Keyword.keys(user_relationship_mappings())
def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__()
def user_relationship_mappings, do: Pleroma.UserRelationship.Type.__enum_map__()
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
user_relationship
@ -129,17 +130,27 @@ defmodule Pleroma.UserRelationship do
end
@doc ":relationships option for StatusView / AccountView / NotificationView"
def view_relationships_option(nil = _reading_user, _actors) do
def view_relationships_option(reading_user, actors, opts \\ [])
def view_relationships_option(nil = _reading_user, _actors, _opts) do
%{user_relationships: [], following_relationships: []}
end
def view_relationships_option(%User{} = reading_user, actors) do
def view_relationships_option(%User{} = reading_user, actors, opts) do
{source_to_target_rel_types, target_to_source_rel_types} =
if opts[:source_mutes_only] do
# This option is used for rendering statuses (FE needs `muted` flag for each one anyways)
{[:mute], []}
else
{[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
end
user_relationships =
UserRelationship.dictionary(
[reading_user],
actors,
[:block, :mute, :notification_mute, :reblog_mute],
[:block, :inverse_subscription]
source_to_target_rel_types,
target_to_source_rel_types
)
following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
@ -147,16 +158,14 @@ defmodule Pleroma.UserRelationship do
%{user_relationships: user_relationships, following_relationships: following_relationships}
end
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
defp validate_not_self_relationship(%Changeset{} = changeset) do
changeset
|> validate_change(:target_id, fn _, target_id ->
if target_id == get_field(changeset, :source_id) do
[target_id: "can't be equal to source_id"]
else
[]
end
end)
|> validate_change(:source_id, fn _, source_id ->
|> validate_source_id_target_id_inequality()
|> validate_target_id_source_id_inequality()
end
defp validate_source_id_target_id_inequality(%Changeset{} = changeset) do
validate_change(changeset, :source_id, fn _, source_id ->
if source_id == get_field(changeset, :target_id) do
[source_id: "can't be equal to target_id"]
else
@ -164,4 +173,14 @@ defmodule Pleroma.UserRelationship do
end
end)
end
defp validate_target_id_source_id_inequality(%Changeset{} = changeset) do
validate_change(changeset, :target_id, fn _, target_id ->
if target_id == get_field(changeset, :source_id) do
[target_id: "can't be equal to source_id"]
else
[]
end
end)
end
end

View file

@ -721,7 +721,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
defp fetch_activities_for_context_query(context, opts) do
def fetch_activities_for_context_query(context, opts) do
public = [Constants.as_public()]
recipients =

View file

@ -6,14 +6,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
def cast(object) when is_binary(object) do
# Host has to be present and scheme has to be an http scheme (for now)
case URI.parse(object) do
%URI{host: nil} ->
:error
%URI{scheme: scheme} when scheme in ["https", "http"] ->
{:ok, object}
_ ->
:error
%URI{host: nil} -> :error
%URI{host: ""} -> :error
%URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object}
_ -> :error
end
end

View file

@ -205,16 +205,46 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("conversation", context)
end
defp add_if_present(map, _key, nil), do: map
defp add_if_present(map, key, value) do
Map.put(map, key, value)
end
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
attachments =
Enum.map(attachment, fn data ->
media_type = data["mediaType"] || data["mimeType"]
href = data["url"] || data["href"]
url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]
url =
cond do
is_list(data["url"]) -> List.first(data["url"])
is_map(data["url"]) -> data["url"]
true -> nil
end
data
|> Map.put("mediaType", media_type)
|> Map.put("url", url)
media_type =
cond do
is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"]
is_binary(data["mediaType"]) -> data["mediaType"]
is_binary(data["mimeType"]) -> data["mimeType"]
true -> nil
end
href =
cond do
is_map(url) && is_binary(url["href"]) -> url["href"]
is_binary(data["url"]) -> data["url"]
is_binary(data["href"]) -> data["href"]
end
attachment_url =
%{"href" => href}
|> add_if_present("mediaType", media_type)
|> add_if_present("type", Map.get(url || %{}, "type"))
%{"url" => [attachment_url]}
|> add_if_present("mediaType", media_type)
|> add_if_present("type", data["type"])
|> add_if_present("name", data["name"])
end)
Map.put(object, "attachment", attachments)
@ -494,7 +524,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
{_, {:ok, _}} <-
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
{:ok, _relationship} <-
FollowingRelationship.update(follower, followed, :follow_accept) do
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed,
@ -504,7 +535,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
else
{:user_blocked, true} ->
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
ActivityPub.reject(%{
to: [follower.ap_id],
@ -515,7 +546,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:follow, {:error, _}} ->
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
ActivityPub.reject(%{
to: [follower.ap_id],
@ -525,7 +556,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
})
{:user_locked, true} ->
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending")
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
:noop
end
@ -545,7 +576,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
ActivityPub.accept(%{
to: follow_activity.data["to"],
type: "Accept",
@ -568,7 +599,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
{:ok, activity} <-
ActivityPub.reject(%{
to: follow_activity.data["to"],

View file

@ -258,7 +258,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
conn
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|> render("index.json", %{activities: activities, as: :activity})
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
end
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
@ -277,7 +277,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
conn
|> put_view(StatusView)
|> render("index.json", %{activities: activities, as: :activity})
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
else
_ -> {:error, :not_found}
end
@ -576,9 +576,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@doc "Sends registration invite via email"
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
with true <-
Config.get([:instance, :invites_enabled]) &&
!Config.get([:instance, :registrations_open]),
with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
{_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
{:ok, invite_token} <- UserInviteToken.create_invite(),
email <-
Pleroma.Emails.UserEmail.user_invitation_email(
@ -589,6 +588,18 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
),
{:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
json_response(conn, :no_content, "")
else
{:registrations_open, _} ->
errors(
conn,
{:error, "To send invites you need to set the `registrations_open` option to false."}
)
{:invites_enabled, _} ->
errors(
conn,
{:error, "To send invites you need to set the `invites_enabled` option to true."}
)
end
end
@ -801,7 +812,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
conn
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|> render("index.json", %{activities: activities, as: :activity})
|> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
end
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do

View file

@ -38,7 +38,12 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
actor: merge_account_views(user),
content: content,
created_at: created_at,
statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
statuses:
StatusView.render("index.json", %{
activities: statuses,
as: :activity,
skip_relationships: false
}),
state: report.data["state"],
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
}

View file

@ -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 do
alias OpenApiSpex.OpenApi
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
@behaviour OpenApi
@impl OpenApi
def spec do
%OpenApi{
servers: [
# Populate the Server info from a phoenix endpoint
OpenApiSpex.Server.from_endpoint(Endpoint)
],
info: %OpenApiSpex.Info{
title: "Pleroma",
description: Application.spec(:pleroma, :description) |> to_string(),
version: Application.spec(:pleroma, :vsn) |> to_string()
},
# populate the paths from a phoenix router
paths: OpenApiSpex.Paths.from_router(Router),
components: %OpenApiSpex.Components{
securitySchemes: %{
"oAuth" => %OpenApiSpex.SecurityScheme{
type: "oauth2",
flows: %OpenApiSpex.OAuthFlows{
password: %OpenApiSpex.OAuthFlow{
authorizationUrl: "/oauth/authorize",
tokenUrl: "/oauth/token",
scopes: %{"read" => "read"}
}
}
}
}
}
}
# discover request/response schemas from path specs
|> OpenApiSpex.resolve_schema_modules()
end
end

View file

@ -0,0 +1,27 @@
# 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.Helpers do
def request_body(description, schema_ref, opts \\ []) do
media_types = ["application/json", "multipart/form-data"]
content =
media_types
|> Enum.map(fn type ->
{type,
%OpenApiSpex.MediaType{
schema: schema_ref,
example: opts[:example],
examples: opts[:examples]
}}
end)
|> Enum.into(%{})
%OpenApiSpex.RequestBody{
description: description,
content: content,
required: opts[:required] || false
}
end
end

View file

@ -0,0 +1,96 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.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
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
@spec create_operation() :: Operation.t()
def create_operation do
%Operation{
tags: ["apps"],
summary: "Create an application",
description: "Create a new application to obtain OAuth2 credentials",
operationId: "AppController.create",
requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
responses: %{
200 => Operation.response("App", "application/json", AppCreateResponse),
422 =>
Operation.response(
"Unprocessable Entity",
"application/json",
%Schema{
type: :object,
description:
"If a required parameter is missing or improperly formatted, the request will fail.",
properties: %{
error: %Schema{type: :string}
},
example: %{
"error" => "Validation failed: Redirect URI must be an absolute URI."
}
}
)
}
}
end
def verify_credentials_operation do
%Operation{
tags: ["apps"],
summary: "Verify your app works",
description: "Confirm that the app's OAuth2 credentials work.",
operationId: "AppController.verify_credentials",
security: [
%{
"oAuth" => ["read"]
}
],
responses: %{
200 =>
Operation.response("App", "application/json", %Schema{
type: :object,
description:
"If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.",
properties: %{
name: %Schema{type: :string},
vapid_key: %Schema{type: :string},
website: %Schema{type: :string, nullable: true}
},
example: %{
"name" => "My App",
"vapid_key" =>
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
"website" => "https://myapp.com/"
}
}),
422 =>
Operation.response(
"Unauthorized",
"application/json",
%Schema{
type: :object,
description:
"If the Authorization header contains an invalid token, is malformed, or is not present, an error will be returned indicating an authorization failure.",
properties: %{
error: %Schema{type: :string}
},
example: %{
"error" => "The access token is invalid."
}
}
)
}
}
end
end

View file

@ -0,0 +1,33 @@
# 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

View file

@ -0,0 +1,33 @@
# 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

View file

@ -187,7 +187,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
end
defp preview?(draft) do
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"])
%__MODULE__{draft | preview?: preview?}
end

View file

@ -45,7 +45,7 @@ defmodule Pleroma.Web.CommonAPI do
with {:ok, follower} <- User.follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),
{:ok, _activity} <-
ActivityPub.accept(%{
to: [follower.ap_id],
@ -60,7 +60,7 @@ defmodule Pleroma.Web.CommonAPI do
def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
{:ok, _activity} <-
ActivityPub.reject(%{
to: [follower.ap_id],

View file

@ -5,10 +5,18 @@
defmodule Pleroma.Web.ControllerHelper do
use Pleroma.Web, :controller
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
alias Pleroma.Config
# As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
@falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
def truthy_param?(value), do: value not in @falsy_param_values
def explicitly_falsy_param?(value), do: value in @falsy_param_values
# Note: `nil` and `""` are considered falsy values in Pleroma
def falsy_param?(value),
do: explicitly_falsy_param?(value) or value in [nil, ""]
def truthy_param?(value), do: not falsy_param?(value)
def json_response(conn, status, json) do
conn
@ -96,4 +104,14 @@ defmodule Pleroma.Web.ControllerHelper do
def put_if_exist(map, _key, nil), do: map
def put_if_exist(map, key, value), do: Map.put(map, key, value)
@doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications"
def skip_relationships?(params) do
if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do
false
else
# BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships.
not truthy_param?(params["with_relationships"])
end
end
end

View file

@ -6,7 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
only: [
add_link_headers: 2,
truthy_param?: 1,
assign_account_by_id: 2,
json_response: 3,
skip_relationships?: 1
]
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
@ -237,7 +243,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
conn
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", activities: activities, for: reading_user, as: :activity)
|> render("index.json",
activities: activities,
for: reading_user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else
_e -> render_error(conn, :not_found, "Can't find user")
end

View file

@ -14,17 +14,20 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
plug(OpenApiSpex.Plug.CastAndValidate)
@local_mastodon_name "Mastodon-Local"
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
@doc "POST /api/v1/apps"
def create(conn, params) do
def create(%{body_params: params} = conn, _params) do
scopes = Scopes.fetch_scopes(params, ["read"])
app_attrs =
params
|> Map.drop(["scope", "scopes"])
|> Map.put("scopes", scopes)
|> Map.take([:client_name, :redirect_uris, :website])
|> Map.put(:scopes, scopes)
with cs <- App.register_changeset(%App{}, app_attrs),
false <- cs.changes[:client_name] == @local_mastodon_name,

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.NotificationController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
@ -45,7 +45,11 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
conn
|> add_link_headers(notifications)
|> render("index.json", notifications: notifications, for: user)
|> render("index.json",
notifications: notifications,
for: user,
skip_relationships: skip_relationships?(params)
)
end
# GET /api/v1/notifications/:id
@ -66,7 +70,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
json(conn, %{})
end
# POST /api/v1/notifications/dismiss
# POST /api/v1/notifications/:id/dismiss
# POST /api/v1/notifications/dismiss (deprecated)
def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
with {:ok, _notif} <- Notification.dismiss(user, id) do
json(conn, %{})

View file

@ -5,13 +5,14 @@
defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1]
alias Pleroma.Activity
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
@ -66,10 +67,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
defp search_options(params, user) do
[
skip_relationships: skip_relationships?(params),
resolve: params["resolve"] == "true",
following: params["following"] == "true",
limit: ControllerHelper.fetch_integer_param(params, "limit"),
offset: ControllerHelper.fetch_integer_param(params, "offset"),
limit: fetch_integer_param(params, "limit"),
offset: fetch_integer_param(params, "offset"),
type: params["type"],
author: get_author(params),
for_user: user
@ -79,12 +81,24 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
defp resource_search(_, "accounts", query, options) do
accounts = with_fallback(fn -> User.search(query, options) end)
AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user)
AccountView.render("index.json",
users: accounts,
for: options[:for_user],
as: :user,
skip_relationships: false
)
end
defp resource_search(_, "statuses", query, options) do
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
StatusView.render("index.json",
activities: statuses,
for: options[:for_user],
as: :activity,
skip_relationships: options[:skip_relationships]
)
end
defp resource_search(:v2, "hashtags", query, _options) do

View file

@ -5,7 +5,8 @@
defmodule Pleroma.Web.MastodonAPI.StatusController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2]
import Pleroma.Web.ControllerHelper,
only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]
require Ecto.Query
@ -101,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
`ids` query param is required
"""
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
limit = 100
activities =
@ -110,7 +111,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|> Activity.all_by_ids_with_object()
|> Enum.filter(&Visibility.visible_for_user?(&1, user))
render(conn, "index.json", activities: activities, for: user, as: :activity)
render(conn, "index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end
@doc """
@ -360,7 +366,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
conn
|> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity)
|> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end
@doc "GET /api/v1/bookmarks"
@ -378,6 +389,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
conn
|> add_link_headers(bookmarks)
|> render("index.json", %{activities: activities, for: user, as: :activity})
|> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end
end

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1]
alias Pleroma.Pagination
alias Pleroma.Plugs.OAuthScopesPlug
@ -14,9 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
# TODO: Replace with a macro when there is a Phoenix release with
# TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
# in it
plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)
plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public)
@ -49,7 +48,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
conn
|> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity)
|> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end
# GET /api/v1/timelines/direct
@ -68,7 +72,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
conn
|> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity)
|> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end
# GET /api/v1/timelines/public
@ -95,7 +104,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
conn
|> add_link_headers(activities, %{"local" => local_only})
|> render("index.json", activities: activities, for: user, as: :activity)
|> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else
render_error(conn, :unauthorized, "authorization required for timeline view")
end
@ -140,7 +154,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
conn
|> add_link_headers(activities, %{"local" => local_only})
|> render("index.json", activities: activities, for: user, as: :activity)
|> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end
# GET /api/v1/timelines/list/:list_id
@ -164,7 +183,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> ActivityPub.fetch_activities_bounded(following, params)
|> Enum.reverse()
render(conn, "index.json", activities: activities, for: user, as: :activity)
render(conn, "index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else
_e -> render_error(conn, :forbidden, "Error.")
end

View file

@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
def render("index.json", %{users: users} = opts) do
reading_user = opts[:for]
# Note: :skip_relationships option is currently intentionally not supported for accounts
relationships_opt =
cond do
Map.has_key?(opts, :relationships) ->
@ -73,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
followed_by =
if following_relationships do
case FollowingRelationship.find(following_relationships, target, reading_user) do
%{state: "accept"} -> true
%{state: :follow_accept} -> true
_ -> false
end
else
@ -83,7 +84,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
%{
id: to_string(target.id),
following: follow_state == "accept",
following: follow_state == :follow_accept,
followed_by: followed_by,
blocking:
UserRelationship.exists?(
@ -125,7 +126,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
reading_user,
&User.subscribed_to?(&2, &1)
),
requested: follow_state == "pending",
requested: follow_state == :follow_pending,
domain_blocking: User.blocks_domain?(reading_user, target),
showing_reblogs:
not UserRelationship.exists?(
@ -192,11 +193,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end)
relationship =
render("relationship.json", %{
user: opts[:for],
target: user,
relationships: opts[:relationships]
})
if opts[:skip_relationships] do
%{}
else
render("relationship.json", %{
user: opts[:for],
target: user,
relationships: opts[:relationships]
})
end
%{
id: to_string(user.id),

View file

@ -51,14 +51,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|> Enum.filter(& &1)
|> Kernel.++(move_activities_targets)
UserRelationship.view_relationships_option(reading_user, actors)
UserRelationship.view_relationships_option(reading_user, actors,
source_mutes_only: opts[:skip_relationships]
)
end
opts = %{
for: reading_user,
parent_activities: parent_activities,
relationships: relationships_opt
}
opts =
opts
|> Map.put(:parent_activities, parent_activities)
|> Map.put(:relationships, relationships_opt)
safe_render_many(notifications, NotificationView, "show.json", opts)
end
@ -82,12 +83,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
mastodon_type = Activity.mastodon_notification_type(activity)
render_opts = %{
relationships: opts[:relationships],
skip_relationships: opts[:skip_relationships]
}
with %{id: _} = account <-
AccountView.render("show.json", %{
user: actor,
for: reading_user,
relationships: opts[:relationships]
}) do
AccountView.render(
"show.json",
Map.merge(render_opts, %{user: actor, for: reading_user})
) do
response = %{
id: to_string(notification.id),
type: mastodon_type,
@ -98,8 +103,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
}
}
render_opts = %{relationships: opts[:relationships]}
case mastodon_type do
"mention" ->
put_status(response, activity, reading_user, render_opts)
@ -111,6 +114,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
put_status(response, parent_activity_fn.(), reading_user, render_opts)
"move" ->
# Note: :skip_relationships option being applied to _account_ rendering (here)
put_target(response, activity, reading_user, render_opts)
"follow" ->

View file

@ -99,7 +99,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
true ->
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
UserRelationship.view_relationships_option(reading_user, actors)
UserRelationship.view_relationships_option(reading_user, actors,
source_mutes_only: opts[:skip_relationships]
)
end
opts =
@ -153,7 +155,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
AccountView.render("show.json", %{
user: user,
for: opts[:for],
relationships: opts[:relationships]
relationships: opts[:relationships],
skip_relationships: opts[:skip_relationships]
}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
@ -301,6 +304,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
_ -> []
end
# Status muted state (would do 1 request per status unless user mutes are preloaded)
muted =
thread_muted? ||
UserRelationship.exists?(
@ -319,7 +323,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
AccountView.render("show.json", %{
user: user,
for: opts[:for],
relationships: opts[:relationships]
relationships: opts[:relationships],
skip_relationships: opts[:skip_relationships]
}),
in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),

View file

@ -15,7 +15,12 @@ defmodule Pleroma.Web.OAuth.Scopes do
Note: `scopes` is used by Mastodon supporting it but sticking to
OAuth's standard `scope` wherever we control it
"""
@spec fetch_scopes(map(), list()) :: list()
@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)
end

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1]
alias Ecto.Changeset
alias Pleroma.Plugs.OAuthScopesPlug
@ -139,7 +139,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
conn
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", activities: activities, for: for_user, as: :activity)
|> render("index.json",
activities: activities,
for: for_user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
@ -110,12 +110,11 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
end
def conversation_statuses(
%{assigns: %{user: user}} = conn,
%{assigns: %{user: %{id: user_id} = user}} = conn,
%{"id" => participation_id} = params
) do
with %Participation{} = participation <-
Participation.get(participation_id, preload: [:conversation]),
true <- user.id == participation.user_id do
with %Participation{user_id: ^user_id} = participation <-
Participation.get(participation_id, preload: [:conversation]) do
params =
params
|> Map.put("blocking_user", user)
@ -124,13 +123,19 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
activities =
participation.conversation.ap_id
|> ActivityPub.fetch_activities_for_context(params)
|> ActivityPub.fetch_activities_for_context_query(params)
|> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
|> Enum.reverse()
conn
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
|> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else
_error ->
conn
@ -184,13 +189,17 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
end
end
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
with notifications <- Notification.set_read_up_to(user, max_id) do
notifications = Enum.take(notifications, 80)
conn
|> put_view(NotificationView)
|> render("index.json", %{notifications: notifications, for: user})
|> render("index.json",
notifications: notifications,
for: user,
skip_relationships: skip_relationships?(params)
)
end
end
end

View file

@ -29,6 +29,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureUserKeyPlug)
plug(Pleroma.Plugs.IdempotencyPlug)
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end
pipeline :authenticated_api do
@ -44,6 +45,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Plugs.IdempotencyPlug)
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end
pipeline :admin_api do
@ -61,6 +63,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Plugs.UserIsAdminPlug)
plug(Pleroma.Plugs.IdempotencyPlug)
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end
pipeline :mastodon_html do
@ -94,10 +97,12 @@ defmodule Pleroma.Web.Router do
pipeline :config do
plug(:accepts, ["json", "xml"])
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end
pipeline :pleroma_api do
plug(:accepts, ["html", "json"])
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end
pipeline :mailbox_preview do
@ -347,9 +352,11 @@ defmodule Pleroma.Web.Router do
get("/notifications", NotificationController, :index)
get("/notifications/:id", NotificationController, :show)
post("/notifications/:id/dismiss", NotificationController, :dismiss)
post("/notifications/clear", NotificationController, :clear)
post("/notifications/dismiss", NotificationController, :dismiss)
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
# Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
post("/notifications/dismiss", NotificationController, :dismiss)
get("/scheduled_statuses", ScheduledActivityController, :index)
get("/scheduled_statuses/:id", ScheduledActivityController, :show)
@ -500,6 +507,12 @@ defmodule Pleroma.Web.Router do
)
end
scope "/api" do
pipe_through(:api)
get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
end
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
pipe_through(:authenticated_api)