Merge branch 'develop' into refactor/notification_settings
This commit is contained in:
commit
433c01b370
304 changed files with 14459 additions and 5927 deletions
|
|
@ -52,6 +52,7 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|
||||
defp do_migrate_to_db(config_file) do
|
||||
if File.exists?(config_file) do
|
||||
shell_info("Migrating settings from file: #{Path.expand(config_file)}")
|
||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
||||
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
||||
|
||||
|
|
@ -72,8 +73,7 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
group
|
||||
|> Pleroma.Config.Loader.filter_group(settings)
|
||||
|> Enum.each(fn {key, value} ->
|
||||
key = inspect(key)
|
||||
{:ok, _} = ConfigDB.update_or_create(%{group: inspect(group), key: key, value: value})
|
||||
{:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
|
||||
|
||||
shell_info("Settings for key #{key} migrated.")
|
||||
end)
|
||||
|
|
@ -131,12 +131,9 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
end
|
||||
|
||||
defp write(config, file) do
|
||||
value =
|
||||
config.value
|
||||
|> ConfigDB.from_binary()
|
||||
|> inspect(limit: :infinity)
|
||||
value = inspect(config.value, limit: :infinity)
|
||||
|
||||
IO.write(file, "config #{config.group}, #{config.key}, #{value}\r\n\r\n")
|
||||
IO.write(file, "config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
|
||||
|
||||
config
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Mix.Tasks.Pleroma.Database do
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Maintenance
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
|
@ -34,13 +35,7 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
)
|
||||
|
||||
if Keyword.get(options, :vacuum) do
|
||||
Logger.info("Runnning VACUUM FULL")
|
||||
|
||||
Repo.query!(
|
||||
"vacuum full;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
Maintenance.vacuum("full")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -94,13 +89,7 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
|> Repo.delete_all(timeout: :infinity)
|
||||
|
||||
if Keyword.get(options, :vacuum) do
|
||||
Logger.info("Runnning VACUUM FULL")
|
||||
|
||||
Repo.query!(
|
||||
"vacuum full;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
Maintenance.vacuum("full")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -135,4 +124,10 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
end)
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
def run(["vacuum", args]) do
|
||||
start_pleroma()
|
||||
|
||||
Maintenance.vacuum(args)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
{options, [], []} = parse_global_opts(args)
|
||||
|
||||
url_or_path = options[:manifest] || default_manifest()
|
||||
manifest = fetch_manifest(url_or_path)
|
||||
manifest = fetch_and_decode(url_or_path)
|
||||
|
||||
Enum.each(manifest, fn {name, info} ->
|
||||
to_print = [
|
||||
|
|
@ -42,12 +42,12 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
|
||||
url_or_path = options[:manifest] || default_manifest()
|
||||
|
||||
manifest = fetch_manifest(url_or_path)
|
||||
manifest = fetch_and_decode(url_or_path)
|
||||
|
||||
for pack_name <- pack_names do
|
||||
if Map.has_key?(manifest, pack_name) do
|
||||
pack = manifest[pack_name]
|
||||
src_url = pack["src"]
|
||||
src = pack["src"]
|
||||
|
||||
IO.puts(
|
||||
IO.ANSI.format([
|
||||
|
|
@ -57,11 +57,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
:normal,
|
||||
" from ",
|
||||
:underline,
|
||||
src_url
|
||||
src
|
||||
])
|
||||
)
|
||||
|
||||
binary_archive = Tesla.get!(client(), src_url).body
|
||||
{:ok, binary_archive} = fetch(src)
|
||||
archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
|
||||
|
||||
sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright]
|
||||
|
|
@ -74,8 +74,8 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
raise "Bad SHA256 for #{pack_name}"
|
||||
end
|
||||
|
||||
# The url specified in files should be in the same directory
|
||||
files_url =
|
||||
# The location specified in files should be in the same directory
|
||||
files_loc =
|
||||
url_or_path
|
||||
|> Path.dirname()
|
||||
|> Path.join(pack["files"])
|
||||
|
|
@ -88,11 +88,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
:normal,
|
||||
" from ",
|
||||
:underline,
|
||||
files_url
|
||||
files_loc
|
||||
])
|
||||
)
|
||||
|
||||
files = Tesla.get!(client(), files_url).body |> Jason.decode!()
|
||||
files = fetch_and_decode(files_loc)
|
||||
|
||||
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
|
||||
|
||||
|
|
@ -237,16 +237,26 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
end
|
||||
end
|
||||
|
||||
defp fetch_manifest(from) do
|
||||
Jason.decode!(
|
||||
if String.starts_with?(from, "http") do
|
||||
Tesla.get!(client(), from).body
|
||||
else
|
||||
File.read!(from)
|
||||
end
|
||||
)
|
||||
def run(["reload"]) do
|
||||
start_pleroma()
|
||||
Pleroma.Emoji.reload()
|
||||
IO.puts("Emoji packs have been reloaded.")
|
||||
end
|
||||
|
||||
defp fetch_and_decode(from) do
|
||||
with {:ok, json} <- fetch(from) do
|
||||
Jason.decode!(json)
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch("http" <> _ = from) do
|
||||
with {:ok, %{body: body}} <- Tesla.get(client(), from) do
|
||||
{:ok, body}
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch(path), do: File.read(path)
|
||||
|
||||
defp parse_global_opts(args) do
|
||||
OptionParser.parse(
|
||||
args,
|
||||
|
|
|
|||
|
|
@ -17,30 +17,53 @@ defmodule Mix.Tasks.Pleroma.RefreshCounterCache do
|
|||
def run([]) do
|
||||
Mix.Pleroma.start_pleroma()
|
||||
|
||||
["public", "unlisted", "private", "direct"]
|
||||
|> Enum.each(fn visibility ->
|
||||
count = status_visibility_count_query(visibility)
|
||||
name = "status_visibility_#{visibility}"
|
||||
CounterCache.set(name, count)
|
||||
Mix.Pleroma.shell_info("Set #{name} to #{count}")
|
||||
instances =
|
||||
Activity
|
||||
|> distinct([a], true)
|
||||
|> select([a], fragment("split_part(?, '/', 3)", a.actor))
|
||||
|> Repo.all()
|
||||
|
||||
instances
|
||||
|> Enum.with_index(1)
|
||||
|> Enum.each(fn {instance, i} ->
|
||||
counters = instance_counters(instance)
|
||||
CounterCache.set(instance, counters)
|
||||
|
||||
Mix.Pleroma.shell_info(
|
||||
"[#{i}/#{length(instances)}] Setting #{instance} counters: #{inspect(counters)}"
|
||||
)
|
||||
end)
|
||||
|
||||
Mix.Pleroma.shell_info("Done")
|
||||
end
|
||||
|
||||
defp status_visibility_count_query(visibility) do
|
||||
defp instance_counters(instance) do
|
||||
counters = %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0}
|
||||
|
||||
Activity
|
||||
|> where(
|
||||
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|
||||
|> where([a], fragment("split_part(?, '/', 3) = ?", a.actor, ^instance))
|
||||
|> select(
|
||||
[a],
|
||||
{fragment(
|
||||
"activity_visibility(?, ?, ?)",
|
||||
a.actor,
|
||||
a.recipients,
|
||||
a.data
|
||||
), count(a.id)}
|
||||
)
|
||||
|> group_by(
|
||||
[a],
|
||||
fragment(
|
||||
"activity_visibility(?, ?, ?) = ?",
|
||||
"activity_visibility(?, ?, ?)",
|
||||
a.actor,
|
||||
a.recipients,
|
||||
a.data,
|
||||
^visibility
|
||||
a.data
|
||||
)
|
||||
)
|
||||
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|
||||
|> Repo.aggregate(:count, :id, timeout: :timer.minutes(30))
|
||||
|> Repo.all(timeout: :timer.minutes(30))
|
||||
|> Enum.reduce(counters, fn {visibility, count}, acc ->
|
||||
Map.put(acc, visibility, count)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -144,6 +144,18 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def run(["reset_mfa", nickname]) do
|
||||
start_pleroma()
|
||||
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, _token} <- Pleroma.MFA.disable(user) do
|
||||
shell_info("Multi-Factor Authentication disabled for #{user.nickname}")
|
||||
else
|
||||
_ ->
|
||||
shell_error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["deactivate", nickname]) do
|
||||
start_pleroma()
|
||||
|
||||
|
|
|
|||
|
|
@ -24,16 +24,6 @@ defmodule Pleroma.Activity do
|
|||
|
||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||
|
||||
# https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
|
||||
@mastodon_notification_types %{
|
||||
"Create" => "mention",
|
||||
"Follow" => ["follow", "follow_request"],
|
||||
"Announce" => "reblog",
|
||||
"Like" => "favourite",
|
||||
"Move" => "move",
|
||||
"EmojiReact" => "pleroma:emoji_reaction"
|
||||
}
|
||||
|
||||
schema "activities" do
|
||||
field(:data, :map)
|
||||
field(:local, :boolean, default: true)
|
||||
|
|
@ -41,6 +31,10 @@ defmodule Pleroma.Activity do
|
|||
field(:recipients, {:array, :string}, default: [])
|
||||
field(:thread_muted?, :boolean, virtual: true)
|
||||
|
||||
# A field that can be used if you need to join some kind of other
|
||||
# id to order / paginate this field by
|
||||
field(:pagination_id, :string, virtual: true)
|
||||
|
||||
# This is a fake relation,
|
||||
# do not use outside of with_preloaded_user_actor/with_joined_user_actor
|
||||
has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id)
|
||||
|
|
@ -300,32 +294,6 @@ defmodule Pleroma.Activity do
|
|||
|
||||
def follow_accepted?(_), do: false
|
||||
|
||||
@spec mastodon_notification_type(Activity.t()) :: String.t() | nil
|
||||
|
||||
for {ap_type, type} <- @mastodon_notification_types, not is_list(type) do
|
||||
def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),
|
||||
do: unquote(type)
|
||||
end
|
||||
|
||||
def mastodon_notification_type(%Activity{data: %{"type" => "Follow"}} = activity) do
|
||||
if follow_accepted?(activity) do
|
||||
"follow"
|
||||
else
|
||||
"follow_request"
|
||||
end
|
||||
end
|
||||
|
||||
def mastodon_notification_type(%Activity{}), do: nil
|
||||
|
||||
@spec from_mastodon_notification_type(String.t()) :: String.t() | nil
|
||||
@doc "Converts Mastodon notification type to AR activity type"
|
||||
def from_mastodon_notification_type(type) do
|
||||
with {k, _v} <-
|
||||
Enum.find(@mastodon_notification_types, fn {_k, v} -> type in List.wrap(v) end) do
|
||||
k
|
||||
end
|
||||
end
|
||||
|
||||
def all_by_actor_and_id(actor, status_ids \\ [])
|
||||
def all_by_actor_and_id(_actor, []), do: []
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ defmodule Pleroma.Application do
|
|||
Pleroma.HTML.compile_scrubbers()
|
||||
Config.DeprecationWarnings.warn()
|
||||
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
|
||||
Pleroma.Repo.check_migrations_applied!()
|
||||
Pleroma.ApplicationRequirements.verify!()
|
||||
setup_instrumenters()
|
||||
load_custom_modules()
|
||||
|
||||
|
|
@ -148,7 +148,8 @@ defmodule Pleroma.Application do
|
|||
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
|
||||
build_cachex("web_resp", limit: 2500),
|
||||
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
|
||||
build_cachex("failed_proxy_url", limit: 2500)
|
||||
build_cachex("failed_proxy_url", limit: 2500),
|
||||
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000)
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
107
lib/pleroma/application_requirements.ex
Normal file
107
lib/pleroma/application_requirements.ex
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ApplicationRequirements do
|
||||
@moduledoc """
|
||||
The module represents the collection of validations to runs before start server.
|
||||
"""
|
||||
|
||||
defmodule VerifyError, do: defexception([:message])
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
|
||||
@spec verify!() :: :ok | VerifyError.t()
|
||||
def verify! do
|
||||
:ok
|
||||
|> check_migrations_applied!()
|
||||
|> check_rum!()
|
||||
|> handle_result()
|
||||
end
|
||||
|
||||
defp handle_result(:ok), do: :ok
|
||||
defp handle_result({:error, message}), do: raise(VerifyError, message: message)
|
||||
|
||||
# Checks for pending migrations.
|
||||
#
|
||||
def check_migrations_applied!(:ok) do
|
||||
unless Pleroma.Config.get(
|
||||
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
|
||||
false
|
||||
) do
|
||||
{_, res, _} =
|
||||
Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
|
||||
down_migrations =
|
||||
Ecto.Migrator.migrations(repo)
|
||||
|> Enum.reject(fn
|
||||
{:up, _, _} -> true
|
||||
{:down, _, _} -> false
|
||||
end)
|
||||
|
||||
if length(down_migrations) > 0 do
|
||||
down_migrations_text =
|
||||
Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
|
||||
|
||||
Logger.error(
|
||||
"The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
|
||||
)
|
||||
|
||||
{:error, "Unapplied Migrations detected"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
res
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def check_migrations_applied!(result), do: result
|
||||
|
||||
# Checks for settings of RUM indexes.
|
||||
#
|
||||
defp check_rum!(:ok) do
|
||||
{_, res, _} =
|
||||
Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
|
||||
migrate =
|
||||
from(o in "columns",
|
||||
where: o.table_name == "objects",
|
||||
where: o.column_name == "fts_content"
|
||||
)
|
||||
|> repo.exists?(prefix: "information_schema")
|
||||
|
||||
setting = Pleroma.Config.get([:database, :rum_enabled], false)
|
||||
|
||||
do_check_rum!(setting, migrate)
|
||||
end)
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
defp check_rum!(result), do: result
|
||||
|
||||
defp do_check_rum!(setting, migrate) do
|
||||
case {setting, migrate} do
|
||||
{true, false} ->
|
||||
Logger.error(
|
||||
"Use `RUM` index is enabled, but were not applied migrations for it.\nIf you want to start Pleroma anyway, set\nconfig :pleroma, :database, rum_enabled: false\nOtherwise apply the following migrations:\n`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`"
|
||||
)
|
||||
|
||||
{:error, "Unapplied RUM Migrations detected"}
|
||||
|
||||
{false, true} ->
|
||||
Logger.error(
|
||||
"Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\nIf you want to use `RUM`, set\nconfig :pleroma, :database, rum_enabled: true\nOtherwise roll `RUM` migrations back.\n`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`"
|
||||
)
|
||||
|
||||
{:error, "RUM Migrations detected"}
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -92,10 +92,10 @@ defmodule Pleroma.BBS.Handler do
|
|||
|
||||
params =
|
||||
%{}
|
||||
|> Map.put("type", ["Create"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put(:type, ["Create"])
|
||||
|> Map.put(:blocking_user, user)
|
||||
|> Map.put(:muting_user, user)
|
||||
|> Map.put(:user, user)
|
||||
|
||||
activities =
|
||||
[user.ap_id | Pleroma.User.following(user)]
|
||||
|
|
|
|||
72
lib/pleroma/chat.ex
Normal file
72
lib/pleroma/chat.ex
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Chat do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@moduledoc """
|
||||
Chat keeps a reference to ChatMessage conversations between a user and an recipient. The recipient can be a user (for now) or a group (not implemented yet).
|
||||
|
||||
It is a helper only, to make it easy to display a list of chats with other people, ordered by last bump. The actual messages are retrieved by querying the recipients of the ChatMessages.
|
||||
"""
|
||||
|
||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||
|
||||
schema "chats" do
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:recipient, :string)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(struct, params) do
|
||||
struct
|
||||
|> cast(params, [:user_id, :recipient])
|
||||
|> validate_change(:recipient, fn
|
||||
:recipient, recipient ->
|
||||
case User.get_cached_by_ap_id(recipient) do
|
||||
nil -> [recipient: "must be an existing user"]
|
||||
_ -> []
|
||||
end
|
||||
end)
|
||||
|> validate_required([:user_id, :recipient])
|
||||
|> unique_constraint(:user_id, name: :chats_user_id_recipient_index)
|
||||
end
|
||||
|
||||
def get_by_id(id) do
|
||||
__MODULE__
|
||||
|> Repo.get(id)
|
||||
end
|
||||
|
||||
def get(user_id, recipient) do
|
||||
__MODULE__
|
||||
|> Repo.get_by(user_id: user_id, recipient: recipient)
|
||||
end
|
||||
|
||||
def get_or_create(user_id, recipient) do
|
||||
%__MODULE__{}
|
||||
|> changeset(%{user_id: user_id, recipient: recipient})
|
||||
|> Repo.insert(
|
||||
# Need to set something, otherwise we get nothing back at all
|
||||
on_conflict: [set: [recipient: recipient]],
|
||||
returning: true,
|
||||
conflict_target: [:user_id, :recipient]
|
||||
)
|
||||
end
|
||||
|
||||
def bump_or_create(user_id, recipient) do
|
||||
%__MODULE__{}
|
||||
|> changeset(%{user_id: user_id, recipient: recipient})
|
||||
|> Repo.insert(
|
||||
on_conflict: [set: [updated_at: NaiveDateTime.utc_now()]],
|
||||
returning: true,
|
||||
conflict_target: [:user_id, :recipient]
|
||||
)
|
||||
end
|
||||
end
|
||||
117
lib/pleroma/chat/message_reference.ex
Normal file
117
lib/pleroma/chat/message_reference.ex
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Chat.MessageReference do
|
||||
@moduledoc """
|
||||
A reference that builds a relation between an AP chat message that a user can see and whether it has been seen
|
||||
by them, or should be displayed to them. Used to build the chat view that is presented to the user.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Chat
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
@primary_key {:id, FlakeId.Ecto.Type, autogenerate: true}
|
||||
|
||||
schema "chat_message_references" do
|
||||
belongs_to(:object, Object)
|
||||
belongs_to(:chat, Chat, type: FlakeId.Ecto.CompatType)
|
||||
|
||||
field(:unread, :boolean, default: true)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(struct, params) do
|
||||
struct
|
||||
|> cast(params, [:object_id, :chat_id, :unread])
|
||||
|> validate_required([:object_id, :chat_id, :unread])
|
||||
end
|
||||
|
||||
def get_by_id(id) do
|
||||
__MODULE__
|
||||
|> Repo.get(id)
|
||||
|> Repo.preload(:object)
|
||||
end
|
||||
|
||||
def delete(cm_ref) do
|
||||
cm_ref
|
||||
|> Repo.delete()
|
||||
end
|
||||
|
||||
def delete_for_object(%{id: object_id}) do
|
||||
from(cr in __MODULE__,
|
||||
where: cr.object_id == ^object_id
|
||||
)
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
|
||||
def for_chat_and_object(%{id: chat_id}, %{id: object_id}) do
|
||||
__MODULE__
|
||||
|> Repo.get_by(chat_id: chat_id, object_id: object_id)
|
||||
|> Repo.preload(:object)
|
||||
end
|
||||
|
||||
def for_chat_query(chat) do
|
||||
from(cr in __MODULE__,
|
||||
where: cr.chat_id == ^chat.id,
|
||||
order_by: [desc: :id],
|
||||
preload: [:object]
|
||||
)
|
||||
end
|
||||
|
||||
def last_message_for_chat(chat) do
|
||||
chat
|
||||
|> for_chat_query()
|
||||
|> limit(1)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def create(chat, object, unread) do
|
||||
params = %{
|
||||
chat_id: chat.id,
|
||||
object_id: object.id,
|
||||
unread: unread
|
||||
}
|
||||
|
||||
%__MODULE__{}
|
||||
|> changeset(params)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def unread_count_for_chat(chat) do
|
||||
chat
|
||||
|> for_chat_query()
|
||||
|> where([cmr], cmr.unread == true)
|
||||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
def mark_as_read(cm_ref) do
|
||||
cm_ref
|
||||
|> changeset(%{unread: false})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def set_all_seen_for_chat(chat, last_read_id \\ nil) do
|
||||
query =
|
||||
chat
|
||||
|> for_chat_query()
|
||||
|> exclude(:order_by)
|
||||
|> exclude(:preload)
|
||||
|> where([cmr], cmr.unread == true)
|
||||
|
||||
if last_read_id do
|
||||
query
|
||||
|> where([cmr], cmr.id <= ^last_read_id)
|
||||
else
|
||||
query
|
||||
end
|
||||
|> Repo.update_all(set: [unread: false])
|
||||
end
|
||||
end
|
||||
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.ConfigDB do
|
|||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
import Ecto.Query, only: [select: 3]
|
||||
import Pleroma.Web.Gettext
|
||||
|
||||
alias __MODULE__
|
||||
|
|
@ -14,16 +14,6 @@ defmodule Pleroma.ConfigDB do
|
|||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
@full_key_update [
|
||||
{:pleroma, :ecto_repos},
|
||||
{:quack, :meta},
|
||||
{:mime, :types},
|
||||
{:cors_plug, [:max_age, :methods, :expose, :headers]},
|
||||
{:auto_linker, :opts},
|
||||
{:swarm, :node_blacklist},
|
||||
{:logger, :backends}
|
||||
]
|
||||
|
||||
@full_subkey_update [
|
||||
{:pleroma, :assets, :mascots},
|
||||
{:pleroma, :emoji, :groups},
|
||||
|
|
@ -32,14 +22,10 @@ defmodule Pleroma.ConfigDB do
|
|||
{:pleroma, :mrf_keyword, :replace}
|
||||
]
|
||||
|
||||
@regex ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
|
||||
|
||||
@delimiters ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
|
||||
|
||||
schema "config" do
|
||||
field(:key, :string)
|
||||
field(:group, :string)
|
||||
field(:value, :binary)
|
||||
field(:key, Pleroma.EctoType.Config.Atom)
|
||||
field(:group, Pleroma.EctoType.Config.Atom)
|
||||
field(:value, Pleroma.EctoType.Config.BinaryValue)
|
||||
field(:db, {:array, :string}, virtual: true, default: [])
|
||||
|
||||
timestamps()
|
||||
|
|
@ -51,10 +37,6 @@ defmodule Pleroma.ConfigDB do
|
|||
|> select([c], {c.group, c.key, c.value})
|
||||
|> Repo.all()
|
||||
|> Enum.reduce([], fn {group, key, value}, acc ->
|
||||
group = ConfigDB.from_string(group)
|
||||
key = ConfigDB.from_string(key)
|
||||
value = from_binary(value)
|
||||
|
||||
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
|
||||
end)
|
||||
end
|
||||
|
|
@ -64,50 +46,41 @@ defmodule Pleroma.ConfigDB do
|
|||
|
||||
@spec changeset(ConfigDB.t(), map()) :: Changeset.t()
|
||||
def changeset(config, params \\ %{}) do
|
||||
params = Map.put(params, :value, transform(params[:value]))
|
||||
|
||||
config
|
||||
|> cast(params, [:key, :group, :value])
|
||||
|> validate_required([:key, :group, :value])
|
||||
|> unique_constraint(:key, name: :config_group_key_index)
|
||||
end
|
||||
|
||||
@spec create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def create(params) do
|
||||
defp create(params) do
|
||||
%ConfigDB{}
|
||||
|> changeset(params)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@spec update(ConfigDB.t(), map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def update(%ConfigDB{} = config, %{value: value}) do
|
||||
defp update(%ConfigDB{} = config, %{value: value}) do
|
||||
config
|
||||
|> changeset(%{value: value})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@spec get_db_keys(ConfigDB.t()) :: [String.t()]
|
||||
def get_db_keys(%ConfigDB{} = config) do
|
||||
config.value
|
||||
|> ConfigDB.from_binary()
|
||||
|> get_db_keys(config.key)
|
||||
end
|
||||
|
||||
@spec get_db_keys(keyword(), any()) :: [String.t()]
|
||||
def get_db_keys(value, key) do
|
||||
if Keyword.keyword?(value) do
|
||||
value |> Keyword.keys() |> Enum.map(&convert(&1))
|
||||
else
|
||||
[convert(key)]
|
||||
end
|
||||
keys =
|
||||
if Keyword.keyword?(value) do
|
||||
Keyword.keys(value)
|
||||
else
|
||||
[key]
|
||||
end
|
||||
|
||||
Enum.map(keys, &to_json_types(&1))
|
||||
end
|
||||
|
||||
@spec merge_group(atom(), atom(), keyword(), keyword()) :: keyword()
|
||||
def merge_group(group, key, old_value, new_value) do
|
||||
new_keys = to_map_set(new_value)
|
||||
new_keys = to_mapset(new_value)
|
||||
|
||||
intersect_keys =
|
||||
old_value |> to_map_set() |> MapSet.intersection(new_keys) |> MapSet.to_list()
|
||||
intersect_keys = old_value |> to_mapset() |> MapSet.intersection(new_keys) |> MapSet.to_list()
|
||||
|
||||
merged_value = ConfigDB.merge(old_value, new_value)
|
||||
|
||||
|
|
@ -120,12 +93,10 @@ defmodule Pleroma.ConfigDB do
|
|||
[]
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.reduce(merged_value, fn subkey, acc ->
|
||||
Keyword.put(acc, subkey, new_value[subkey])
|
||||
end)
|
||||
|> Enum.reduce(merged_value, &Keyword.put(&2, &1, new_value[&1]))
|
||||
end
|
||||
|
||||
defp to_map_set(keyword) do
|
||||
defp to_mapset(keyword) do
|
||||
keyword
|
||||
|> Keyword.keys()
|
||||
|> MapSet.new()
|
||||
|
|
@ -159,57 +130,55 @@ defmodule Pleroma.ConfigDB do
|
|||
|
||||
@spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def update_or_create(params) do
|
||||
params = Map.put(params, :value, to_elixir_types(params[:value]))
|
||||
search_opts = Map.take(params, [:group, :key])
|
||||
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
|
||||
{:partial_update, true, config} <-
|
||||
{:partial_update, can_be_partially_updated?(config), config},
|
||||
old_value <- from_binary(config.value),
|
||||
transformed_value <- do_transform(params[:value]),
|
||||
{:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config},
|
||||
new_value <-
|
||||
merge_group(
|
||||
ConfigDB.from_string(config.group),
|
||||
ConfigDB.from_string(config.key),
|
||||
old_value,
|
||||
transformed_value
|
||||
) do
|
||||
ConfigDB.update(config, %{value: new_value})
|
||||
{_, true, config} <- {:partial_update, can_be_partially_updated?(config), config},
|
||||
{_, true, config} <-
|
||||
{:can_be_merged, is_list(params[:value]) and is_list(config.value), config} do
|
||||
new_value = merge_group(config.group, config.key, config.value, params[:value])
|
||||
update(config, %{value: new_value})
|
||||
else
|
||||
{reason, false, config} when reason in [:partial_update, :can_be_merged] ->
|
||||
ConfigDB.update(config, params)
|
||||
update(config, params)
|
||||
|
||||
nil ->
|
||||
ConfigDB.create(params)
|
||||
create(params)
|
||||
end
|
||||
end
|
||||
|
||||
defp can_be_partially_updated?(%ConfigDB{} = config), do: not only_full_update?(config)
|
||||
|
||||
defp only_full_update?(%ConfigDB{} = config) do
|
||||
config_group = ConfigDB.from_string(config.group)
|
||||
config_key = ConfigDB.from_string(config.key)
|
||||
defp only_full_update?(%ConfigDB{group: group, key: key}) do
|
||||
full_key_update = [
|
||||
{:pleroma, :ecto_repos},
|
||||
{:quack, :meta},
|
||||
{:mime, :types},
|
||||
{:cors_plug, [:max_age, :methods, :expose, :headers]},
|
||||
{:auto_linker, :opts},
|
||||
{:swarm, :node_blacklist},
|
||||
{:logger, :backends}
|
||||
]
|
||||
|
||||
Enum.any?(@full_key_update, fn
|
||||
{group, key} when is_list(key) ->
|
||||
config_group == group and config_key in key
|
||||
|
||||
{group, key} ->
|
||||
config_group == group and config_key == key
|
||||
Enum.any?(full_key_update, fn
|
||||
{s_group, s_key} ->
|
||||
group == s_group and ((is_list(s_key) and key in s_key) or key == s_key)
|
||||
end)
|
||||
end
|
||||
|
||||
@spec delete(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
@spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def delete(%ConfigDB{} = config), do: Repo.delete(config)
|
||||
|
||||
def delete(params) do
|
||||
search_opts = Map.delete(params, :subkeys)
|
||||
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
|
||||
{config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]},
|
||||
old_value <- from_binary(config.value),
|
||||
keys <- Enum.map(sub_keys, &do_transform_string(&1)),
|
||||
{:partial_remove, config, new_value} when new_value != [] <-
|
||||
{:partial_remove, config, Keyword.drop(old_value, keys)} do
|
||||
ConfigDB.update(config, %{value: new_value})
|
||||
keys <- Enum.map(sub_keys, &string_to_elixir_types(&1)),
|
||||
{_, config, new_value} when new_value != [] <-
|
||||
{:partial_remove, config, Keyword.drop(config.value, keys)} do
|
||||
update(config, %{value: new_value})
|
||||
else
|
||||
{:partial_remove, config, []} ->
|
||||
Repo.delete(config)
|
||||
|
|
@ -225,37 +194,32 @@ defmodule Pleroma.ConfigDB do
|
|||
end
|
||||
end
|
||||
|
||||
@spec from_binary(binary()) :: term()
|
||||
def from_binary(binary), do: :erlang.binary_to_term(binary)
|
||||
|
||||
@spec from_binary_with_convert(binary()) :: any()
|
||||
def from_binary_with_convert(binary) do
|
||||
binary
|
||||
|> from_binary()
|
||||
|> do_convert()
|
||||
@spec to_json_types(term()) :: map() | list() | boolean() | String.t()
|
||||
def to_json_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_json_types/1)
|
||||
end
|
||||
|
||||
@spec from_string(String.t()) :: atom() | no_return()
|
||||
def from_string(string), do: do_transform_string(string)
|
||||
def to_json_types(%Regex{} = entity), do: inspect(entity)
|
||||
|
||||
@spec convert(any()) :: any()
|
||||
def convert(entity), do: do_convert(entity)
|
||||
|
||||
defp do_convert(entity) when is_list(entity) do
|
||||
for v <- entity, into: [], do: do_convert(v)
|
||||
def to_json_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_json_types(k), to_json_types(v)} end)
|
||||
end
|
||||
|
||||
defp do_convert(%Regex{} = entity), do: inspect(entity)
|
||||
def to_json_types({:args, args}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn
|
||||
arg when is_tuple(arg) -> inspect(arg)
|
||||
arg -> to_json_types(arg)
|
||||
end)
|
||||
|
||||
defp do_convert(entity) when is_map(entity) do
|
||||
for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)}
|
||||
%{"tuple" => [":args", arguments]}
|
||||
end
|
||||
|
||||
defp do_convert({:proxy_url, {type, :localhost, port}}) do
|
||||
%{"tuple" => [":proxy_url", %{"tuple" => [do_convert(type), "localhost", port]}]}
|
||||
def to_json_types({:proxy_url, {type, :localhost, port}}) do
|
||||
%{"tuple" => [":proxy_url", %{"tuple" => [to_json_types(type), "localhost", port]}]}
|
||||
end
|
||||
|
||||
defp do_convert({:proxy_url, {type, host, port}}) when is_tuple(host) do
|
||||
def to_json_types({:proxy_url, {type, host, port}}) when is_tuple(host) do
|
||||
ip =
|
||||
host
|
||||
|> :inet_parse.ntoa()
|
||||
|
|
@ -264,66 +228,64 @@ defmodule Pleroma.ConfigDB do
|
|||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [do_convert(type), ip, port]}
|
||||
%{"tuple" => [to_json_types(type), ip, port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
defp do_convert({:proxy_url, {type, host, port}}) do
|
||||
def to_json_types({:proxy_url, {type, host, port}}) do
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [do_convert(type), to_string(host), port]}
|
||||
%{"tuple" => [to_json_types(type), to_string(host), port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
|
||||
def to_json_types({:partial_chain, entity}),
|
||||
do: %{"tuple" => [":partial_chain", inspect(entity)]}
|
||||
|
||||
defp do_convert(entity) when is_tuple(entity) do
|
||||
def to_json_types(entity) when is_tuple(entity) do
|
||||
value =
|
||||
entity
|
||||
|> Tuple.to_list()
|
||||
|> do_convert()
|
||||
|> to_json_types()
|
||||
|
||||
%{"tuple" => value}
|
||||
end
|
||||
|
||||
defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
|
||||
def to_json_types(entity) when is_binary(entity), do: entity
|
||||
|
||||
def to_json_types(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
|
||||
entity
|
||||
end
|
||||
|
||||
defp do_convert(entity)
|
||||
when is_atom(entity) and entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
|
||||
def to_json_types(entity) when entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
|
||||
":#{entity}"
|
||||
end
|
||||
|
||||
defp do_convert(entity) when is_atom(entity), do: inspect(entity)
|
||||
def to_json_types(entity) when is_atom(entity), do: inspect(entity)
|
||||
|
||||
defp do_convert(entity) when is_binary(entity), do: entity
|
||||
@spec to_elixir_types(boolean() | String.t() | map() | list()) :: term()
|
||||
def to_elixir_types(%{"tuple" => [":args", args]}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn arg ->
|
||||
if String.contains?(arg, ["{", "}"]) do
|
||||
{elem, []} = Code.eval_string(arg)
|
||||
elem
|
||||
else
|
||||
to_elixir_types(arg)
|
||||
end
|
||||
end)
|
||||
|
||||
@spec transform(any()) :: binary() | no_return()
|
||||
def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do
|
||||
entity
|
||||
|> do_transform()
|
||||
|> to_binary()
|
||||
{:args, arguments}
|
||||
end
|
||||
|
||||
def transform(entity), do: to_binary(entity)
|
||||
|
||||
@spec transform_with_out_binary(any()) :: any()
|
||||
def transform_with_out_binary(entity), do: do_transform(entity)
|
||||
|
||||
@spec to_binary(any()) :: binary()
|
||||
def to_binary(entity), do: :erlang.term_to_binary(entity)
|
||||
|
||||
defp do_transform(%Regex{} = entity), do: entity
|
||||
|
||||
defp do_transform(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
|
||||
{:proxy_url, {do_transform_string(type), parse_host(host), port}}
|
||||
def to_elixir_types(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
|
||||
{:proxy_url, {string_to_elixir_types(type), parse_host(host), port}}
|
||||
end
|
||||
|
||||
defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
|
||||
def to_elixir_types(%{"tuple" => [":partial_chain", entity]}) do
|
||||
{partial_chain, []} =
|
||||
entity
|
||||
|> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
|
||||
|
|
@ -332,25 +294,51 @@ defmodule Pleroma.ConfigDB do
|
|||
{:partial_chain, partial_chain}
|
||||
end
|
||||
|
||||
defp do_transform(%{"tuple" => entity}) do
|
||||
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
|
||||
def to_elixir_types(%{"tuple" => entity}) do
|
||||
Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1)))
|
||||
end
|
||||
|
||||
defp do_transform(entity) when is_map(entity) do
|
||||
for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)}
|
||||
def to_elixir_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_elixir_types(k), to_elixir_types(v)} end)
|
||||
end
|
||||
|
||||
defp do_transform(entity) when is_list(entity) do
|
||||
for v <- entity, into: [], do: do_transform(v)
|
||||
def to_elixir_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_elixir_types/1)
|
||||
end
|
||||
|
||||
defp do_transform(entity) when is_binary(entity) do
|
||||
def to_elixir_types(entity) when is_binary(entity) do
|
||||
entity
|
||||
|> String.trim()
|
||||
|> do_transform_string()
|
||||
|> string_to_elixir_types()
|
||||
end
|
||||
|
||||
defp do_transform(entity), do: entity
|
||||
def to_elixir_types(entity), do: entity
|
||||
|
||||
@spec string_to_elixir_types(String.t()) ::
|
||||
atom() | Regex.t() | module() | String.t() | no_return()
|
||||
def string_to_elixir_types("~r" <> _pattern = regex) do
|
||||
pattern =
|
||||
~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
|
||||
|
||||
delimiters = ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
|
||||
|
||||
with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
|
||||
Regex.named_captures(pattern, regex),
|
||||
{:ok, {leading, closing}} <- find_valid_delimiter(delimiters, pattern, regex_delimiter),
|
||||
{result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def string_to_elixir_types(":" <> atom), do: String.to_atom(atom)
|
||||
|
||||
def string_to_elixir_types(value) do
|
||||
if module_name?(value) do
|
||||
String.to_existing_atom("Elixir." <> value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_host("localhost"), do: :localhost
|
||||
|
||||
|
|
@ -387,27 +375,8 @@ defmodule Pleroma.ConfigDB do
|
|||
end
|
||||
end
|
||||
|
||||
defp do_transform_string("~r" <> _pattern = regex) do
|
||||
with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
|
||||
Regex.named_captures(@regex, regex),
|
||||
{:ok, {leading, closing}} <- find_valid_delimiter(@delimiters, pattern, regex_delimiter),
|
||||
{result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
defp do_transform_string(":" <> atom), do: String.to_atom(atom)
|
||||
|
||||
defp do_transform_string(value) do
|
||||
if is_module_name?(value) do
|
||||
String.to_existing_atom("Elixir." <> value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
@spec is_module_name?(String.t()) :: boolean()
|
||||
def is_module_name?(string) do
|
||||
@spec module_name?(String.t()) :: boolean()
|
||||
def module_name?(string) do
|
||||
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
|
||||
string in ["Oban", "Ueberauth", "ExSyslogger"]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,25 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.DeprecationWarnings do
|
||||
alias Pleroma.Config
|
||||
|
||||
require Logger
|
||||
alias Pleroma.Config
|
||||
|
||||
@type config_namespace() :: [atom()]
|
||||
@type config_map() :: {config_namespace(), config_namespace(), String.t()}
|
||||
|
||||
@mrf_config_map [
|
||||
{[:instance, :rewrite_policy], [:mrf, :policies],
|
||||
"\n* `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies`"},
|
||||
{[:instance, :mrf_transparency], [:mrf, :transparency],
|
||||
"\n* `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency`"},
|
||||
{[:instance, :mrf_transparency_exclusions], [:mrf, :transparency_exclusions],
|
||||
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
|
||||
]
|
||||
|
||||
def check_hellthread_threshold do
|
||||
if Pleroma.Config.get([:mrf_hellthread, :threshold]) do
|
||||
if Config.get([:mrf_hellthread, :threshold]) do
|
||||
Logger.warn("""
|
||||
!!!DEPRECATION WARNING!!!
|
||||
You are using the old configuration mechanism for the hellthread filter. Please check config.md.
|
||||
|
|
@ -14,7 +29,59 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
|||
end
|
||||
end
|
||||
|
||||
def mrf_user_allowlist do
|
||||
config = Config.get(:mrf_user_allowlist)
|
||||
|
||||
if config && Enum.any?(config, fn {k, _} -> is_atom(k) end) do
|
||||
rewritten =
|
||||
Enum.reduce(Config.get(:mrf_user_allowlist), Map.new(), fn {k, v}, acc ->
|
||||
Map.put(acc, to_string(k), v)
|
||||
end)
|
||||
|
||||
Config.put(:mrf_user_allowlist, rewritten)
|
||||
|
||||
Logger.error("""
|
||||
!!!DEPRECATION WARNING!!!
|
||||
As of Pleroma 2.0.7, the `mrf_user_allowlist` setting changed of format.
|
||||
Pleroma 2.1 will remove support for the old format. Please change your configuration to match this:
|
||||
|
||||
config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)}
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
def warn do
|
||||
check_hellthread_threshold()
|
||||
mrf_user_allowlist()
|
||||
check_old_mrf_config()
|
||||
end
|
||||
|
||||
def check_old_mrf_config do
|
||||
warning_preface = """
|
||||
!!!DEPRECATION WARNING!!!
|
||||
Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later:
|
||||
"""
|
||||
|
||||
move_namespace_and_warn(@mrf_config_map, warning_preface)
|
||||
end
|
||||
|
||||
@spec move_namespace_and_warn([config_map()], String.t()) :: :ok
|
||||
def move_namespace_and_warn(config_map, warning_preface) do
|
||||
warning =
|
||||
Enum.reduce(config_map, "", fn
|
||||
{old, new, err_msg}, acc ->
|
||||
old_config = Config.get(old)
|
||||
|
||||
if old_config do
|
||||
Config.put(new, old_config)
|
||||
acc <> err_msg
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
if warning != "" do
|
||||
Logger.warn(warning_preface <> warning)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -28,10 +28,6 @@ defmodule Pleroma.Config.TransferTask do
|
|||
{:pleroma, Pleroma.Captcha, [:seconds_valid]},
|
||||
{:pleroma, Pleroma.Upload, [:proxy_remote]},
|
||||
{:pleroma, :instance, [:upload_limit]},
|
||||
{:pleroma, :email_notifications, [:digest]},
|
||||
{:pleroma, :oauth2, [:clean_expired_tokens]},
|
||||
{:pleroma, Pleroma.ActivityExpiration, [:enabled]},
|
||||
{:pleroma, Pleroma.ScheduledActivity, [:enabled]},
|
||||
{:pleroma, :gopher, [:enabled]}
|
||||
]
|
||||
|
||||
|
|
@ -48,7 +44,7 @@ defmodule Pleroma.Config.TransferTask do
|
|||
|
||||
{logger, other} =
|
||||
(Repo.all(ConfigDB) ++ deleted_settings)
|
||||
|> Enum.map(&transform_and_merge/1)
|
||||
|> Enum.map(&merge_with_default/1)
|
||||
|> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end)
|
||||
|
||||
logger
|
||||
|
|
@ -92,11 +88,7 @@ defmodule Pleroma.Config.TransferTask do
|
|||
end
|
||||
end
|
||||
|
||||
defp transform_and_merge(%{group: group, key: key, value: value} = setting) do
|
||||
group = ConfigDB.from_string(group)
|
||||
key = ConfigDB.from_string(key)
|
||||
value = ConfigDB.from_binary(value)
|
||||
|
||||
defp merge_with_default(%{group: group, key: key, value: value} = setting) do
|
||||
default = Config.Holder.default_config(group, key)
|
||||
|
||||
merged =
|
||||
|
|
|
|||
|
|
@ -24,6 +24,6 @@ defmodule Pleroma.Constants do
|
|||
|
||||
const(static_only_files,
|
||||
do:
|
||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
|
||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -162,10 +162,13 @@ defmodule Pleroma.Conversation.Participation do
|
|||
for_user(user, params)
|
||||
|> Enum.map(fn participation ->
|
||||
activity_id =
|
||||
ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
|
||||
"user" => user,
|
||||
"blocking_user" => user
|
||||
})
|
||||
ActivityPub.fetch_latest_direct_activity_id_for_context(
|
||||
participation.conversation.ap_id,
|
||||
%{
|
||||
user: user,
|
||||
blocking_user: user
|
||||
}
|
||||
)
|
||||
|
||||
%{
|
||||
participation
|
||||
|
|
|
|||
|
|
@ -10,32 +10,70 @@ defmodule Pleroma.CounterCache do
|
|||
import Ecto.Query
|
||||
|
||||
schema "counter_cache" do
|
||||
field(:name, :string)
|
||||
field(:count, :integer)
|
||||
field(:instance, :string)
|
||||
field(:public, :integer)
|
||||
field(:unlisted, :integer)
|
||||
field(:private, :integer)
|
||||
field(:direct, :integer)
|
||||
end
|
||||
|
||||
def changeset(struct, params) do
|
||||
struct
|
||||
|> cast(params, [:name, :count])
|
||||
|> validate_required([:name])
|
||||
|> unique_constraint(:name)
|
||||
|> cast(params, [:instance, :public, :unlisted, :private, :direct])
|
||||
|> validate_required([:instance])
|
||||
|> unique_constraint(:instance)
|
||||
end
|
||||
|
||||
def get_as_map(names) when is_list(names) do
|
||||
def get_by_instance(instance) do
|
||||
CounterCache
|
||||
|> where([cc], cc.name in ^names)
|
||||
|> Repo.all()
|
||||
|> Enum.group_by(& &1.name, & &1.count)
|
||||
|> Map.new(fn {k, v} -> {k, hd(v)} end)
|
||||
|> select([c], %{
|
||||
"public" => c.public,
|
||||
"unlisted" => c.unlisted,
|
||||
"private" => c.private,
|
||||
"direct" => c.direct
|
||||
})
|
||||
|> where([c], c.instance == ^instance)
|
||||
|> Repo.one()
|
||||
|> case do
|
||||
nil -> %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0}
|
||||
val -> val
|
||||
end
|
||||
end
|
||||
|
||||
def set(name, count) do
|
||||
def get_sum do
|
||||
CounterCache
|
||||
|> select([c], %{
|
||||
"public" => type(sum(c.public), :integer),
|
||||
"unlisted" => type(sum(c.unlisted), :integer),
|
||||
"private" => type(sum(c.private), :integer),
|
||||
"direct" => type(sum(c.direct), :integer)
|
||||
})
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def set(instance, values) do
|
||||
params =
|
||||
Enum.reduce(
|
||||
["public", "private", "unlisted", "direct"],
|
||||
%{"instance" => instance},
|
||||
fn param, acc ->
|
||||
Map.put_new(acc, param, Map.get(values, param, 0))
|
||||
end
|
||||
)
|
||||
|
||||
%CounterCache{}
|
||||
|> changeset(%{"name" => name, "count" => count})
|
||||
|> changeset(params)
|
||||
|> Repo.insert(
|
||||
on_conflict: [set: [count: count]],
|
||||
on_conflict: [
|
||||
set: [
|
||||
public: params["public"],
|
||||
private: params["private"],
|
||||
unlisted: params["unlisted"],
|
||||
direct: params["direct"]
|
||||
]
|
||||
],
|
||||
returning: true,
|
||||
conflict_target: :name
|
||||
conflict_target: :instance
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime do
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime do
|
||||
@moduledoc """
|
||||
The AP standard defines the date fields in AP as xsd:DateTime. Elixir's
|
||||
DateTime can't parse this, but it can parse the related iso8601. This
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID do
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: :string
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients do
|
||||
use Ecto.Type
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID
|
||||
|
||||
def type, do: {:array, ObjectID}
|
||||
|
||||
def cast(object) when is_binary(object) do
|
||||
cast([object])
|
||||
end
|
||||
|
||||
def cast(data) when is_list(data) do
|
||||
data
|
||||
|> Enum.reduce_while({:ok, []}, fn element, {:ok, list} ->
|
||||
case ObjectID.cast(element) do
|
||||
{:ok, id} ->
|
||||
{:cont, {:ok, [id | list]}}
|
||||
|
||||
_ ->
|
||||
{:halt, :error}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def cast(_) do
|
||||
:error
|
||||
end
|
||||
|
||||
def dump(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def load(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.SafeText do
|
||||
use Ecto.Type
|
||||
|
||||
alias Pleroma.HTML
|
||||
|
||||
def type, do: :string
|
||||
|
||||
def cast(str) when is_binary(str) do
|
||||
{:ok, HTML.filter_tags(str)}
|
||||
end
|
||||
|
||||
def cast(_), do: :error
|
||||
|
||||
def dump(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def load(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Uri do
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Uri do
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: :string
|
||||
26
lib/pleroma/ecto_type/config/atom.ex
Normal file
26
lib/pleroma/ecto_type/config/atom.ex
Normal file
|
|
@ -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.EctoType.Config.Atom do
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: :atom
|
||||
|
||||
def cast(key) when is_atom(key) do
|
||||
{:ok, key}
|
||||
end
|
||||
|
||||
def cast(key) when is_binary(key) do
|
||||
{:ok, Pleroma.ConfigDB.string_to_elixir_types(key)}
|
||||
end
|
||||
|
||||
def cast(_), do: :error
|
||||
|
||||
def load(key) do
|
||||
{:ok, Pleroma.ConfigDB.string_to_elixir_types(key)}
|
||||
end
|
||||
|
||||
def dump(key) when is_atom(key), do: {:ok, inspect(key)}
|
||||
def dump(_), do: :error
|
||||
end
|
||||
27
lib/pleroma/ecto_type/config/binary_value.ex
Normal file
27
lib/pleroma/ecto_type/config/binary_value.ex
Normal 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.EctoType.Config.BinaryValue do
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: :term
|
||||
|
||||
def cast(value) when is_binary(value) do
|
||||
if String.valid?(value) do
|
||||
{:ok, value}
|
||||
else
|
||||
{:ok, :erlang.binary_to_term(value)}
|
||||
end
|
||||
end
|
||||
|
||||
def cast(value), do: {:ok, value}
|
||||
|
||||
def load(value) when is_binary(value) do
|
||||
{:ok, :erlang.binary_to_term(value)}
|
||||
end
|
||||
|
||||
def dump(value) do
|
||||
{:ok, :erlang.term_to_binary(value)}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
defmodule Pleroma.Emoji.Pack do
|
||||
@derive {Jason.Encoder, only: [:files, :pack]}
|
||||
@derive {Jason.Encoder, only: [:files, :pack, :files_count]}
|
||||
defstruct files: %{},
|
||||
files_count: 0,
|
||||
pack_file: nil,
|
||||
path: nil,
|
||||
pack: %{},
|
||||
|
|
@ -8,6 +9,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
|
||||
@type t() :: %__MODULE__{
|
||||
files: %{String.t() => Path.t()},
|
||||
files_count: non_neg_integer(),
|
||||
pack_file: Path.t(),
|
||||
path: Path.t(),
|
||||
pack: map(),
|
||||
|
|
@ -16,7 +18,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
|
||||
alias Pleroma.Emoji
|
||||
|
||||
@spec create(String.t()) :: :ok | {:error, File.posix()} | {:error, :empty_values}
|
||||
@spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
|
||||
def create(name) do
|
||||
with :ok <- validate_not_empty([name]),
|
||||
dir <- Path.join(emoji_path(), name),
|
||||
|
|
@ -26,10 +28,28 @@ defmodule Pleroma.Emoji.Pack do
|
|||
end
|
||||
end
|
||||
|
||||
@spec show(String.t()) :: {:ok, t()} | {:error, atom()}
|
||||
def show(name) do
|
||||
defp paginate(entities, 1, page_size), do: Enum.take(entities, page_size)
|
||||
|
||||
defp paginate(entities, page, page_size) do
|
||||
entities
|
||||
|> Enum.chunk_every(page_size)
|
||||
|> Enum.at(page - 1)
|
||||
end
|
||||
|
||||
@spec show(keyword()) :: {:ok, t()} | {:error, atom()}
|
||||
def show(opts) do
|
||||
name = opts[:name]
|
||||
|
||||
with :ok <- validate_not_empty([name]),
|
||||
{:ok, pack} <- load_pack(name) do
|
||||
shortcodes =
|
||||
pack.files
|
||||
|> Map.keys()
|
||||
|> Enum.sort()
|
||||
|> paginate(opts[:page], opts[:page_size])
|
||||
|
||||
pack = Map.put(pack, :files, Map.take(pack.files, shortcodes))
|
||||
|
||||
{:ok, validate_pack(pack)}
|
||||
end
|
||||
end
|
||||
|
|
@ -120,10 +140,10 @@ defmodule Pleroma.Emoji.Pack do
|
|||
end
|
||||
end
|
||||
|
||||
@spec list_local() :: {:ok, map()}
|
||||
def list_local do
|
||||
@spec list_local(keyword()) :: {:ok, map(), non_neg_integer()}
|
||||
def list_local(opts) do
|
||||
with {:ok, results} <- list_packs_dir() do
|
||||
packs =
|
||||
all_packs =
|
||||
results
|
||||
|> Enum.map(fn name ->
|
||||
case load_pack(name) do
|
||||
|
|
@ -132,9 +152,13 @@ defmodule Pleroma.Emoji.Pack do
|
|||
end
|
||||
end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
packs =
|
||||
all_packs
|
||||
|> paginate(opts[:page], opts[:page_size])
|
||||
|> Map.new(fn pack -> {pack.name, validate_pack(pack)} end)
|
||||
|
||||
{:ok, packs}
|
||||
{:ok, packs, length(all_packs)}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -146,7 +170,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
end
|
||||
end
|
||||
|
||||
@spec download(String.t(), String.t(), String.t()) :: :ok | {:error, atom()}
|
||||
@spec download(String.t(), String.t(), String.t()) :: {:ok, t()} | {:error, atom()}
|
||||
def download(name, url, as) do
|
||||
uri = url |> String.trim() |> URI.parse()
|
||||
|
||||
|
|
@ -197,7 +221,12 @@ defmodule Pleroma.Emoji.Pack do
|
|||
|> Map.put(:path, Path.dirname(pack_file))
|
||||
|> Map.put(:name, name)
|
||||
|
||||
{:ok, pack}
|
||||
files_count =
|
||||
pack.files
|
||||
|> Map.keys()
|
||||
|> length()
|
||||
|
||||
{:ok, Map.put(pack, :files_count, files_count)}
|
||||
else
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
|
@ -296,7 +325,9 @@ defmodule Pleroma.Emoji.Pack do
|
|||
# Otherwise, they'd have to download it from external-src
|
||||
pack.pack["share-files"] &&
|
||||
Enum.all?(pack.files, fn {_, file} ->
|
||||
File.exists?(Path.join(pack.path, file))
|
||||
pack.path
|
||||
|> Path.join(file)
|
||||
|> File.exists?()
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
@ -440,7 +471,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
# with the API so it should be sufficient
|
||||
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
|
||||
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
|
||||
{:ok, results}
|
||||
{:ok, Enum.sort(results)}
|
||||
else
|
||||
{:create_dir, {:error, e}} -> {:error, :create_dir, e}
|
||||
{:ls, {:error, e}} -> {:error, :ls, e}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ defmodule Pleroma.FollowingRelationship do
|
|||
|> join(:inner, [r], f in assoc(r, :follower))
|
||||
|> where([r], r.state == ^:follow_pending)
|
||||
|> where([r], r.following_id == ^id)
|
||||
|> where([r, f], f.deactivated != true)
|
||||
|> select([r, f], f)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
|
@ -141,6 +142,12 @@ defmodule Pleroma.FollowingRelationship do
|
|||
|> where([r], r.state == ^:follow_accept)
|
||||
end
|
||||
|
||||
def outgoing_pending_follow_requests_query(%User{} = follower) do
|
||||
__MODULE__
|
||||
|> where([r], r.follower_id == ^follower.id)
|
||||
|> where([r], r.state == ^:follow_pending)
|
||||
end
|
||||
|
||||
def following(%User{} = user) do
|
||||
following =
|
||||
following_query(user)
|
||||
|
|
|
|||
|
|
@ -17,14 +17,6 @@ defmodule Pleroma.Helpers.UriHelper do
|
|||
|> URI.to_string()
|
||||
end
|
||||
|
||||
def append_param_if_present(%{} = params, param_name, param_value) do
|
||||
if param_value do
|
||||
Map.put(params, param_name, param_value)
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_add_base("/" <> uri, base), do: Path.join([base, uri])
|
||||
def maybe_add_base(uri, _base), do: uri
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,22 +22,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|
|||
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
|
||||
end
|
||||
|
||||
defp add_scheme_opts(opts, %URI{scheme: "http"}), do: opts
|
||||
|
||||
defp add_scheme_opts(opts, %URI{scheme: "https", host: host}) do
|
||||
ssl_opts = [
|
||||
ssl_options: [
|
||||
# Workaround for remote server certificate chain issues
|
||||
partial_chain: &:hackney_connect.partial_chain/1,
|
||||
|
||||
# We don't support TLS v1.3 yet
|
||||
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
|
||||
server_name_indication: to_charlist(host)
|
||||
]
|
||||
]
|
||||
|
||||
Keyword.merge(opts, ssl_opts)
|
||||
end
|
||||
defp add_scheme_opts(opts, _), do: opts
|
||||
|
||||
def after_request(_), do: :ok
|
||||
end
|
||||
|
|
|
|||
22
lib/pleroma/http/ex_aws.ex
Normal file
22
lib/pleroma/http/ex_aws.ex
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.ExAws do
|
||||
@moduledoc false
|
||||
|
||||
@behaviour ExAws.Request.HttpClient
|
||||
|
||||
alias Pleroma.HTTP
|
||||
|
||||
@impl true
|
||||
def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do
|
||||
case HTTP.request(method, url, body, headers, http_opts) do
|
||||
{:ok, env} ->
|
||||
{:ok, %{status_code: env.status, headers: env.headers, body: env.body}}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, %{reason: reason}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.HTTP do
|
|||
require Logger
|
||||
|
||||
@type t :: __MODULE__
|
||||
@type method() :: :get | :post | :put | :delete | :head
|
||||
|
||||
@doc """
|
||||
Performs GET request.
|
||||
|
|
@ -28,6 +29,9 @@ defmodule Pleroma.HTTP do
|
|||
def get(nil, _, _), do: nil
|
||||
def get(url, headers, options), do: request(:get, url, "", headers, options)
|
||||
|
||||
@spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
|
||||
def head(url, headers \\ [], options \\ []), do: request(:head, url, "", headers, options)
|
||||
|
||||
@doc """
|
||||
Performs POST request.
|
||||
|
||||
|
|
@ -42,7 +46,7 @@ defmodule Pleroma.HTTP do
|
|||
Builds and performs http request.
|
||||
|
||||
# Arguments:
|
||||
`method` - :get, :post, :put, :delete
|
||||
`method` - :get, :post, :put, :delete, :head
|
||||
`url` - full url
|
||||
`body` - request body
|
||||
`headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
|
||||
|
|
@ -52,7 +56,7 @@ defmodule Pleroma.HTTP do
|
|||
`{:ok, %Tesla.Env{}}` or `{:error, error}`
|
||||
|
||||
"""
|
||||
@spec request(atom(), Request.url(), String.t(), Request.headers(), keyword()) ::
|
||||
@spec request(method(), Request.url(), String.t(), Request.headers(), keyword()) ::
|
||||
{:ok, Env.t()} | {:error, any()}
|
||||
def request(method, url, body, headers, options) when is_binary(url) do
|
||||
uri = URI.parse(url)
|
||||
|
|
|
|||
25
lib/pleroma/http/tzdata.ex
Normal file
25
lib/pleroma/http/tzdata.ex
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.Tzdata do
|
||||
@moduledoc false
|
||||
|
||||
@behaviour Tzdata.HTTPClient
|
||||
|
||||
alias Pleroma.HTTP
|
||||
|
||||
@impl true
|
||||
def get(url, headers, options) do
|
||||
with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do
|
||||
{:ok, {env.status, env.headers, env.body}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def head(url, headers, options) do
|
||||
with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do
|
||||
{:ok, {env.status, env.headers}}
|
||||
end
|
||||
end
|
||||
end
|
||||
37
lib/pleroma/maintenance.ex
Normal file
37
lib/pleroma/maintenance.ex
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Maintenance do
|
||||
alias Pleroma.Repo
|
||||
require Logger
|
||||
|
||||
def vacuum(args) do
|
||||
case args do
|
||||
"analyze" ->
|
||||
Logger.info("Runnning VACUUM ANALYZE.")
|
||||
|
||||
Repo.query!(
|
||||
"vacuum analyze;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
"full" ->
|
||||
Logger.info("Runnning VACUUM FULL.")
|
||||
|
||||
Logger.warn(
|
||||
"Re-packing your entire database may take a while and will consume extra disk space during the process."
|
||||
)
|
||||
|
||||
Repo.query!(
|
||||
"vacuum full;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
_ ->
|
||||
Logger.error("Error: invalid vacuum argument.")
|
||||
end
|
||||
end
|
||||
end
|
||||
15
lib/pleroma/maps.ex
Normal file
15
lib/pleroma/maps.ex
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Maps do
|
||||
def put_if_present(map, key, value, value_function \\ &{:ok, &1}) when is_map(map) do
|
||||
with false <- is_nil(key),
|
||||
false <- is_nil(value),
|
||||
{:ok, new_value} <- value_function.(value) do
|
||||
Map.put(map, key, new_value)
|
||||
else
|
||||
_ -> map
|
||||
end
|
||||
end
|
||||
end
|
||||
85
lib/pleroma/migration_helper/notification_backfill.ex
Normal file
85
lib/pleroma/migration_helper/notification_backfill.ex
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
def fill_in_notification_types do
|
||||
query =
|
||||
from(n in Pleroma.Notification,
|
||||
where: is_nil(n.type),
|
||||
preload: :activity
|
||||
)
|
||||
|
||||
query
|
||||
|> Repo.chunk_stream(100)
|
||||
|> Enum.each(fn notification ->
|
||||
type =
|
||||
notification.activity
|
||||
|> type_from_activity()
|
||||
|
||||
notification
|
||||
|> Notification.changeset(%{type: type})
|
||||
|> Repo.update()
|
||||
end)
|
||||
end
|
||||
|
||||
# This is copied over from Notifications to keep this stable.
|
||||
defp type_from_activity(%{data: %{"type" => type}} = activity) do
|
||||
case type do
|
||||
"Follow" ->
|
||||
accepted_function = fn activity ->
|
||||
with %User{} = follower <- User.get_by_ap_id(activity.data["actor"]),
|
||||
%User{} = followed <- User.get_by_ap_id(activity.data["object"]) do
|
||||
Pleroma.FollowingRelationship.following?(follower, followed)
|
||||
end
|
||||
end
|
||||
|
||||
if accepted_function.(activity) do
|
||||
"follow"
|
||||
else
|
||||
"follow_request"
|
||||
end
|
||||
|
||||
"Announce" ->
|
||||
"reblog"
|
||||
|
||||
"Like" ->
|
||||
"favourite"
|
||||
|
||||
"Move" ->
|
||||
"move"
|
||||
|
||||
"EmojiReact" ->
|
||||
"pleroma:emoji_reaction"
|
||||
|
||||
# Compatibility with old reactions
|
||||
"EmojiReaction" ->
|
||||
"pleroma:emoji_reaction"
|
||||
|
||||
"Create" ->
|
||||
activity
|
||||
|> type_from_activity_object()
|
||||
|
||||
t ->
|
||||
raise "No notification type for activity type #{t}"
|
||||
end
|
||||
end
|
||||
|
||||
defp type_from_activity_object(%{data: %{"type" => "Create", "object" => %{}}}), do: "mention"
|
||||
|
||||
defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
|
||||
object = Object.get_by_ap_id(activity.data["object"])
|
||||
|
||||
case object && object.data["type"] do
|
||||
"ChatMessage" -> "pleroma:chat_mention"
|
||||
_ -> "mention"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -30,12 +30,29 @@ defmodule Pleroma.Notification do
|
|||
|
||||
schema "notifications" do
|
||||
field(:seen, :boolean, default: false)
|
||||
# This is an enum type in the database. If you add a new notification type,
|
||||
# remember to add a migration to add it to the `notifications_type` enum
|
||||
# as well.
|
||||
field(:type, :string)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def update_notification_type(user, activity) do
|
||||
with %__MODULE__{} = notification <-
|
||||
Repo.get_by(__MODULE__, user_id: user.id, activity_id: activity.id) do
|
||||
type =
|
||||
activity
|
||||
|> type_from_activity()
|
||||
|
||||
notification
|
||||
|> changeset(%{type: type})
|
||||
|> Repo.update()
|
||||
end
|
||||
end
|
||||
|
||||
@spec unread_notifications_count(User.t()) :: integer()
|
||||
def unread_notifications_count(%User{id: user_id}) do
|
||||
from(q in __MODULE__,
|
||||
|
|
@ -44,9 +61,21 @@ defmodule Pleroma.Notification do
|
|||
|> Repo.aggregate(:count, :id)
|
||||
end
|
||||
|
||||
@notification_types ~w{
|
||||
favourite
|
||||
follow
|
||||
follow_request
|
||||
mention
|
||||
move
|
||||
pleroma:chat_mention
|
||||
pleroma:emoji_reaction
|
||||
reblog
|
||||
}
|
||||
|
||||
def changeset(%Notification{} = notification, attrs) do
|
||||
notification
|
||||
|> cast(attrs, [:seen])
|
||||
|> cast(attrs, [:seen, :type])
|
||||
|> validate_inclusion(:type, @notification_types)
|
||||
end
|
||||
|
||||
@spec last_read_query(User.t()) :: Ecto.Queryable.t()
|
||||
|
|
@ -137,8 +166,16 @@ defmodule Pleroma.Notification do
|
|||
query
|
||||
|> join(:left, [n, a], mutated_activity in Pleroma.Activity,
|
||||
on:
|
||||
fragment("?->>'context'", a.data) ==
|
||||
fragment("?->>'context'", mutated_activity.data) and
|
||||
fragment(
|
||||
"COALESCE((?->'object')->>'id', ?->>'object')",
|
||||
a.data,
|
||||
a.data
|
||||
) ==
|
||||
fragment(
|
||||
"COALESCE((?->'object')->>'id', ?->>'object')",
|
||||
mutated_activity.data,
|
||||
mutated_activity.data
|
||||
) and
|
||||
fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
|
||||
fragment("?->>'type'", mutated_activity.data) == "Create",
|
||||
as: :mutated_activity
|
||||
|
|
@ -300,42 +337,95 @@ defmodule Pleroma.Notification do
|
|||
end
|
||||
end
|
||||
|
||||
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
|
||||
object = Object.normalize(activity)
|
||||
def create_notifications(activity, options \\ [])
|
||||
|
||||
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
|
||||
object = Object.normalize(activity, false)
|
||||
|
||||
if object && object.data["type"] == "Answer" do
|
||||
{:ok, []}
|
||||
else
|
||||
do_create_notifications(activity)
|
||||
do_create_notifications(activity, options)
|
||||
end
|
||||
end
|
||||
|
||||
def create_notifications(%Activity{data: %{"type" => type}} = activity)
|
||||
def create_notifications(%Activity{data: %{"type" => type}} = activity, options)
|
||||
when type in ["Follow", "Like", "Announce", "Move", "EmojiReact"] do
|
||||
do_create_notifications(activity)
|
||||
do_create_notifications(activity, options)
|
||||
end
|
||||
|
||||
def create_notifications(_), do: {:ok, []}
|
||||
def create_notifications(_, _), do: {:ok, []}
|
||||
|
||||
defp do_create_notifications(%Activity{} = activity, options) do
|
||||
do_send = Keyword.get(options, :do_send, true)
|
||||
|
||||
defp do_create_notifications(%Activity{} = activity) do
|
||||
{enabled_receivers, disabled_receivers} = get_notified_from_activity(activity)
|
||||
potential_receivers = enabled_receivers ++ disabled_receivers
|
||||
|
||||
notifications =
|
||||
Enum.map(potential_receivers, fn user ->
|
||||
do_send = user in enabled_receivers
|
||||
do_send = do_send && user in enabled_receivers
|
||||
create_notification(activity, user, do_send)
|
||||
end)
|
||||
|
||||
{:ok, notifications}
|
||||
end
|
||||
|
||||
defp type_from_activity(%{data: %{"type" => type}} = activity) do
|
||||
case type do
|
||||
"Follow" ->
|
||||
if Activity.follow_accepted?(activity) do
|
||||
"follow"
|
||||
else
|
||||
"follow_request"
|
||||
end
|
||||
|
||||
"Announce" ->
|
||||
"reblog"
|
||||
|
||||
"Like" ->
|
||||
"favourite"
|
||||
|
||||
"Move" ->
|
||||
"move"
|
||||
|
||||
"EmojiReact" ->
|
||||
"pleroma:emoji_reaction"
|
||||
|
||||
# Compatibility with old reactions
|
||||
"EmojiReaction" ->
|
||||
"pleroma:emoji_reaction"
|
||||
|
||||
"Create" ->
|
||||
activity
|
||||
|> type_from_activity_object()
|
||||
|
||||
t ->
|
||||
raise "No notification type for activity type #{t}"
|
||||
end
|
||||
end
|
||||
|
||||
defp type_from_activity_object(%{data: %{"type" => "Create", "object" => %{}}}), do: "mention"
|
||||
|
||||
defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
|
||||
object = Object.get_by_ap_id(activity.data["object"])
|
||||
|
||||
case object && object.data["type"] do
|
||||
"ChatMessage" -> "pleroma:chat_mention"
|
||||
_ -> "mention"
|
||||
end
|
||||
end
|
||||
|
||||
# TODO move to sql, too.
|
||||
def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do
|
||||
unless skip?(activity, user) do
|
||||
{:ok, %{notification: notification}} =
|
||||
Multi.new()
|
||||
|> Multi.insert(:notification, %Notification{user_id: user.id, activity: activity})
|
||||
|> Multi.insert(:notification, %Notification{
|
||||
user_id: user.id,
|
||||
activity: activity,
|
||||
type: type_from_activity(activity)
|
||||
})
|
||||
|> Marker.multi_set_last_read_id(user, "notifications")
|
||||
|> Repo.transaction()
|
||||
|
||||
|
|
@ -459,6 +549,7 @@ defmodule Pleroma.Notification do
|
|||
def skip?(%Activity{} = activity, %User{} = user) do
|
||||
[
|
||||
:self,
|
||||
:invisible,
|
||||
:from_followers,
|
||||
:from_following,
|
||||
:from_strangers,
|
||||
|
|
@ -474,6 +565,12 @@ defmodule Pleroma.Notification do
|
|||
activity.data["actor"] == user.ap_id
|
||||
end
|
||||
|
||||
def skip?(:invisible, %Activity{} = activity, _) do
|
||||
actor = activity.data["actor"]
|
||||
user = User.get_cached_by_ap_id(actor)
|
||||
User.invisible?(user)
|
||||
end
|
||||
|
||||
def skip?(
|
||||
:from_followers,
|
||||
%Activity{} = activity,
|
||||
|
|
@ -516,4 +613,12 @@ defmodule Pleroma.Notification do
|
|||
end
|
||||
|
||||
def skip?(_, _, _), do: false
|
||||
|
||||
def for_user_and_activity(user, activity) do
|
||||
from(n in __MODULE__,
|
||||
where: n.user_id == ^user.id,
|
||||
where: n.activity_id == ^activity.id
|
||||
)
|
||||
|> Repo.one()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ defmodule Pleroma.Pagination do
|
|||
@spec fetch_paginated(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()]
|
||||
def fetch_paginated(query, params, type \\ :keyset, table_binding \\ nil)
|
||||
|
||||
def fetch_paginated(query, %{"total" => true} = params, :keyset, table_binding) do
|
||||
def fetch_paginated(query, %{total: true} = params, :keyset, table_binding) do
|
||||
total = Repo.aggregate(query, :count, :id)
|
||||
|
||||
%{
|
||||
total: total,
|
||||
items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset, table_binding)
|
||||
items: fetch_paginated(query, Map.drop(params, [:total]), :keyset, table_binding)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ defmodule Pleroma.Pagination do
|
|||
|> enforce_order(options)
|
||||
end
|
||||
|
||||
def fetch_paginated(query, %{"total" => true} = params, :offset, table_binding) do
|
||||
def fetch_paginated(query, %{total: true} = params, :offset, table_binding) do
|
||||
total =
|
||||
query
|
||||
|> Ecto.Query.exclude(:left_join)
|
||||
|
|
@ -49,7 +49,7 @@ defmodule Pleroma.Pagination do
|
|||
|
||||
%{
|
||||
total: total,
|
||||
items: fetch_paginated(query, Map.drop(params, ["total"]), :offset, table_binding)
|
||||
items: fetch_paginated(query, Map.drop(params, [:total]), :offset, table_binding)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -64,6 +64,12 @@ defmodule Pleroma.Pagination do
|
|||
@spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()]
|
||||
def paginate(query, options, method \\ :keyset, table_binding \\ nil)
|
||||
|
||||
def paginate(list, options, _method, _table_binding) when is_list(list) do
|
||||
offset = options[:offset] || 0
|
||||
limit = options[:limit] || 0
|
||||
Enum.slice(list, offset, limit)
|
||||
end
|
||||
|
||||
def paginate(query, options, :keyset, table_binding) do
|
||||
query
|
||||
|> restrict(:min_id, options, table_binding)
|
||||
|
|
@ -90,12 +96,6 @@ defmodule Pleroma.Pagination do
|
|||
skip_order: :boolean
|
||||
}
|
||||
|
||||
params =
|
||||
Enum.reduce(params, %{}, fn
|
||||
{key, _value}, acc when is_atom(key) -> Map.drop(acc, [key])
|
||||
{key, value}, acc -> Map.put(acc, key, value)
|
||||
end)
|
||||
|
||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||
changeset.changes
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
{"x-content-type-options", "nosniff"},
|
||||
{"referrer-policy", referrer_policy},
|
||||
{"x-download-options", "noopen"},
|
||||
{"content-security-policy", csp_string() <> ";"}
|
||||
{"content-security-policy", csp_string()}
|
||||
]
|
||||
|
||||
if report_uri do
|
||||
|
|
@ -43,23 +43,46 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
]
|
||||
}
|
||||
|
||||
headers ++ [{"reply-to", Jason.encode!(report_group)}]
|
||||
[{"reply-to", Jason.encode!(report_group)} | headers]
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
static_csp_rules = [
|
||||
"default-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"font-src 'self'",
|
||||
"manifest-src 'self'"
|
||||
]
|
||||
|
||||
@csp_start [Enum.join(static_csp_rules, ";") <> ";"]
|
||||
|
||||
defp csp_string do
|
||||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
||||
static_url = Pleroma.Web.Endpoint.static_url()
|
||||
websocket_url = Pleroma.Web.Endpoint.websocket_url()
|
||||
report_uri = Config.get([:http_security, :report_uri])
|
||||
|
||||
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
|
||||
img_src = "img-src 'self' data: blob:"
|
||||
media_src = "media-src 'self'"
|
||||
|
||||
{img_src, media_src} =
|
||||
if Config.get([:media_proxy, :enabled]) &&
|
||||
!Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
|
||||
sources = get_proxy_and_attachment_sources()
|
||||
{[img_src, sources], [media_src, sources]}
|
||||
else
|
||||
{[img_src, " https:"], [media_src, " https:"]}
|
||||
end
|
||||
|
||||
connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
|
||||
|
||||
connect_src =
|
||||
if Pleroma.Config.get(:env) == :dev do
|
||||
connect_src <> " http://localhost:3035/"
|
||||
[connect_src, " http://localhost:3035/"]
|
||||
else
|
||||
connect_src
|
||||
end
|
||||
|
|
@ -71,27 +94,51 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
"script-src 'self'"
|
||||
end
|
||||
|
||||
main_part = [
|
||||
"default-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"img-src 'self' data: blob: https:",
|
||||
"media-src 'self' https:",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"font-src 'self'",
|
||||
"manifest-src 'self'",
|
||||
connect_src,
|
||||
script_src
|
||||
]
|
||||
report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
|
||||
insecure = if scheme == "https", do: "upgrade-insecure-requests"
|
||||
|
||||
report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: []
|
||||
|
||||
insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: []
|
||||
|
||||
(main_part ++ report ++ insecure)
|
||||
|> Enum.join("; ")
|
||||
@csp_start
|
||||
|> add_csp_param(img_src)
|
||||
|> add_csp_param(media_src)
|
||||
|> add_csp_param(connect_src)
|
||||
|> add_csp_param(script_src)
|
||||
|> add_csp_param(insecure)
|
||||
|> add_csp_param(report)
|
||||
|> :erlang.iolist_to_binary()
|
||||
end
|
||||
|
||||
defp get_proxy_and_attachment_sources do
|
||||
media_proxy_whitelist =
|
||||
Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc ->
|
||||
add_source(acc, host)
|
||||
end)
|
||||
|
||||
media_proxy_base_url =
|
||||
if Config.get([:media_proxy, :base_url]),
|
||||
do: URI.parse(Config.get([:media_proxy, :base_url])).host
|
||||
|
||||
upload_base_url =
|
||||
if Config.get([Pleroma.Upload, :base_url]),
|
||||
do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host
|
||||
|
||||
s3_endpoint =
|
||||
if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3,
|
||||
do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host
|
||||
|
||||
[]
|
||||
|> add_source(media_proxy_base_url)
|
||||
|> add_source(upload_base_url)
|
||||
|> add_source(s3_endpoint)
|
||||
|> add_source(media_proxy_whitelist)
|
||||
end
|
||||
|
||||
defp add_source(iodata, nil), do: iodata
|
||||
defp add_source(iodata, source), do: [[?\s, source] | iodata]
|
||||
|
||||
defp add_csp_param(csp_iodata, nil), do: csp_iodata
|
||||
|
||||
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
|
||||
|
||||
def warn_if_disabled do
|
||||
unless Config.get([:http_security, :enabled]) do
|
||||
Logger.warn("
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ defmodule Pleroma.Plugs.UploadedMedia do
|
|||
import Pleroma.Web.Gettext
|
||||
require Logger
|
||||
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
@behaviour Plug
|
||||
# no slashes
|
||||
@path "media"
|
||||
|
|
@ -35,8 +37,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
|
|||
%{query_params: %{"name" => name}} = conn ->
|
||||
name = String.replace(name, "\"", "\\\"")
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-disposition", "filename=\"#{name}\"")
|
||||
put_resp_header(conn, "content-disposition", "filename=\"#{name}\"")
|
||||
|
||||
conn ->
|
||||
conn
|
||||
|
|
@ -47,7 +48,8 @@ defmodule Pleroma.Plugs.UploadedMedia do
|
|||
|
||||
with uploader <- Keyword.fetch!(config, :uploader),
|
||||
proxy_remote = Keyword.get(config, :proxy_remote, false),
|
||||
{:ok, get_method} <- uploader.get_file(file) do
|
||||
{:ok, get_method} <- uploader.get_file(file),
|
||||
false <- media_is_banned(conn, get_method) do
|
||||
get_media(conn, get_method, proxy_remote, opts)
|
||||
else
|
||||
_ ->
|
||||
|
|
@ -59,6 +61,14 @@ defmodule Pleroma.Plugs.UploadedMedia do
|
|||
|
||||
def call(conn, _opts), do: conn
|
||||
|
||||
defp media_is_banned(%{request_path: path} = _conn, {:static_dir, _}) do
|
||||
MediaProxy.in_banned_urls(Pleroma.Web.base_url() <> path)
|
||||
end
|
||||
|
||||
defp media_is_banned(_, {:url, url}), do: MediaProxy.in_banned_urls(url)
|
||||
|
||||
defp media_is_banned(_, _), do: false
|
||||
|
||||
defp get_media(conn, {:static_dir, directory}, _, opts) do
|
||||
static_opts =
|
||||
Map.get(opts, :static_plug_opts)
|
||||
|
|
|
|||
|
|
@ -8,11 +8,10 @@ defmodule Pleroma.Repo do
|
|||
adapter: Ecto.Adapters.Postgres,
|
||||
migration_timestamps: [type: :naive_datetime_usec]
|
||||
|
||||
import Ecto.Query
|
||||
require Logger
|
||||
|
||||
defmodule Instrumenter do
|
||||
use Prometheus.EctoInstrumenter
|
||||
end
|
||||
defmodule Instrumenter, do: use(Prometheus.EctoInstrumenter)
|
||||
|
||||
@doc """
|
||||
Dynamically loads the repository url from the
|
||||
|
|
@ -50,36 +49,30 @@ defmodule Pleroma.Repo do
|
|||
end
|
||||
end
|
||||
|
||||
def check_migrations_applied!() do
|
||||
unless Pleroma.Config.get(
|
||||
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
|
||||
false
|
||||
) do
|
||||
Ecto.Migrator.with_repo(__MODULE__, fn repo ->
|
||||
down_migrations =
|
||||
Ecto.Migrator.migrations(repo)
|
||||
|> Enum.reject(fn
|
||||
{:up, _, _} -> true
|
||||
{:down, _, _} -> false
|
||||
end)
|
||||
def chunk_stream(query, chunk_size) do
|
||||
# We don't actually need start and end funcitons of resource streaming,
|
||||
# but it seems to be the only way to not fetch records one-by-one and
|
||||
# have individual records be the elements of the stream, instead of
|
||||
# lists of records
|
||||
Stream.resource(
|
||||
fn -> 0 end,
|
||||
fn
|
||||
last_id ->
|
||||
query
|
||||
|> order_by(asc: :id)
|
||||
|> where([r], r.id > ^last_id)
|
||||
|> limit(^chunk_size)
|
||||
|> all()
|
||||
|> case do
|
||||
[] ->
|
||||
{:halt, last_id}
|
||||
|
||||
if length(down_migrations) > 0 do
|
||||
down_migrations_text =
|
||||
Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
|
||||
|
||||
Logger.error(
|
||||
"The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
|
||||
)
|
||||
|
||||
raise Pleroma.Repo.UnappliedMigrationsError
|
||||
end
|
||||
end)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
records ->
|
||||
last_id = List.last(records).id
|
||||
{records, last_id}
|
||||
end
|
||||
end,
|
||||
fn _ -> :ok end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.Repo.UnappliedMigrationsError do
|
||||
defexception message: "Unapplied Migrations detected"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
defmodule Pleroma.Signature do
|
||||
@behaviour HTTPSignatures.Adapter
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Keys
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
|
||||
def key_id_to_actor_id(key_id) do
|
||||
uri =
|
||||
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.Signature do
|
|||
|
||||
maybe_ap_id = URI.to_string(uri)
|
||||
|
||||
case Types.ObjectID.cast(maybe_ap_id) do
|
||||
case ObjectValidators.ObjectID.cast(maybe_ap_id) do
|
||||
{:ok, ap_id} ->
|
||||
{:ok, ap_id}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,20 +97,11 @@ defmodule Pleroma.Stats do
|
|||
}
|
||||
end
|
||||
|
||||
def get_status_visibility_count do
|
||||
counter_cache =
|
||||
CounterCache.get_as_map([
|
||||
"status_visibility_public",
|
||||
"status_visibility_private",
|
||||
"status_visibility_unlisted",
|
||||
"status_visibility_direct"
|
||||
])
|
||||
|
||||
%{
|
||||
public: counter_cache["status_visibility_public"] || 0,
|
||||
unlisted: counter_cache["status_visibility_unlisted"] || 0,
|
||||
private: counter_cache["status_visibility_private"] || 0,
|
||||
direct: counter_cache["status_visibility_direct"] || 0
|
||||
}
|
||||
def get_status_visibility_count(instance \\ nil) do
|
||||
if is_nil(instance) do
|
||||
CounterCache.get_sum()
|
||||
else
|
||||
CounterCache.get_by_instance(instance)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ defmodule Pleroma.Upload do
|
|||
{:ok,
|
||||
%{
|
||||
"type" => opts.activity_type,
|
||||
"mediaType" => upload.content_type,
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Config
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Delivery
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Formatter
|
||||
|
|
@ -30,7 +31,6 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
|
@ -79,6 +79,7 @@ defmodule Pleroma.User do
|
|||
|
||||
schema "users" do
|
||||
field(:bio, :string)
|
||||
field(:raw_bio, :string)
|
||||
field(:email, :string)
|
||||
field(:name, :string)
|
||||
field(:nickname, :string)
|
||||
|
|
@ -115,7 +116,7 @@ defmodule Pleroma.User do
|
|||
field(:is_admin, :boolean, default: false)
|
||||
field(:show_role, :boolean, default: true)
|
||||
field(:settings, :map, default: nil)
|
||||
field(:uri, Types.Uri, default: nil)
|
||||
field(:uri, ObjectValidators.Uri, default: nil)
|
||||
field(:hide_followers_count, :boolean, default: false)
|
||||
field(:hide_follows_count, :boolean, default: false)
|
||||
field(:hide_followers, :boolean, default: false)
|
||||
|
|
@ -262,37 +263,60 @@ defmodule Pleroma.User do
|
|||
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
|
||||
|
||||
def account_status(%User{confirmation_pending: true}) do
|
||||
case Config.get([:instance, :account_activation_required]) do
|
||||
true -> :confirmation_pending
|
||||
_ -> :active
|
||||
if Config.get([:instance, :account_activation_required]) do
|
||||
:confirmation_pending
|
||||
else
|
||||
:active
|
||||
end
|
||||
end
|
||||
|
||||
def account_status(%User{}), do: :active
|
||||
|
||||
@spec visible_for?(User.t(), User.t() | nil) :: boolean()
|
||||
def visible_for?(user, for_user \\ nil)
|
||||
@spec visible_for(User.t(), User.t() | nil) ::
|
||||
:visible
|
||||
| :invisible
|
||||
| :restricted_unauthenticated
|
||||
| :deactivated
|
||||
| :confirmation_pending
|
||||
def visible_for(user, for_user \\ nil)
|
||||
|
||||
def visible_for?(%User{invisible: true}, _), do: false
|
||||
def visible_for(%User{invisible: true}, _), do: :invisible
|
||||
|
||||
def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true
|
||||
def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
|
||||
|
||||
def visible_for?(%User{local: local} = user, nil) do
|
||||
cfg_key =
|
||||
if local,
|
||||
do: :local,
|
||||
else: :remote
|
||||
|
||||
if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
|
||||
do: false,
|
||||
else: account_status(user) == :active
|
||||
def visible_for(%User{} = user, nil) do
|
||||
if restrict_unauthenticated?(user) do
|
||||
:restrict_unauthenticated
|
||||
else
|
||||
visible_account_status(user)
|
||||
end
|
||||
end
|
||||
|
||||
def visible_for?(%User{} = user, for_user) do
|
||||
account_status(user) == :active || superuser?(for_user)
|
||||
def visible_for(%User{} = user, for_user) do
|
||||
if superuser?(for_user) do
|
||||
:visible
|
||||
else
|
||||
visible_account_status(user)
|
||||
end
|
||||
end
|
||||
|
||||
def visible_for?(_, _), do: false
|
||||
def visible_for(_, _), do: :invisible
|
||||
|
||||
defp restrict_unauthenticated?(%User{local: local}) do
|
||||
config_key = if local, do: :local, else: :remote
|
||||
|
||||
Config.get([:restrict_unauthenticated, :profiles, config_key], false)
|
||||
end
|
||||
|
||||
defp visible_account_status(user) do
|
||||
status = account_status(user)
|
||||
|
||||
if status in [:active, :password_reset_pending] do
|
||||
:visible
|
||||
else
|
||||
status
|
||||
end
|
||||
end
|
||||
|
||||
@spec superuser?(User.t()) :: boolean()
|
||||
def superuser?(%User{local: true, is_admin: true}), do: true
|
||||
|
|
@ -432,6 +456,7 @@ defmodule Pleroma.User do
|
|||
params,
|
||||
[
|
||||
:bio,
|
||||
:raw_bio,
|
||||
:name,
|
||||
:emoji,
|
||||
:avatar,
|
||||
|
|
@ -463,6 +488,7 @@ defmodule Pleroma.User do
|
|||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, min: 1, max: name_limit)
|
||||
|> validate_inclusion(:actor_type, ["Person", "Service"])
|
||||
|> put_fields()
|
||||
|> put_emoji()
|
||||
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
||||
|
|
@ -538,9 +564,10 @@ defmodule Pleroma.User do
|
|||
|> delete_change(:also_known_as)
|
||||
|> unique_constraint(:email)
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_inclusion(:actor_type, ["Person", "Service"])
|
||||
end
|
||||
|
||||
@spec update_as_admin(%User{}, map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||
def update_as_admin(user, params) do
|
||||
params = Map.put(params, "password_confirmation", params["password"])
|
||||
changeset = update_as_admin_changeset(user, params)
|
||||
|
|
@ -561,7 +588,7 @@ defmodule Pleroma.User do
|
|||
|> put_change(:password_reset_pending, false)
|
||||
end
|
||||
|
||||
@spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||
def reset_password(%User{} = user, params) do
|
||||
reset_password(user, user, params)
|
||||
end
|
||||
|
|
@ -606,7 +633,16 @@ defmodule Pleroma.User do
|
|||
|
||||
struct
|
||||
|> confirmation_changeset(need_confirmation: need_confirmation?)
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation, :emoji])
|
||||
|> cast(params, [
|
||||
:bio,
|
||||
:raw_bio,
|
||||
:email,
|
||||
:name,
|
||||
:nickname,
|
||||
:password,
|
||||
:password_confirmation,
|
||||
:emoji
|
||||
])
|
||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|
|
@ -746,7 +782,6 @@ defmodule Pleroma.User do
|
|||
|
||||
follower
|
||||
|> update_following_count()
|
||||
|> set_cache()
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -775,7 +810,6 @@ defmodule Pleroma.User do
|
|||
{:ok, follower} =
|
||||
follower
|
||||
|> update_following_count()
|
||||
|> set_cache()
|
||||
|
||||
{:ok, follower, followed}
|
||||
|
||||
|
|
@ -1127,35 +1161,25 @@ defmodule Pleroma.User do
|
|||
])
|
||||
end
|
||||
|
||||
@spec update_follower_count(User.t()) :: {:ok, User.t()}
|
||||
def update_follower_count(%User{} = user) do
|
||||
if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
||||
follower_count_query =
|
||||
User.Query.build(%{followers: user, deactivated: false})
|
||||
|> select([u], %{count: count(u.id)})
|
||||
follower_count = FollowingRelationship.follower_count(user)
|
||||
|
||||
User
|
||||
|> where(id: ^user.id)
|
||||
|> join(:inner, [u], s in subquery(follower_count_query))
|
||||
|> update([u, s],
|
||||
set: [follower_count: s.count]
|
||||
)
|
||||
|> select([u], u)
|
||||
|> Repo.update_all([])
|
||||
|> case do
|
||||
{1, [user]} -> set_cache(user)
|
||||
_ -> {:error, user}
|
||||
end
|
||||
user
|
||||
|> follow_information_changeset(%{follower_count: follower_count})
|
||||
|> update_and_set_cache
|
||||
else
|
||||
{:ok, maybe_fetch_follow_information(user)}
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_following_count(User.t()) :: User.t()
|
||||
@spec update_following_count(User.t()) :: {:ok, User.t()}
|
||||
def update_following_count(%User{local: false} = user) do
|
||||
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
||||
maybe_fetch_follow_information(user)
|
||||
{:ok, maybe_fetch_follow_information(user)}
|
||||
else
|
||||
user
|
||||
{:ok, user}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1164,7 +1188,7 @@ defmodule Pleroma.User do
|
|||
|
||||
user
|
||||
|> follow_information_changeset(%{following_count: following_count})
|
||||
|> Repo.update!()
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def set_unread_conversation_count(%User{local: true} = user) do
|
||||
|
|
@ -1487,6 +1511,9 @@ defmodule Pleroma.User do
|
|||
end)
|
||||
|
||||
delete_user_activities(user)
|
||||
delete_notifications_from_user_activities(user)
|
||||
|
||||
delete_outgoing_pending_follow_requests(user)
|
||||
|
||||
delete_or_deactivate(user)
|
||||
end
|
||||
|
|
@ -1573,6 +1600,13 @@ defmodule Pleroma.User do
|
|||
})
|
||||
end
|
||||
|
||||
def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
|
||||
Notification
|
||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||
|> where([n, a], fragment("? = ?", a.actor, ^ap_id))
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
|
||||
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||
ap_id
|
||||
|> Activity.Queries.by_actor()
|
||||
|
|
@ -1610,6 +1644,12 @@ defmodule Pleroma.User do
|
|||
|
||||
defp delete_activity(_activity, _user), do: "Doing nothing"
|
||||
|
||||
defp delete_outgoing_pending_follow_requests(user) do
|
||||
user
|
||||
|> FollowingRelationship.outgoing_pending_follow_requests_query()
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
|
||||
def html_filter_policy(%User{no_rich_text: true}) do
|
||||
Pleroma.HTML.Scrubber.TwitterText
|
||||
end
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ defmodule Pleroma.User.Query do
|
|||
is_admin: boolean(),
|
||||
is_moderator: boolean(),
|
||||
super_users: boolean(),
|
||||
exclude_service_users: boolean(),
|
||||
invisible: boolean(),
|
||||
followers: User.t(),
|
||||
friends: User.t(),
|
||||
recipients_from_activity: [String.t()],
|
||||
|
|
@ -89,8 +89,8 @@ defmodule Pleroma.User.Query do
|
|||
where(query, [u], ilike(field(u, ^key), ^"%#{value}%"))
|
||||
end
|
||||
|
||||
defp compose_query({:exclude_service_users, _}, query) do
|
||||
where(query, [u], not like(u.ap_id, "%/relay") and not like(u.ap_id, "%/internal/fetch"))
|
||||
defp compose_query({:invisible, bool}, query) when is_boolean(bool) do
|
||||
where(query, [u], u.invisible == ^bool)
|
||||
end
|
||||
|
||||
defp compose_query({key, value}, query)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
alias Pleroma.Web.ActivityPub.UserView
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.FederatingPlug
|
||||
alias Pleroma.Web.Federator
|
||||
|
|
@ -230,27 +231,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
when page? in [true, "true"] do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- User.ensure_keys_present(user) do
|
||||
activities =
|
||||
if params["max_id"] do
|
||||
ActivityPub.fetch_user_activities(user, for_user, %{
|
||||
"max_id" => params["max_id"],
|
||||
# This is a hack because postgres generates inefficient queries when filtering by
|
||||
# 'Answer', poll votes will be hidden by the visibility filter in this case anyway
|
||||
"include_poll_votes" => true,
|
||||
"limit" => 10
|
||||
})
|
||||
else
|
||||
ActivityPub.fetch_user_activities(user, for_user, %{
|
||||
"limit" => 10,
|
||||
"include_poll_votes" => true
|
||||
})
|
||||
end
|
||||
# "include_poll_votes" is a hack because postgres generates inefficient
|
||||
# queries when filtering by 'Answer', poll votes will be hidden by the
|
||||
# visibility filter in this case anyway
|
||||
params =
|
||||
params
|
||||
|> Map.drop(["nickname", "page"])
|
||||
|> Map.put("include_poll_votes", true)
|
||||
|> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
|
||||
|
||||
activities = ActivityPub.fetch_user_activities(user, for_user, params)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/activity+json")
|
||||
|> put_view(UserView)
|
||||
|> render("activity_collection_page.json", %{
|
||||
activities: activities,
|
||||
pagination: ControllerHelper.get_pagination_fields(conn, activities),
|
||||
iri: "#{user.ap_id}/outbox"
|
||||
})
|
||||
end
|
||||
|
|
@ -353,21 +350,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
%{"nickname" => nickname, "page" => page?} = params
|
||||
)
|
||||
when page? in [true, "true"] do
|
||||
params =
|
||||
params
|
||||
|> Map.drop(["nickname", "page"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
|
||||
|
||||
activities =
|
||||
if params["max_id"] do
|
||||
ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{
|
||||
"max_id" => params["max_id"],
|
||||
"limit" => 10
|
||||
})
|
||||
else
|
||||
ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{"limit" => 10})
|
||||
end
|
||||
[user.ap_id | User.following(user)]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/activity+json")
|
||||
|> put_view(UserView)
|
||||
|> render("activity_collection_page.json", %{
|
||||
activities: activities,
|
||||
pagination: ControllerHelper.get_pagination_fields(conn, activities),
|
||||
iri: "#{user.ap_id}/inbox"
|
||||
})
|
||||
end
|
||||
|
|
@ -514,7 +514,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
{new_user, for_user}
|
||||
end
|
||||
|
||||
# TODO: Add support for "object" field
|
||||
@doc """
|
||||
Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>
|
||||
|
||||
|
|
@ -525,6 +524,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
Response:
|
||||
- HTTP Code: 201 Created
|
||||
- HTTP Body: ActivityPub object to be inserted into another's `attachment` field
|
||||
|
||||
Note: Will not point to a URL with a `Location` header because no standalone Activity has been created.
|
||||
"""
|
||||
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
|
||||
with {:ok, object} <-
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
This module encodes our addressing policies and general shape of our objects.
|
||||
"""
|
||||
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
|
|
@ -64,6 +66,42 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
}, []}
|
||||
end
|
||||
|
||||
def create(actor, object, recipients) do
|
||||
{:ok,
|
||||
%{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"actor" => actor.ap_id,
|
||||
"to" => recipients,
|
||||
"object" => object,
|
||||
"type" => "Create",
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601()
|
||||
}, []}
|
||||
end
|
||||
|
||||
def chat_message(actor, recipient, content, opts \\ []) do
|
||||
basic = %{
|
||||
"id" => Utils.generate_object_id(),
|
||||
"actor" => actor.ap_id,
|
||||
"type" => "ChatMessage",
|
||||
"to" => [recipient],
|
||||
"content" => content,
|
||||
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||
"emoji" => Emoji.Formatter.get_emoji_map(content)
|
||||
}
|
||||
|
||||
case opts[:attachment] do
|
||||
%Object{data: attachment_data} ->
|
||||
{
|
||||
:ok,
|
||||
Map.put(basic, "attachment", attachment_data),
|
||||
[]
|
||||
}
|
||||
|
||||
_ ->
|
||||
{:ok, basic, []}
|
||||
end
|
||||
end
|
||||
|
||||
@spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
|
||||
def tombstone(actor, id) do
|
||||
{:ok,
|
||||
|
|
@ -85,15 +123,35 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
end
|
||||
end
|
||||
|
||||
# Retricted to user updates for now, always public
|
||||
@spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
||||
def update(actor, object) do
|
||||
to = [Pleroma.Constants.as_public(), actor.follower_address]
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"type" => "Update",
|
||||
"actor" => actor.ap_id,
|
||||
"object" => object,
|
||||
"to" => to
|
||||
}, []}
|
||||
end
|
||||
|
||||
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
||||
def announce(actor, object, options \\ []) do
|
||||
public? = Keyword.get(options, :public, false)
|
||||
to = [actor.follower_address, object.data["actor"]]
|
||||
|
||||
to =
|
||||
if public? do
|
||||
[Pleroma.Constants.as_public() | to]
|
||||
else
|
||||
to
|
||||
cond do
|
||||
actor.ap_id == Relay.relay_ap_id() ->
|
||||
[actor.follower_address]
|
||||
|
||||
public? ->
|
||||
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
|
||||
|
||||
true ->
|
||||
[actor.follower_address, object.data["actor"]]
|
||||
end
|
||||
|
||||
{:ok,
|
||||
|
|
|
|||
|
|
@ -8,18 +8,15 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
|||
def filter(policies, %{} = object) do
|
||||
policies
|
||||
|> Enum.reduce({:ok, object}, fn
|
||||
policy, {:ok, object} ->
|
||||
policy.filter(object)
|
||||
|
||||
_, error ->
|
||||
error
|
||||
policy, {:ok, object} -> policy.filter(object)
|
||||
_, error -> error
|
||||
end)
|
||||
end
|
||||
|
||||
def filter(%{} = object), do: get_policies() |> filter(object)
|
||||
|
||||
def get_policies do
|
||||
Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies()
|
||||
Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
|
||||
end
|
||||
|
||||
defp get_policies(policy) when is_atom(policy), do: [policy]
|
||||
|
|
@ -54,7 +51,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
|||
get_policies()
|
||||
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
||||
|
||||
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||
exclusions = Pleroma.Config.get([:mrf, :transparency_exclusions])
|
||||
|
||||
base =
|
||||
%{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
|
||||
@moduledoc "Adds expiration to all local Create activities"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@impl true
|
||||
def filter(activity) do
|
||||
activity =
|
||||
if note?(activity) and local?(activity) do
|
||||
maybe_add_expiration(activity)
|
||||
else
|
||||
activity
|
||||
end
|
||||
|
||||
{:ok, activity}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
|
||||
defp local?(%{"id" => id}) do
|
||||
String.starts_with?(id, Pleroma.Web.Endpoint.url())
|
||||
end
|
||||
|
||||
defp note?(activity) do
|
||||
match?(%{"type" => "Create", "object" => %{"type" => "Note"}}, activity)
|
||||
end
|
||||
|
||||
defp maybe_add_expiration(activity) do
|
||||
days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
|
||||
expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: days)
|
||||
|
||||
with %{"expires_at" => existing_expires_at} <- activity,
|
||||
:lt <- NaiveDateTime.compare(existing_expires_at, expires_at) do
|
||||
activity
|
||||
else
|
||||
_ -> Map.put(activity, "expires_at", expires_at)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -13,8 +13,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
|
|||
|
||||
defp delist_message(message, threshold) when threshold > 0 do
|
||||
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
|
||||
to = message["to"] || []
|
||||
cc = message["cc"] || []
|
||||
|
||||
follower_collection? = Enum.member?(message["to"] ++ message["cc"], follower_collection)
|
||||
follower_collection? = Enum.member?(to ++ cc, follower_collection)
|
||||
|
||||
message =
|
||||
case get_recipient_count(message) do
|
||||
|
|
@ -71,7 +73,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
|
|||
end
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create"} = message) do
|
||||
def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message)
|
||||
when object_type in ~w{Note Article} do
|
||||
reject_threshold =
|
||||
Pleroma.Config.get(
|
||||
[:mrf_hellthread, :reject_threshold],
|
||||
|
|
|
|||
|
|
@ -3,21 +3,23 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
@moduledoc "Filter activities depending on their origin instance"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
||||
accepts =
|
||||
Pleroma.Config.get([:mrf_simple, :accept])
|
||||
Config.get([:mrf_simple, :accept])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
cond do
|
||||
accepts == [] -> {:ok, object}
|
||||
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
|
||||
actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
|
||||
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
|
||||
true -> {:reject, nil}
|
||||
end
|
||||
|
|
@ -25,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|
||||
defp check_reject(%{host: actor_host} = _actor_info, object) do
|
||||
rejects =
|
||||
Pleroma.Config.get([:mrf_simple, :reject])
|
||||
Config.get([:mrf_simple, :reject])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
if MRF.subdomain_match?(rejects, actor_host) do
|
||||
|
|
@ -41,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
)
|
||||
when length(child_attachment) > 0 do
|
||||
media_removal =
|
||||
Pleroma.Config.get([:mrf_simple, :media_removal])
|
||||
Config.get([:mrf_simple, :media_removal])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
object =
|
||||
|
|
@ -65,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
} = object
|
||||
) do
|
||||
media_nsfw =
|
||||
Pleroma.Config.get([:mrf_simple, :media_nsfw])
|
||||
Config.get([:mrf_simple, :media_nsfw])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
object =
|
||||
|
|
@ -85,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|
||||
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
||||
timeline_removal =
|
||||
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
|
||||
Config.get([:mrf_simple, :federated_timeline_removal])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
object =
|
||||
|
|
@ -108,7 +110,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|
||||
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
|
||||
report_removal =
|
||||
Pleroma.Config.get([:mrf_simple, :report_removal])
|
||||
Config.get([:mrf_simple, :report_removal])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
if MRF.subdomain_match?(report_removal, actor_host) do
|
||||
|
|
@ -122,7 +124,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|
||||
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
|
||||
avatar_removal =
|
||||
Pleroma.Config.get([:mrf_simple, :avatar_removal])
|
||||
Config.get([:mrf_simple, :avatar_removal])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
if MRF.subdomain_match?(avatar_removal, actor_host) do
|
||||
|
|
@ -136,7 +138,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|
||||
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
|
||||
banner_removal =
|
||||
Pleroma.Config.get([:mrf_simple, :banner_removal])
|
||||
Config.get([:mrf_simple, :banner_removal])
|
||||
|> MRF.subdomains_regex()
|
||||
|
||||
if MRF.subdomain_match?(banner_removal, actor_host) do
|
||||
|
|
@ -197,10 +199,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
|
||||
@impl true
|
||||
def describe do
|
||||
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||
exclusions = Config.get([:mrf, :transparency_exclusions])
|
||||
|
||||
mrf_simple =
|
||||
Pleroma.Config.get(:mrf_simple)
|
||||
Config.get(:mrf_simple)
|
||||
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
|
|||
|
||||
allow_list =
|
||||
Config.get(
|
||||
[:mrf_user_allowlist, String.to_atom(actor_info.host)],
|
||||
[:mrf_user_allowlist, actor_info.host],
|
||||
[]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,18 +9,31 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
the system.
|
||||
"""
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
|
||||
|
||||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||
def validate(object, meta)
|
||||
|
||||
def validate(%{"type" => "Update"} = update_activity, meta) do
|
||||
with {:ok, update_activity} <-
|
||||
update_activity
|
||||
|> UpdateValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
update_activity = stringify_keys(update_activity)
|
||||
{:ok, update_activity, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "Undo"} = object, meta) do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|
|
@ -43,8 +56,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
|
||||
def validate(%{"type" => "Like"} = object, meta) do
|
||||
with {:ok, object} <-
|
||||
object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object |> Map.from_struct())
|
||||
object
|
||||
|> LikeValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "ChatMessage"} = object, meta) do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> ChatMessageValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
|
@ -59,6 +84,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "Create", "object" => object} = create_activity, meta) do
|
||||
with {:ok, object_data} <- cast_and_apply(object),
|
||||
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
||||
{:ok, create_activity} <-
|
||||
create_activity
|
||||
|> CreateChatMessageValidator.cast_and_validate(meta)
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
create_activity = stringify_keys(create_activity)
|
||||
{:ok, create_activity, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "Announce"} = object, meta) do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|
|
@ -69,19 +106,32 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
end
|
||||
end
|
||||
|
||||
def cast_and_apply(%{"type" => "ChatMessage"} = object) do
|
||||
ChatMessageValidator.cast_and_apply(object)
|
||||
end
|
||||
|
||||
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
|
||||
|
||||
def stringify_keys(%{__struct__: _} = object) do
|
||||
object
|
||||
|> Map.from_struct()
|
||||
|> stringify_keys
|
||||
end
|
||||
|
||||
def stringify_keys(object) do
|
||||
def stringify_keys(object) when is_map(object) do
|
||||
object
|
||||
|> Map.new(fn {key, val} -> {to_string(key), val} end)
|
||||
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
|
||||
end
|
||||
|
||||
def stringify_keys(object) when is_list(object) do
|
||||
object
|
||||
|> Enum.map(&stringify_keys/1)
|
||||
end
|
||||
|
||||
def stringify_keys(object), do: object
|
||||
|
||||
def fetch_actor(object) do
|
||||
with {:ok, actor} <- Types.ObjectID.cast(object["actor"]) do
|
||||
with {:ok, actor} <- ObjectValidators.ObjectID.cast(object["actor"]) do
|
||||
User.get_or_fetch_by_ap_id(actor)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
|
|
@ -19,14 +19,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, Types.ObjectID)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:context, :string, autogenerate: {Utils, :generate_context_id, []})
|
||||
field(:to, Types.Recipients, default: [])
|
||||
field(:cc, Types.Recipients, default: [])
|
||||
field(:published, Types.DateTime)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:published, ObjectValidators.DateTime)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
embedded_schema do
|
||||
field(:type, :string)
|
||||
field(:mediaType, :string, default: "application/octet-stream")
|
||||
field(:name, :string)
|
||||
|
||||
embeds_many(:url, UrlObjectValidator)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data()
|
||||
|> validate_data()
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> changeset(data)
|
||||
end
|
||||
|
||||
def changeset(struct, data) do
|
||||
data =
|
||||
data
|
||||
|> fix_media_type()
|
||||
|> fix_url()
|
||||
|
||||
struct
|
||||
|> cast(data, [:type, :mediaType, :name])
|
||||
|> cast_embed(:url, required: true)
|
||||
end
|
||||
|
||||
def fix_media_type(data) do
|
||||
data =
|
||||
data
|
||||
|> Map.put_new("mediaType", data["mimeType"])
|
||||
|
||||
if MIME.valid?(data["mediaType"]) do
|
||||
data
|
||||
else
|
||||
data
|
||||
|> Map.put("mediaType", "application/octet-stream")
|
||||
end
|
||||
end
|
||||
|
||||
def fix_url(data) do
|
||||
case data["url"] do
|
||||
url when is_binary(url) ->
|
||||
data
|
||||
|> Map.put(
|
||||
"url",
|
||||
[
|
||||
%{
|
||||
"href" => url,
|
||||
"type" => "Link",
|
||||
"mediaType" => data["mediaType"]
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
_ ->
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
def validate_data(cng) do
|
||||
cng
|
||||
|> validate_required([:mediaType, :url, :type])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.Transmogrifier, only: [fix_emoji: 1]
|
||||
|
||||
@primary_key false
|
||||
@derive Jason.Encoder
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:type, :string)
|
||||
field(:content, ObjectValidators.SafeText)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:published, ObjectValidators.DateTime)
|
||||
field(:emoji, :map, default: %{})
|
||||
|
||||
embeds_one(:attachment, AttachmentValidator)
|
||||
end
|
||||
|
||||
def cast_and_apply(data) do
|
||||
data
|
||||
|> cast_data
|
||||
|> apply_action(:insert)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data()
|
||||
|> validate_data()
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> changeset(data)
|
||||
end
|
||||
|
||||
def fix(data) do
|
||||
data
|
||||
|> fix_emoji()
|
||||
|> fix_attachment()
|
||||
|> Map.put_new("actor", data["attributedTo"])
|
||||
end
|
||||
|
||||
# Throws everything but the first one away
|
||||
def fix_attachment(%{"attachment" => [attachment | _]} = data) do
|
||||
data
|
||||
|> Map.put("attachment", attachment)
|
||||
end
|
||||
|
||||
def fix_attachment(data), do: data
|
||||
|
||||
def changeset(struct, data) do
|
||||
data = fix(data)
|
||||
|
||||
struct
|
||||
|> cast(data, List.delete(__schema__(:fields), :attachment))
|
||||
|> cast_embed(:attachment)
|
||||
end
|
||||
|
||||
def validate_data(data_cng) do
|
||||
data_cng
|
||||
|> validate_inclusion(:type, ["ChatMessage"])
|
||||
|> validate_required([:id, :actor, :to, :type, :published])
|
||||
|> validate_content_or_attachment()
|
||||
|> validate_length(:to, is: 1)
|
||||
|> validate_length(:content, max: Pleroma.Config.get([:instance, :remote_limit]))
|
||||
|> validate_local_concern()
|
||||
end
|
||||
|
||||
def validate_content_or_attachment(cng) do
|
||||
attachment = get_field(cng, :attachment)
|
||||
|
||||
if attachment do
|
||||
cng
|
||||
else
|
||||
cng
|
||||
|> validate_required([:content])
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates the following
|
||||
- If both users are in our system
|
||||
- If at least one of the users in this ChatMessage is a local user
|
||||
- If the recipient is not blocking the actor
|
||||
"""
|
||||
def validate_local_concern(cng) do
|
||||
with actor_ap <- get_field(cng, :actor),
|
||||
{_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
|
||||
{_, %User{} = recipient} <-
|
||||
{:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
|
||||
{_, false} <- {:blocking_actor?, User.blocks?(recipient, actor)},
|
||||
{_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
|
||||
cng
|
||||
else
|
||||
{:blocking_actor?, true} ->
|
||||
cng
|
||||
|> add_error(:actor, "actor is blocked by recipient")
|
||||
|
||||
{:local?, false} ->
|
||||
cng
|
||||
|> add_error(:actor, "actor and recipient are both remote")
|
||||
|
||||
{:find_actor, _} ->
|
||||
cng
|
||||
|> add_error(:actor, "can't find user")
|
||||
|
||||
{:find_recipient, _} ->
|
||||
cng
|
||||
|> add_error(:to, "can't find user")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
# NOTES
|
||||
# - Can probably be a generic create validator
|
||||
# - doesn't embed, will only get the object id
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
|
||||
use Ecto.Schema
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
alias Pleroma.Object
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:type, :string)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
end
|
||||
|
||||
def cast_and_apply(data) do
|
||||
data
|
||||
|> cast_data
|
||||
|> apply_action(:insert)
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
cast(%__MODULE__{}, data, __schema__(:fields))
|
||||
end
|
||||
|
||||
def cast_and_validate(data, meta \\ []) do
|
||||
cast_data(data)
|
||||
|> validate_data(meta)
|
||||
end
|
||||
|
||||
def validate_data(cng, meta \\ []) do
|
||||
cng
|
||||
|> validate_required([:id, :actor, :to, :type, :object])
|
||||
|> validate_inclusion(:type, ["Create"])
|
||||
|> validate_actor_presence()
|
||||
|> validate_recipients_match(meta)
|
||||
|> validate_actors_match(meta)
|
||||
|> validate_object_nonexistence()
|
||||
end
|
||||
|
||||
def validate_object_nonexistence(cng) do
|
||||
cng
|
||||
|> validate_change(:object, fn :object, object_id ->
|
||||
if Object.get_cached_by_ap_id(object_id) do
|
||||
[{:object, "The object to create already exists"}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def validate_actors_match(cng, meta) do
|
||||
object_actor = meta[:object_data]["actor"]
|
||||
|
||||
cng
|
||||
|> validate_change(:actor, fn :actor, actor ->
|
||||
if actor == object_actor do
|
||||
[]
|
||||
else
|
||||
[{:actor, "Actor doesn't match with object actor"}]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def validate_recipients_match(cng, meta) do
|
||||
object_recipients = meta[:object_data]["to"] || []
|
||||
|
||||
cng
|
||||
|> validate_change(:to, fn :to, recipients ->
|
||||
activity_set = MapSet.new(recipients)
|
||||
object_set = MapSet.new(object_recipients)
|
||||
|
||||
if MapSet.equal?(activity_set, object_set) do
|
||||
[]
|
||||
else
|
||||
[{:to, "Recipients don't match with object recipients"}]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
@ -5,16 +5,16 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:type, :string)
|
||||
field(:to, {:array, :string})
|
||||
field(:cc, {:array, :string})
|
||||
|
|
@ -6,8 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|
|||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
|
@ -15,13 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:to, Types.Recipients, default: [])
|
||||
field(:cc, Types.Recipients, default: [])
|
||||
field(:deleted_activity_id, Types.ObjectID)
|
||||
field(:object, Types.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:deleted_activity_id, ObjectValidators.ObjectID)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
|
|
@ -46,12 +46,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|
|||
Answer
|
||||
Article
|
||||
Audio
|
||||
ChatMessage
|
||||
Event
|
||||
Note
|
||||
Page
|
||||
Question
|
||||
Video
|
||||
Tombstone
|
||||
Video
|
||||
}
|
||||
def validate_data(cng) do
|
||||
cng
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
|
@ -14,10 +14,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, Types.ObjectID)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:context, :string)
|
||||
field(:content, :string)
|
||||
field(:to, {:array, :string}, default: [])
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
import Ecto.Changeset
|
||||
|
|
@ -15,13 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, Types.ObjectID)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:context, :string)
|
||||
field(:to, Types.Recipients, default: [])
|
||||
field(:cc, Types.Recipients, default: [])
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
|
|
@ -67,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
|||
|
||||
with {[], []} <- {to, cc},
|
||||
%Object{data: %{"actor" => actor}} <- Object.get_cached_by_ap_id(object),
|
||||
{:ok, actor} <- Types.ObjectID.cast(actor) do
|
||||
{:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
|
||||
cng
|
||||
|> put_change(:to, [actor])
|
||||
else
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:to, {:array, :string}, default: [])
|
||||
field(:cc, {:array, :string}, default: [])
|
||||
field(:bto, {:array, :string}, default: [])
|
||||
|
|
@ -22,10 +22,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
|
|||
field(:type, :string)
|
||||
field(:content, :string)
|
||||
field(:context, :string)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:attributedTo, Types.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:attributedTo, ObjectValidators.ObjectID)
|
||||
field(:summary, :string)
|
||||
field(:published, Types.DateTime)
|
||||
field(:published, ObjectValidators.DateTime)
|
||||
# TODO: Write type
|
||||
field(:emoji, :map, default: %{})
|
||||
field(:sensitive, :boolean, default: false)
|
||||
|
|
@ -35,13 +35,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
|
|||
field(:like_count, :integer, default: 0)
|
||||
field(:announcement_count, :integer, default: 0)
|
||||
field(:inRepyTo, :string)
|
||||
field(:uri, Types.Uri)
|
||||
field(:uri, ObjectValidators.Uri)
|
||||
|
||||
field(:likes, {:array, :string}, default: [])
|
||||
field(:announcements, {:array, :string}, default: [])
|
||||
|
||||
# see if needed
|
||||
field(:conversation, :string)
|
||||
field(:context_id, :string)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients do
|
||||
use Ecto.Type
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
|
||||
|
||||
def type, do: {:array, ObjectID}
|
||||
|
||||
def cast(object) when is_binary(object) do
|
||||
cast([object])
|
||||
end
|
||||
|
||||
def cast(data) when is_list(data) do
|
||||
data
|
||||
|> Enum.reduce({:ok, []}, fn element, acc ->
|
||||
case {acc, ObjectID.cast(element)} do
|
||||
{:error, _} -> :error
|
||||
{_, :error} -> :error
|
||||
{{:ok, list}, {:ok, id}} -> {:ok, [id | list]}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def cast(_) do
|
||||
:error
|
||||
end
|
||||
|
||||
def dump(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def load(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
end
|
||||
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
|
|||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
|
@ -14,10 +14,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, Types.ObjectID)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, {:array, :string}, default: [])
|
||||
field(:cc, {:array, :string}, default: [])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
# In this case, we save the full object in this activity instead of just a
|
||||
# reference, so we can always see what was actually changed by this.
|
||||
field(:object, :map)
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> cast(data, __schema__(:fields))
|
||||
end
|
||||
|
||||
def validate_data(cng) do
|
||||
cng
|
||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||
|> validate_inclusion(:type, ["Update"])
|
||||
|> validate_actor_presence()
|
||||
|> validate_updating_rights()
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data
|
||||
|> validate_data
|
||||
end
|
||||
|
||||
# For now we only support updating users, and here the rule is easy:
|
||||
# object id == actor id
|
||||
def validate_updating_rights(cng) do
|
||||
with actor = get_field(cng, :actor),
|
||||
object = get_field(cng, :object),
|
||||
{:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
|
||||
true <- actor == object_id do
|
||||
cng
|
||||
else
|
||||
_e ->
|
||||
cng
|
||||
|> add_error(:object, "Can't be updated by this actor")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:type, :string)
|
||||
field(:href, ObjectValidators.Uri)
|
||||
field(:mediaType, :string)
|
||||
end
|
||||
|
||||
def changeset(struct, data) do
|
||||
struct
|
||||
|> cast(data, __schema__(:fields))
|
||||
|> validate_required([:type, :href, :mediaType])
|
||||
end
|
||||
end
|
||||
|
|
@ -17,6 +17,10 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
|||
{:ok, Activity.t() | Object.t(), keyword()} | {:error, any()}
|
||||
def common_pipeline(object, meta) do
|
||||
case Repo.transaction(fn -> do_common_pipeline(object, meta) end) do
|
||||
{:ok, {:ok, activity, meta}} ->
|
||||
SideEffects.handle_after_transaction(meta)
|
||||
{:ok, activity, meta}
|
||||
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
|
|
|
|||
|
|
@ -6,15 +6,40 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
collection, and so on.
|
||||
"""
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Chat
|
||||
alias Pleroma.Chat.MessageReference
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.Push
|
||||
alias Pleroma.Web.Streamer
|
||||
|
||||
def handle(object, meta \\ [])
|
||||
|
||||
# Tasks this handles:
|
||||
# - Update the user
|
||||
#
|
||||
# For a local user, we also get a changeset with the full information, so we
|
||||
# can update non-federating, non-activitypub settings as well.
|
||||
def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
|
||||
if changeset = Keyword.get(meta, :user_update_changeset) do
|
||||
changeset
|
||||
|> User.update_and_set_cache()
|
||||
else
|
||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(updated_object)
|
||||
|
||||
User.get_by_ap_id(updated_object["id"])
|
||||
|> User.remote_user_changeset(new_user_data)
|
||||
|> User.update_and_set_cache()
|
||||
end
|
||||
|
||||
{:ok, object, meta}
|
||||
end
|
||||
|
||||
# Tasks this handles:
|
||||
# - Add like to object
|
||||
# - Set up notification
|
||||
|
|
@ -27,17 +52,38 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
{:ok, object, meta}
|
||||
end
|
||||
|
||||
# Tasks this handles
|
||||
# - Actually create object
|
||||
# - Rollback if we couldn't create it
|
||||
# - Set up notifications
|
||||
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||
with {:ok, _object, meta} <- handle_object_creation(meta[:object_data], meta) do
|
||||
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
|
||||
|
||||
meta =
|
||||
meta
|
||||
|> add_notifications(notifications)
|
||||
|
||||
{:ok, activity, meta}
|
||||
else
|
||||
e -> Repo.rollback(e)
|
||||
end
|
||||
end
|
||||
|
||||
# Tasks this handles:
|
||||
# - Add announce to object
|
||||
# - Set up notification
|
||||
# - Stream out the announce
|
||||
def handle(%{data: %{"type" => "Announce"}} = object, meta) do
|
||||
announced_object = Object.get_by_ap_id(object.data["object"])
|
||||
user = User.get_cached_by_ap_id(object.data["actor"])
|
||||
|
||||
Utils.add_announce_to_object(object, announced_object)
|
||||
|
||||
Notification.create_notifications(object)
|
||||
ActivityPub.stream_out(object)
|
||||
if !User.is_internal_user?(user) do
|
||||
Notification.create_notifications(object)
|
||||
ActivityPub.stream_out(object)
|
||||
end
|
||||
|
||||
{:ok, object, meta}
|
||||
end
|
||||
|
|
@ -85,6 +131,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
Object.decrease_replies_count(in_reply_to)
|
||||
end
|
||||
|
||||
MessageReference.delete_for_object(deleted_object)
|
||||
|
||||
ActivityPub.stream_out(object)
|
||||
ActivityPub.stream_out_participations(deleted_object, user)
|
||||
:ok
|
||||
|
|
@ -109,6 +157,39 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
{:ok, object, meta}
|
||||
end
|
||||
|
||||
def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
|
||||
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||
actor = User.get_cached_by_ap_id(object.data["actor"])
|
||||
recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
|
||||
|
||||
streamables =
|
||||
[[actor, recipient], [recipient, actor]]
|
||||
|> Enum.map(fn [user, other_user] ->
|
||||
if user.local do
|
||||
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
|
||||
{:ok, cm_ref} = MessageReference.create(chat, object, user.ap_id != actor.ap_id)
|
||||
|
||||
{
|
||||
["user", "user:pleroma_chat"],
|
||||
{user, %{cm_ref | chat: chat, object: object}}
|
||||
}
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
meta =
|
||||
meta
|
||||
|> add_streamables(streamables)
|
||||
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
# Nothing to do
|
||||
def handle_object_creation(object) do
|
||||
{:ok, object}
|
||||
end
|
||||
|
||||
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
|
||||
with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
|
||||
{:ok, _} <- Utils.remove_like_from_object(object, liked_object),
|
||||
|
|
@ -145,4 +226,43 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
end
|
||||
|
||||
def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
|
||||
|
||||
defp send_notifications(meta) do
|
||||
Keyword.get(meta, :notifications, [])
|
||||
|> Enum.each(fn notification ->
|
||||
Streamer.stream(["user", "user:notification"], notification)
|
||||
Push.send(notification)
|
||||
end)
|
||||
|
||||
meta
|
||||
end
|
||||
|
||||
defp send_streamables(meta) do
|
||||
Keyword.get(meta, :streamables, [])
|
||||
|> Enum.each(fn {topics, items} ->
|
||||
Streamer.stream(topics, items)
|
||||
end)
|
||||
|
||||
meta
|
||||
end
|
||||
|
||||
defp add_streamables(meta, streamables) do
|
||||
existing = Keyword.get(meta, :streamables, [])
|
||||
|
||||
meta
|
||||
|> Keyword.put(:streamables, streamables ++ existing)
|
||||
end
|
||||
|
||||
defp add_notifications(meta, notifications) do
|
||||
existing = Keyword.get(meta, :notifications, [])
|
||||
|
||||
meta
|
||||
|> Keyword.put(:notifications, notifications ++ existing)
|
||||
end
|
||||
|
||||
def handle_after_transaction(meta) do
|
||||
meta
|
||||
|> send_notifications()
|
||||
|> send_streamables()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
"""
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.EarmarkRenderer
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.Repo
|
||||
|
|
@ -16,7 +19,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
|
@ -170,8 +172,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
object
|
||||
|> Map.put("inReplyTo", replied_object.data["id"])
|
||||
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|
||||
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|
||||
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
||||
|> Map.drop(["conversation"])
|
||||
else
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
|
||||
|
|
@ -205,13 +207,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
object
|
||||
|> Map.put("context", context)
|
||||
|> 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)
|
||||
|> Map.drop(["conversation"])
|
||||
end
|
||||
|
||||
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
|
||||
|
|
@ -226,9 +222,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
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"]
|
||||
is_map(url) && MIME.valid?(url["mediaType"]) -> url["mediaType"]
|
||||
MIME.valid?(data["mediaType"]) -> data["mediaType"]
|
||||
MIME.valid?(data["mimeType"]) -> data["mimeType"]
|
||||
true -> nil
|
||||
end
|
||||
|
||||
|
|
@ -241,13 +237,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
attachment_url =
|
||||
%{"href" => href}
|
||||
|> add_if_present("mediaType", media_type)
|
||||
|> add_if_present("type", Map.get(url || %{}, "type"))
|
||||
|> Maps.put_if_present("mediaType", media_type)
|
||||
|> Maps.put_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"])
|
||||
|> Maps.put_if_present("mediaType", media_type)
|
||||
|> Maps.put_if_present("type", data["type"])
|
||||
|> Maps.put_if_present("name", data["name"])
|
||||
end)
|
||||
|
||||
Map.put(object, "attachment", attachments)
|
||||
|
|
@ -462,7 +458,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
to: data["to"],
|
||||
object: object,
|
||||
actor: user,
|
||||
context: object["conversation"],
|
||||
context: object["context"],
|
||||
local: false,
|
||||
published: data["published"],
|
||||
additional:
|
||||
|
|
@ -532,7 +528,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
|
||||
{:ok, %User{} = follower} <-
|
||||
User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
||||
{:ok, activity} <-
|
||||
ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do
|
||||
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
|
||||
{_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
|
||||
{_, false} <- {:user_locked, User.locked?(followed)},
|
||||
|
|
@ -575,6 +572,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
:noop
|
||||
end
|
||||
|
||||
ActivityPub.notify_and_stream(activity)
|
||||
{:ok, activity}
|
||||
else
|
||||
_e ->
|
||||
|
|
@ -595,6 +593,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
User.update_follower_count(followed)
|
||||
User.update_following_count(follower)
|
||||
|
||||
Notification.update_notification_type(followed, follow_activity)
|
||||
|
||||
ActivityPub.accept(%{
|
||||
to: follow_activity.data["to"],
|
||||
type: "Accept",
|
||||
|
|
@ -662,6 +662,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> handle_incoming(options)
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Create", "object" => %{"type" => "ChatMessage"}} = data,
|
||||
_options
|
||||
) do
|
||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(%{"type" => type} = data, _options)
|
||||
when type in ["Like", "EmojiReact", "Announce"] do
|
||||
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||
|
|
@ -674,35 +684,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
|
||||
data,
|
||||
%{"type" => "Update"} = data,
|
||||
_options
|
||||
)
|
||||
when object_type in [
|
||||
"Person",
|
||||
"Application",
|
||||
"Service",
|
||||
"Organization"
|
||||
] do
|
||||
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
|
||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||
|
||||
actor
|
||||
|> User.remote_user_changeset(new_user_data)
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
ActivityPub.update(%{
|
||||
local: false,
|
||||
to: data["to"] || [],
|
||||
cc: data["cc"] || [],
|
||||
object: object,
|
||||
actor: actor_id,
|
||||
activity_id: data["id"]
|
||||
})
|
||||
else
|
||||
e ->
|
||||
Logger.error(e)
|
||||
:error
|
||||
) do
|
||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -715,7 +702,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
else
|
||||
{:error, {:validate_object, _}} = e ->
|
||||
# Check if we have a create activity for this
|
||||
with {:ok, object_id} <- Types.ObjectID.cast(data["object"]),
|
||||
with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
|
||||
%Activity{data: %{"actor" => actor}} <-
|
||||
Activity.create_by_object_ap_id(object_id) |> Repo.one(),
|
||||
# We have one, insert a tombstone and retry
|
||||
|
|
@ -1113,6 +1100,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
Map.put(object, "attributedTo", attributed_to)
|
||||
end
|
||||
|
||||
# TODO: Revisit this
|
||||
def prepare_attachments(%{"type" => "ChatMessage"} = object), do: object
|
||||
|
||||
def prepare_attachments(object) do
|
||||
attachments =
|
||||
object
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
alias Ecto.UUID
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
|
|
@ -244,7 +245,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
Inserts a full object if it is contained in an activity.
|
||||
"""
|
||||
def insert_full_object(%{"object" => %{"type" => type} = object_data} = map)
|
||||
when is_map(object_data) and type in @supported_object_types do
|
||||
when type in @supported_object_types do
|
||||
with {:ok, object} <- Object.create(object_data) do
|
||||
map = Map.put(map, "object", object.data["id"])
|
||||
|
||||
|
|
@ -307,7 +308,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"cc" => cc,
|
||||
"context" => object.data["context"]
|
||||
}
|
||||
|> maybe_put("id", activity_id)
|
||||
|> Maps.put_if_present("id", activity_id)
|
||||
end
|
||||
|
||||
def make_emoji_reaction_data(user, object, emoji, activity_id) do
|
||||
|
|
@ -477,7 +478,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"object" => followed_id,
|
||||
"state" => "pending"
|
||||
}
|
||||
|> maybe_put("id", activity_id)
|
||||
|> Maps.put_if_present("id", activity_id)
|
||||
end
|
||||
|
||||
def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
|
||||
|
|
@ -546,7 +547,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"cc" => [],
|
||||
"context" => object.data["context"]
|
||||
}
|
||||
|> maybe_put("id", activity_id)
|
||||
|> Maps.put_if_present("id", activity_id)
|
||||
end
|
||||
|
||||
def make_announce_data(
|
||||
|
|
@ -563,7 +564,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"cc" => [Pleroma.Constants.as_public()],
|
||||
"context" => object.data["context"]
|
||||
}
|
||||
|> maybe_put("id", activity_id)
|
||||
|> Maps.put_if_present("id", activity_id)
|
||||
end
|
||||
|
||||
def make_undo_data(
|
||||
|
|
@ -582,7 +583,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"cc" => [Pleroma.Constants.as_public()],
|
||||
"context" => context
|
||||
}
|
||||
|> maybe_put("id", activity_id)
|
||||
|> Maps.put_if_present("id", activity_id)
|
||||
end
|
||||
|
||||
@spec add_announce_to_object(Activity.t(), Object.t()) ::
|
||||
|
|
@ -627,7 +628,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"to" => [followed.ap_id],
|
||||
"object" => follow_activity.data
|
||||
}
|
||||
|> maybe_put("id", activity_id)
|
||||
|> Maps.put_if_present("id", activity_id)
|
||||
end
|
||||
|
||||
#### Block-related helpers
|
||||
|
|
@ -650,7 +651,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"to" => [blocked.ap_id],
|
||||
"object" => blocked.ap_id
|
||||
}
|
||||
|> maybe_put("id", activity_id)
|
||||
|> Maps.put_if_present("id", activity_id)
|
||||
end
|
||||
|
||||
#### Create-related helpers
|
||||
|
|
@ -740,12 +741,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
def get_reports(params, page, page_size) do
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", "Flag")
|
||||
|> Map.put("skip_preload", true)
|
||||
|> Map.put("preload_report_notes", true)
|
||||
|> Map.put("total", true)
|
||||
|> Map.put("limit", page_size)
|
||||
|> Map.put("offset", (page - 1) * page_size)
|
||||
|> Map.put(:type, "Flag")
|
||||
|> Map.put(:skip_preload, true)
|
||||
|> Map.put(:preload_report_notes, true)
|
||||
|> Map.put(:total, true)
|
||||
|> Map.put(:limit, page_size)
|
||||
|> Map.put(:offset, (page - 1) * page_size)
|
||||
|
||||
ActivityPub.fetch_activities([], params, :offset)
|
||||
end
|
||||
|
|
@ -870,7 +871,4 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
|> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data))
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def maybe_put(map, _key, nil), do: map
|
||||
def maybe_put(map, key, value), do: Map.put(map, key, value)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -213,34 +213,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("activity_collection_page.json", %{activities: activities, iri: iri}) do
|
||||
# this is sorted chronologically, so first activity is the newest (max)
|
||||
{max_id, min_id, collection} =
|
||||
if length(activities) > 0 do
|
||||
{
|
||||
Enum.at(activities, 0).id,
|
||||
Enum.at(Enum.reverse(activities), 0).id,
|
||||
Enum.map(activities, fn act ->
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
|
||||
data
|
||||
end)
|
||||
}
|
||||
else
|
||||
{
|
||||
0,
|
||||
0,
|
||||
[]
|
||||
}
|
||||
end
|
||||
def render("activity_collection_page.json", %{
|
||||
activities: activities,
|
||||
iri: iri,
|
||||
pagination: pagination
|
||||
}) do
|
||||
collection =
|
||||
Enum.map(activities, fn activity ->
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
data
|
||||
end)
|
||||
|
||||
%{
|
||||
"id" => "#{iri}?max_id=#{max_id}&page=true",
|
||||
"type" => "OrderedCollectionPage",
|
||||
"partOf" => iri,
|
||||
"orderedItems" => collection,
|
||||
"next" => "#{iri}?max_id=#{min_id}&page=true"
|
||||
"orderedItems" => collection
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|> Map.merge(pagination)
|
||||
end
|
||||
|
||||
defp maybe_put_total_items(map, false, _total), do: map
|
||||
|
|
|
|||
|
|
@ -7,38 +7,24 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.MFA
|
||||
alias Pleroma.ModerationLog
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.ReportNote
|
||||
alias Pleroma.Stats
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.AdminAPI
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.AdminAPI.ConfigView
|
||||
alias Pleroma.Web.AdminAPI.ModerationLogView
|
||||
alias Pleroma.Web.AdminAPI.Report
|
||||
alias Pleroma.Web.AdminAPI.ReportView
|
||||
alias Pleroma.Web.AdminAPI.Search
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MastodonAPI
|
||||
alias Pleroma.Web.MastodonAPI.AppView
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.Router
|
||||
|
||||
require Logger
|
||||
|
||||
@descriptions Pleroma.Docs.JSON.compile()
|
||||
@users_page_size 50
|
||||
|
||||
plug(
|
||||
|
|
@ -69,30 +55,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
]
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:invites"], admin: true}
|
||||
when action in [:create_invite_token, :revoke_invite, :email_invite]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:follows"], admin: true}
|
||||
when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:reports"], admin: true}
|
||||
when action in [:list_reports, :report_show]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:reports"], admin: true}
|
||||
when action in [:reports_update, :report_notes_create, :report_notes_delete]
|
||||
when action in [:user_follow, :user_unfollow]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
|
@ -105,11 +71,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
OAuthScopesPlug,
|
||||
%{scopes: ["read"], admin: true}
|
||||
when action in [
|
||||
:config_show,
|
||||
:list_log,
|
||||
:stats,
|
||||
:relay_list,
|
||||
:config_descriptions,
|
||||
:need_reboot
|
||||
]
|
||||
)
|
||||
|
|
@ -119,13 +82,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
%{scopes: ["write"], admin: true}
|
||||
when action in [
|
||||
:restart,
|
||||
:config_update,
|
||||
:resend_confirmation_email,
|
||||
:confirm_email,
|
||||
:oauth_app_create,
|
||||
:oauth_app_list,
|
||||
:oauth_app_update,
|
||||
:oauth_app_delete,
|
||||
:reload_emoji
|
||||
]
|
||||
)
|
||||
|
|
@ -153,8 +111,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
action: "delete"
|
||||
})
|
||||
|
||||
conn
|
||||
|> json(nicknames)
|
||||
json(conn, nicknames)
|
||||
end
|
||||
|
||||
def user_follow(%{assigns: %{user: admin}} = conn, %{
|
||||
|
|
@ -173,8 +130,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
})
|
||||
end
|
||||
|
||||
conn
|
||||
|> json("ok")
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def user_unfollow(%{assigns: %{user: admin}} = conn, %{
|
||||
|
|
@ -193,8 +149,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
})
|
||||
end
|
||||
|
||||
conn
|
||||
|> json("ok")
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
|
||||
|
|
@ -233,8 +188,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
action: "create"
|
||||
})
|
||||
|
||||
conn
|
||||
|> json(res)
|
||||
json(conn, res)
|
||||
|
||||
{:error, id, changeset, _} ->
|
||||
res =
|
||||
|
|
@ -268,10 +222,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
activities =
|
||||
ActivityPub.fetch_statuses(nil, %{
|
||||
"instance" => instance,
|
||||
"limit" => page_size,
|
||||
"offset" => (page - 1) * page_size,
|
||||
"exclude_reblogs" => !with_reblogs && "true"
|
||||
instance: instance,
|
||||
limit: page_size,
|
||||
offset: (page - 1) * page_size,
|
||||
exclude_reblogs: not with_reblogs
|
||||
})
|
||||
|
||||
conn
|
||||
|
|
@ -288,13 +242,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
activities =
|
||||
ActivityPub.fetch_user_activities(user, nil, %{
|
||||
"limit" => page_size,
|
||||
"godmode" => godmode,
|
||||
"exclude_reblogs" => !with_reblogs && "true"
|
||||
limit: page_size,
|
||||
godmode: godmode,
|
||||
exclude_reblogs: not with_reblogs
|
||||
})
|
||||
|
||||
conn
|
||||
|> put_view(MastodonAPI.StatusView)
|
||||
|> put_view(AdminAPI.StatusView)
|
||||
|> render("index.json", %{activities: activities, as: :activity})
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
|
|
@ -405,8 +359,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
filters
|
||||
|> String.split(",")
|
||||
|> Enum.filter(&Enum.member?(@filters, &1))
|
||||
|> Enum.map(&String.to_atom(&1))
|
||||
|> Enum.into(%{}, &{&1, true})
|
||||
|> Enum.map(&String.to_atom/1)
|
||||
|> Map.new(&{&1, true})
|
||||
end
|
||||
|
||||
def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
|
||||
|
|
@ -531,113 +485,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
render_error(conn, :forbidden, "You can't revoke your own admin status.")
|
||||
end
|
||||
|
||||
def relay_list(conn, _params) do
|
||||
with {:ok, list} <- Relay.list() do
|
||||
json(conn, %{relays: list})
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(500)
|
||||
end
|
||||
end
|
||||
|
||||
def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
|
||||
with {:ok, _message} <- Relay.follow(target) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "relay_follow",
|
||||
actor: admin,
|
||||
target: target
|
||||
})
|
||||
|
||||
json(conn, target)
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(target)
|
||||
end
|
||||
end
|
||||
|
||||
def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
|
||||
with {:ok, _message} <- Relay.unfollow(target) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "relay_unfollow",
|
||||
actor: admin,
|
||||
target: target
|
||||
})
|
||||
|
||||
json(conn, target)
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(target)
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Sends registration invite via email"
|
||||
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
|
||||
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(
|
||||
user,
|
||||
invite_token,
|
||||
email,
|
||||
params["name"]
|
||||
),
|
||||
{:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
{:registrations_open, _} ->
|
||||
{:error, "To send invites you need to set the `registrations_open` option to false."}
|
||||
|
||||
{:invites_enabled, _} ->
|
||||
{:error, "To send invites you need to set the `invites_enabled` option to true."}
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Create an account registration invite token"
|
||||
def create_invite_token(conn, params) do
|
||||
opts = %{}
|
||||
|
||||
opts =
|
||||
if params["max_use"],
|
||||
do: Map.put(opts, :max_use, params["max_use"]),
|
||||
else: opts
|
||||
|
||||
opts =
|
||||
if params["expires_at"],
|
||||
do: Map.put(opts, :expires_at, params["expires_at"]),
|
||||
else: opts
|
||||
|
||||
{:ok, invite} = UserInviteToken.create_invite(opts)
|
||||
|
||||
json(conn, AccountView.render("invite.json", %{invite: invite}))
|
||||
end
|
||||
|
||||
@doc "Get list of created invites"
|
||||
def invites(conn, _params) do
|
||||
invites = UserInviteToken.list_invites()
|
||||
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("invites.json", %{invites: invites})
|
||||
end
|
||||
|
||||
@doc "Revokes invite by token"
|
||||
def revoke_invite(conn, %{"token" => token}) do
|
||||
with {:ok, invite} <- UserInviteToken.find_by_token(token),
|
||||
{:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("invite.json", %{invite: updated_invite})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Get a password reset token (base64 string) for given nickname"
|
||||
def get_password_reset(conn, %{"nickname" => nickname}) do
|
||||
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)
|
||||
|
|
@ -693,7 +540,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
%{assigns: %{user: admin}} = conn,
|
||||
%{"nickname" => nickname} = params
|
||||
) do
|
||||
with {_, user} <- {:user, User.get_cached_by_nickname(nickname)},
|
||||
with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
|
||||
{:ok, _user} <-
|
||||
User.update_as_admin(user, params) do
|
||||
ModerationLog.insert_log(%{
|
||||
|
|
@ -715,90 +562,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
json(conn, %{status: "success"})
|
||||
else
|
||||
{:error, changeset} ->
|
||||
{_, {error, _}} = Enum.at(changeset.errors, 0)
|
||||
json(conn, %{error: "New password #{error}."})
|
||||
errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
|
||||
|
||||
{:errors, errors}
|
||||
|
||||
_ ->
|
||||
json(conn, %{error: "Unable to change password."})
|
||||
end
|
||||
end
|
||||
|
||||
def list_reports(conn, params) do
|
||||
{page, page_size} = page_params(params)
|
||||
|
||||
reports = Utils.get_reports(params, page, page_size)
|
||||
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("index.json", %{reports: reports})
|
||||
end
|
||||
|
||||
def report_show(conn, %{"id" => id}) do
|
||||
with %Activity{} = report <- Activity.get_by_id(id) do
|
||||
conn
|
||||
|> put_view(ReportView)
|
||||
|> render("show.json", Report.extract_report_info(report))
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
|
||||
result =
|
||||
reports
|
||||
|> Enum.map(fn report ->
|
||||
with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_update",
|
||||
actor: admin,
|
||||
subject: activity
|
||||
})
|
||||
|
||||
activity
|
||||
else
|
||||
{:error, message} -> %{id: report["id"], error: message}
|
||||
end
|
||||
end)
|
||||
|
||||
case Enum.any?(result, &Map.has_key?(&1, :error)) do
|
||||
true -> json_response(conn, :bad_request, result)
|
||||
false -> json_response(conn, :no_content, "")
|
||||
end
|
||||
end
|
||||
|
||||
def report_notes_create(%{assigns: %{user: user}} = conn, %{
|
||||
"id" => report_id,
|
||||
"content" => content
|
||||
}) do
|
||||
with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_note",
|
||||
actor: user,
|
||||
subject: Activity.get_by_id(report_id),
|
||||
text: content
|
||||
})
|
||||
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
_ -> json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
def report_notes_delete(%{assigns: %{user: user}} = conn, %{
|
||||
"id" => note_id,
|
||||
"report_id" => report_id
|
||||
}) do
|
||||
with {:ok, note} <- ReportNote.destroy(note_id) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_note_delete",
|
||||
actor: user,
|
||||
subject: Activity.get_by_id(report_id),
|
||||
text: note.content
|
||||
})
|
||||
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
_ -> json_response(conn, :bad_request, "")
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -820,105 +589,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|> render("index.json", %{log: log})
|
||||
end
|
||||
|
||||
def config_descriptions(conn, _params) do
|
||||
descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
|
||||
|
||||
json(conn, descriptions)
|
||||
end
|
||||
|
||||
def config_show(conn, %{"only_db" => true}) do
|
||||
with :ok <- configurable_from_database() do
|
||||
configs = Pleroma.Repo.all(ConfigDB)
|
||||
|
||||
conn
|
||||
|> put_view(ConfigView)
|
||||
|> render("index.json", %{configs: configs})
|
||||
end
|
||||
end
|
||||
|
||||
def config_show(conn, _params) do
|
||||
with :ok <- configurable_from_database() do
|
||||
configs = ConfigDB.get_all_as_keyword()
|
||||
|
||||
merged =
|
||||
Config.Holder.default_config()
|
||||
|> ConfigDB.merge(configs)
|
||||
|> Enum.map(fn {group, values} ->
|
||||
Enum.map(values, fn {key, value} ->
|
||||
db =
|
||||
if configs[group][key] do
|
||||
ConfigDB.get_db_keys(configs[group][key], key)
|
||||
end
|
||||
|
||||
db_value = configs[group][key]
|
||||
|
||||
merged_value =
|
||||
if !is_nil(db_value) and Keyword.keyword?(db_value) and
|
||||
ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
|
||||
ConfigDB.merge_group(group, key, value, db_value)
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
setting = %{
|
||||
group: ConfigDB.convert(group),
|
||||
key: ConfigDB.convert(key),
|
||||
value: ConfigDB.convert(merged_value)
|
||||
}
|
||||
|
||||
if db, do: Map.put(setting, :db, db), else: setting
|
||||
end)
|
||||
end)
|
||||
|> List.flatten()
|
||||
|
||||
json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||
end
|
||||
end
|
||||
|
||||
def config_update(conn, %{"configs" => configs}) do
|
||||
with :ok <- configurable_from_database() do
|
||||
{_errors, results} =
|
||||
configs
|
||||
|> Enum.filter(&whitelisted_config?/1)
|
||||
|> Enum.map(fn
|
||||
%{"group" => group, "key" => key, "delete" => true} = params ->
|
||||
ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]})
|
||||
|
||||
%{"group" => group, "key" => key, "value" => value} ->
|
||||
ConfigDB.update_or_create(%{group: group, key: key, value: value})
|
||||
end)
|
||||
|> Enum.split_with(fn result -> elem(result, 0) == :error end)
|
||||
|
||||
{deleted, updated} =
|
||||
results
|
||||
|> Enum.map(fn {:ok, config} ->
|
||||
Map.put(config, :db, ConfigDB.get_db_keys(config))
|
||||
end)
|
||||
|> Enum.split_with(fn config ->
|
||||
Ecto.get_meta(config, :state) == :deleted
|
||||
end)
|
||||
|
||||
Config.TransferTask.load_and_update_env(deleted, false)
|
||||
|
||||
if !Restarter.Pleroma.need_reboot?() do
|
||||
changed_reboot_settings? =
|
||||
(updated ++ deleted)
|
||||
|> Enum.any?(fn config ->
|
||||
group = ConfigDB.from_string(config.group)
|
||||
key = ConfigDB.from_string(config.key)
|
||||
value = ConfigDB.from_binary(config.value)
|
||||
Config.TransferTask.pleroma_need_restart?(group, key, value)
|
||||
end)
|
||||
|
||||
if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_view(ConfigView)
|
||||
|> render("index.json", %{configs: updated, need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||
end
|
||||
end
|
||||
|
||||
def restart(conn, _params) do
|
||||
with :ok <- configurable_from_database() do
|
||||
Restarter.Pleroma.restart(Config.get(:env), 50)
|
||||
|
|
@ -939,32 +609,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
defp whitelisted_config?(group, key) do
|
||||
if whitelisted_configs = Config.get(:database_config_whitelist) do
|
||||
Enum.any?(whitelisted_configs, fn
|
||||
{whitelisted_group} ->
|
||||
group == inspect(whitelisted_group)
|
||||
|
||||
{whitelisted_group, whitelisted_key} ->
|
||||
group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
|
||||
end)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
defp whitelisted_config?(%{"group" => group, "key" => key}) do
|
||||
whitelisted_config?(group, key)
|
||||
end
|
||||
|
||||
defp whitelisted_config?(%{:group => group} = config) do
|
||||
whitelisted_config?(group, config[:key])
|
||||
end
|
||||
|
||||
def reload_emoji(conn, _params) do
|
||||
Pleroma.Emoji.reload()
|
||||
|
||||
conn |> json("ok")
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
||||
|
|
@ -978,7 +626,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
action: "confirm_email"
|
||||
})
|
||||
|
||||
conn |> json("")
|
||||
json(conn, "")
|
||||
end
|
||||
|
||||
def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
||||
|
|
@ -992,91 +640,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
action: "resend_confirmation_email"
|
||||
})
|
||||
|
||||
conn |> json("")
|
||||
json(conn, "")
|
||||
end
|
||||
|
||||
def oauth_app_create(conn, params) do
|
||||
params =
|
||||
if params["name"] do
|
||||
Map.put(params, "client_name", params["name"])
|
||||
else
|
||||
params
|
||||
end
|
||||
def stats(conn, params) do
|
||||
counters = Stats.get_status_visibility_count(params["instance"])
|
||||
|
||||
result =
|
||||
case App.create(params) do
|
||||
{:ok, app} ->
|
||||
AppView.render("show.json", %{app: app, admin: true})
|
||||
|
||||
{:error, changeset} ->
|
||||
App.errors(changeset)
|
||||
end
|
||||
|
||||
json(conn, result)
|
||||
end
|
||||
|
||||
def oauth_app_update(conn, params) do
|
||||
params =
|
||||
if params["name"] do
|
||||
Map.put(params, "client_name", params["name"])
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
with {:ok, app} <- App.update(params) do
|
||||
json(conn, AppView.render("show.json", %{app: app, admin: true}))
|
||||
else
|
||||
{:error, changeset} ->
|
||||
json(conn, App.errors(changeset))
|
||||
|
||||
nil ->
|
||||
json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
def oauth_app_list(conn, params) do
|
||||
{page, page_size} = page_params(params)
|
||||
|
||||
search_params = %{
|
||||
client_name: params["name"],
|
||||
client_id: params["client_id"],
|
||||
page: page,
|
||||
page_size: page_size
|
||||
}
|
||||
|
||||
search_params =
|
||||
if Map.has_key?(params, "trusted") do
|
||||
Map.put(search_params, :trusted, params["trusted"])
|
||||
else
|
||||
search_params
|
||||
end
|
||||
|
||||
with {:ok, apps, count} <- App.search(search_params) do
|
||||
json(
|
||||
conn,
|
||||
AppView.render("index.json",
|
||||
apps: apps,
|
||||
count: count,
|
||||
page_size: page_size,
|
||||
admin: true
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def oauth_app_delete(conn, params) do
|
||||
with {:ok, _app} <- App.destroy(params["id"]) do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
_ -> json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
def stats(conn, _) do
|
||||
count = Stats.get_status_visibility_count()
|
||||
|
||||
conn
|
||||
|> json(%{"status_visibility" => count})
|
||||
json(conn, %{"status_visibility" => counters})
|
||||
end
|
||||
|
||||
defp page_params(params) do
|
||||
|
|
|
|||
152
lib/pleroma/web/admin_api/controllers/config_controller.ex
Normal file
152
lib/pleroma/web/admin_api/controllers/config_controller.ex
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.ConfigController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
|
||||
@descriptions Pleroma.Docs.JSON.compile()
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read"], admin: true}
|
||||
when action in [:show, :descriptions]
|
||||
)
|
||||
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
|
||||
|
||||
def descriptions(conn, _params) do
|
||||
descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
|
||||
|
||||
json(conn, descriptions)
|
||||
end
|
||||
|
||||
def show(conn, %{only_db: true}) do
|
||||
with :ok <- configurable_from_database() do
|
||||
configs = Pleroma.Repo.all(ConfigDB)
|
||||
|
||||
render(conn, "index.json", %{
|
||||
configs: configs,
|
||||
need_reboot: Restarter.Pleroma.need_reboot?()
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def show(conn, _params) do
|
||||
with :ok <- configurable_from_database() do
|
||||
configs = ConfigDB.get_all_as_keyword()
|
||||
|
||||
merged =
|
||||
Config.Holder.default_config()
|
||||
|> ConfigDB.merge(configs)
|
||||
|> Enum.map(fn {group, values} ->
|
||||
Enum.map(values, fn {key, value} ->
|
||||
db =
|
||||
if configs[group][key] do
|
||||
ConfigDB.get_db_keys(configs[group][key], key)
|
||||
end
|
||||
|
||||
db_value = configs[group][key]
|
||||
|
||||
merged_value =
|
||||
if not is_nil(db_value) and Keyword.keyword?(db_value) and
|
||||
ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
|
||||
ConfigDB.merge_group(group, key, value, db_value)
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
%ConfigDB{
|
||||
group: group,
|
||||
key: key,
|
||||
value: merged_value
|
||||
}
|
||||
|> Pleroma.Maps.put_if_present(:db, db)
|
||||
end)
|
||||
end)
|
||||
|> List.flatten()
|
||||
|
||||
render(conn, "index.json", %{
|
||||
configs: merged,
|
||||
need_reboot: Restarter.Pleroma.need_reboot?()
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def update(%{body_params: %{configs: configs}} = conn, _) do
|
||||
with :ok <- configurable_from_database() do
|
||||
results =
|
||||
configs
|
||||
|> Enum.filter(&whitelisted_config?/1)
|
||||
|> Enum.map(fn
|
||||
%{group: group, key: key, delete: true} = params ->
|
||||
ConfigDB.delete(%{group: group, key: key, subkeys: params[:subkeys]})
|
||||
|
||||
%{group: group, key: key, value: value} ->
|
||||
ConfigDB.update_or_create(%{group: group, key: key, value: value})
|
||||
end)
|
||||
|> Enum.reject(fn {result, _} -> result == :error end)
|
||||
|
||||
{deleted, updated} =
|
||||
results
|
||||
|> Enum.map(fn {:ok, %{key: key, value: value} = config} ->
|
||||
Map.put(config, :db, ConfigDB.get_db_keys(value, key))
|
||||
end)
|
||||
|> Enum.split_with(&(Ecto.get_meta(&1, :state) == :deleted))
|
||||
|
||||
Config.TransferTask.load_and_update_env(deleted, false)
|
||||
|
||||
if not Restarter.Pleroma.need_reboot?() do
|
||||
changed_reboot_settings? =
|
||||
(updated ++ deleted)
|
||||
|> Enum.any?(&Config.TransferTask.pleroma_need_restart?(&1.group, &1.key, &1.value))
|
||||
|
||||
if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
|
||||
end
|
||||
|
||||
render(conn, "index.json", %{
|
||||
configs: updated,
|
||||
need_reboot: Restarter.Pleroma.need_reboot?()
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
defp configurable_from_database do
|
||||
if Config.get(:configurable_from_database) do
|
||||
:ok
|
||||
else
|
||||
{:error, "To use this endpoint you need to enable configuration from database."}
|
||||
end
|
||||
end
|
||||
|
||||
defp whitelisted_config?(group, key) do
|
||||
if whitelisted_configs = Config.get(:database_config_whitelist) do
|
||||
Enum.any?(whitelisted_configs, fn
|
||||
{whitelisted_group} ->
|
||||
group == inspect(whitelisted_group)
|
||||
|
||||
{whitelisted_group, whitelisted_key} ->
|
||||
group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
|
||||
end)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
defp whitelisted_config?(%{group: group, key: key}) do
|
||||
whitelisted_config?(group, key)
|
||||
end
|
||||
|
||||
defp whitelisted_config?(%{group: group} = config) do
|
||||
whitelisted_config?(group, config[:key])
|
||||
end
|
||||
end
|
||||
|
|
@ -17,6 +17,12 @@ defmodule Pleroma.Web.AdminAPI.FallbackController do
|
|||
|> json(%{error: reason})
|
||||
end
|
||||
|
||||
def call(conn, {:errors, errors}) do
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{errors: errors})
|
||||
end
|
||||
|
||||
def call(conn, {:param_cast, _}) do
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|
|
|
|||
78
lib/pleroma/web/admin_api/controllers/invite_controller.ex
Normal file
78
lib/pleroma/web/admin_api/controllers/invite_controller.ex
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.InviteController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.UserInviteToken
|
||||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :index)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:invites"], admin: true} when action in [:create, :revoke, :email]
|
||||
)
|
||||
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InviteOperation
|
||||
|
||||
@doc "Get list of created invites"
|
||||
def index(conn, _params) do
|
||||
invites = UserInviteToken.list_invites()
|
||||
|
||||
render(conn, "index.json", invites: invites)
|
||||
end
|
||||
|
||||
@doc "Create an account registration invite token"
|
||||
def create(%{body_params: params} = conn, _) do
|
||||
{:ok, invite} = UserInviteToken.create_invite(params)
|
||||
|
||||
render(conn, "show.json", invite: invite)
|
||||
end
|
||||
|
||||
@doc "Revokes invite by token"
|
||||
def revoke(%{body_params: %{token: token}} = conn, _) do
|
||||
with {:ok, invite} <- UserInviteToken.find_by_token(token),
|
||||
{:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
|
||||
render(conn, "show.json", invite: updated_invite)
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Sends registration invite via email"
|
||||
def email(%{assigns: %{user: user}, body_params: %{email: email} = params} = conn, _) do
|
||||
with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
|
||||
{_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
|
||||
{:ok, invite_token} <- UserInviteToken.create_invite(),
|
||||
{:ok, _} <-
|
||||
user
|
||||
|> Pleroma.Emails.UserEmail.user_invitation_email(
|
||||
invite_token,
|
||||
email,
|
||||
params[:name]
|
||||
)
|
||||
|> Pleroma.Emails.Mailer.deliver() do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
{:registrations_open, _} ->
|
||||
{:error, "To send invites you need to set the `registrations_open` option to false."}
|
||||
|
||||
{:invites_enabled, _} ->
|
||||
{:error, "To send invites you need to set the `invites_enabled` option to true."}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Web.ApiSpec.Admin, as: Spec
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:media_proxy_caches"], admin: true} when action in [:index]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:media_proxy_caches"], admin: true} when action in [:purge, :delete]
|
||||
)
|
||||
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Spec.MediaProxyCacheOperation
|
||||
|
||||
def index(%{assigns: %{user: _}} = conn, params) do
|
||||
cursor =
|
||||
:banned_urls_cache
|
||||
|> :ets.table([{:traverse, {:select, Cachex.Query.create(true, :key)}}])
|
||||
|> :qlc.cursor()
|
||||
|
||||
urls =
|
||||
case params.page do
|
||||
1 ->
|
||||
:qlc.next_answers(cursor, params.page_size)
|
||||
|
||||
_ ->
|
||||
:qlc.next_answers(cursor, (params.page - 1) * params.page_size)
|
||||
:qlc.next_answers(cursor, params.page_size)
|
||||
end
|
||||
|
||||
:qlc.delete_cursor(cursor)
|
||||
|
||||
render(conn, "index.json", urls: urls)
|
||||
end
|
||||
|
||||
def delete(%{assigns: %{user: _}, body_params: %{urls: urls}} = conn, _) do
|
||||
MediaProxy.remove_from_banned_urls(urls)
|
||||
render(conn, "index.json", urls: urls)
|
||||
end
|
||||
|
||||
def purge(%{assigns: %{user: _}, body_params: %{urls: urls, ban: ban}} = conn, _) do
|
||||
MediaProxy.Invalidation.purge(urls)
|
||||
|
||||
if ban do
|
||||
MediaProxy.put_in_banned_urls(urls)
|
||||
end
|
||||
|
||||
render(conn, "index.json", urls: urls)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.OAuthAppController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Web.OAuth.App
|
||||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write"], admin: true}
|
||||
when action in [:create, :index, :update, :delete]
|
||||
)
|
||||
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.OAuthAppOperation
|
||||
|
||||
def index(conn, params) do
|
||||
search_params =
|
||||
params
|
||||
|> Map.take([:client_id, :page, :page_size, :trusted])
|
||||
|> Map.put(:client_name, params[:name])
|
||||
|
||||
with {:ok, apps, count} <- App.search(search_params) do
|
||||
render(conn, "index.json",
|
||||
apps: apps,
|
||||
count: count,
|
||||
page_size: params.page_size,
|
||||
admin: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def create(%{body_params: params} = conn, _) do
|
||||
params = Pleroma.Maps.put_if_present(params, :client_name, params[:name])
|
||||
|
||||
case App.create(params) do
|
||||
{:ok, app} ->
|
||||
render(conn, "show.json", app: app, admin: true)
|
||||
|
||||
{:error, changeset} ->
|
||||
json(conn, App.errors(changeset))
|
||||
end
|
||||
end
|
||||
|
||||
def update(%{body_params: params} = conn, %{id: id}) do
|
||||
params = Pleroma.Maps.put_if_present(params, :client_name, params[:name])
|
||||
|
||||
with {:ok, app} <- App.update(id, params) do
|
||||
render(conn, "show.json", app: app, admin: true)
|
||||
else
|
||||
{:error, changeset} ->
|
||||
json(conn, App.errors(changeset))
|
||||
|
||||
nil ->
|
||||
json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, params) do
|
||||
with {:ok, _app} <- App.destroy(params.id) do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
_ -> json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
67
lib/pleroma/web/admin_api/controllers/relay_controller.ex
Normal file
67
lib/pleroma/web/admin_api/controllers/relay_controller.ex
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.RelayController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.ModerationLog
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:follows"], admin: true}
|
||||
when action in [:follow, :unfollow]
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :index)
|
||||
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.RelayOperation
|
||||
|
||||
def index(conn, _params) do
|
||||
with {:ok, list} <- Relay.list() do
|
||||
json(conn, %{relays: list})
|
||||
end
|
||||
end
|
||||
|
||||
def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
|
||||
with {:ok, _message} <- Relay.follow(target) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "relay_follow",
|
||||
actor: admin,
|
||||
target: target
|
||||
})
|
||||
|
||||
json(conn, target)
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(target)
|
||||
end
|
||||
end
|
||||
|
||||
def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
|
||||
with {:ok, _message} <- Relay.unfollow(target) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "relay_unfollow",
|
||||
actor: admin,
|
||||
target: target
|
||||
})
|
||||
|
||||
json(conn, target)
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
107
lib/pleroma/web/admin_api/controllers/report_controller.ex
Normal file
107
lib/pleroma/web/admin_api/controllers/report_controller.ex
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.ReportController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.ModerationLog
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.ReportNote
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.AdminAPI
|
||||
alias Pleroma.Web.AdminAPI.Report
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:reports"], admin: true} when action in [:index, :show])
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:reports"], admin: true}
|
||||
when action in [:update, :notes_create, :notes_delete]
|
||||
)
|
||||
|
||||
action_fallback(AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ReportOperation
|
||||
|
||||
def index(conn, params) do
|
||||
reports = Utils.get_reports(params, params.page, params.page_size)
|
||||
|
||||
render(conn, "index.json", reports: reports)
|
||||
end
|
||||
|
||||
def show(conn, %{id: id}) do
|
||||
with %Activity{} = report <- Activity.get_by_id(id) do
|
||||
render(conn, "show.json", Report.extract_report_info(report))
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def update(%{assigns: %{user: admin}, body_params: %{reports: reports}} = conn, _) do
|
||||
result =
|
||||
Enum.map(reports, fn report ->
|
||||
case CommonAPI.update_report_state(report.id, report.state) do
|
||||
{:ok, activity} ->
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_update",
|
||||
actor: admin,
|
||||
subject: activity
|
||||
})
|
||||
|
||||
activity
|
||||
|
||||
{:error, message} ->
|
||||
%{id: report.id, error: message}
|
||||
end
|
||||
end)
|
||||
|
||||
if Enum.any?(result, &Map.has_key?(&1, :error)) do
|
||||
json_response(conn, :bad_request, result)
|
||||
else
|
||||
json_response(conn, :no_content, "")
|
||||
end
|
||||
end
|
||||
|
||||
def notes_create(%{assigns: %{user: user}, body_params: %{content: content}} = conn, %{
|
||||
id: report_id
|
||||
}) do
|
||||
with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_note",
|
||||
actor: user,
|
||||
subject: Activity.get_by_id(report_id),
|
||||
text: content
|
||||
})
|
||||
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
_ -> json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
def notes_delete(%{assigns: %{user: user}} = conn, %{
|
||||
id: note_id,
|
||||
report_id: report_id
|
||||
}) do
|
||||
with {:ok, note} <- ReportNote.destroy(note_id) do
|
||||
ModerationLog.insert_log(%{
|
||||
action: "report_note_delete",
|
||||
actor: user,
|
||||
subject: Activity.get_by_id(report_id),
|
||||
text: note.content
|
||||
})
|
||||
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
_ -> json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -29,11 +29,11 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
|
|||
def index(%{assigns: %{user: _admin}} = conn, params) do
|
||||
activities =
|
||||
ActivityPub.fetch_statuses(nil, %{
|
||||
"godmode" => params.godmode,
|
||||
"local_only" => params.local_only,
|
||||
"limit" => params.page_size,
|
||||
"offset" => (params.page - 1) * params.page_size,
|
||||
"exclude_reblogs" => not params.with_reblogs
|
||||
godmode: params.godmode,
|
||||
local_only: params.local_only,
|
||||
limit: params.page_size,
|
||||
offset: (params.page - 1) * params.page_size,
|
||||
exclude_reblogs: not params.with_reblogs
|
||||
})
|
||||
|
||||
render(conn, "index.json", activities: activities, as: :activity)
|
||||
|
|
@ -41,9 +41,7 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
|
|||
|
||||
def show(conn, %{id: id}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(id) do
|
||||
conn
|
||||
|> put_view(MastodonAPI.StatusView)
|
||||
|> render("show.json", %{activity: activity})
|
||||
render(conn, "show.json", %{activity: activity})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ defmodule Pleroma.Web.AdminAPI.Search do
|
|||
query =
|
||||
params
|
||||
|> Map.drop([:page, :page_size])
|
||||
|> Map.put(:exclude_service_users, true)
|
||||
|> Map.put(:invisible, false)
|
||||
|> User.Query.build()
|
||||
|> order_by([u], u.nickname)
|
||||
|
||||
|
|
@ -31,7 +31,6 @@ defmodule Pleroma.Web.AdminAPI.Search do
|
|||
count = Repo.aggregate(query, :count, :id)
|
||||
|
||||
results = Repo.all(paginated_query)
|
||||
|
||||
{:ok, results, count}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -76,25 +76,8 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
|
|||
"local" => user.local,
|
||||
"roles" => User.roles(user),
|
||||
"tags" => user.tags || [],
|
||||
"confirmation_pending" => user.confirmation_pending
|
||||
}
|
||||
end
|
||||
|
||||
def render("invite.json", %{invite: invite}) do
|
||||
%{
|
||||
"id" => invite.id,
|
||||
"token" => invite.token,
|
||||
"used" => invite.used,
|
||||
"expires_at" => invite.expires_at,
|
||||
"uses" => invite.uses,
|
||||
"max_use" => invite.max_use,
|
||||
"invite_type" => invite.invite_type
|
||||
}
|
||||
end
|
||||
|
||||
def render("invites.json", %{invites: invites}) do
|
||||
%{
|
||||
invites: render_many(invites, AccountView, "invite.json", as: :invite)
|
||||
"confirmation_pending" => user.confirmation_pending,
|
||||
"url" => user.uri || user.ap_id
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,23 +5,20 @@
|
|||
defmodule Pleroma.Web.AdminAPI.ConfigView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
def render("index.json", %{configs: configs} = params) do
|
||||
map = %{
|
||||
configs: render_many(configs, __MODULE__, "show.json", as: :config)
|
||||
}
|
||||
alias Pleroma.ConfigDB
|
||||
|
||||
if params[:need_reboot] do
|
||||
Map.put(map, :need_reboot, true)
|
||||
else
|
||||
map
|
||||
end
|
||||
def render("index.json", %{configs: configs} = params) do
|
||||
%{
|
||||
configs: render_many(configs, __MODULE__, "show.json", as: :config),
|
||||
need_reboot: params[:need_reboot]
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{config: config}) do
|
||||
map = %{
|
||||
key: config.key,
|
||||
group: config.group,
|
||||
value: Pleroma.ConfigDB.from_binary_with_convert(config.value)
|
||||
key: ConfigDB.to_json_types(config.key),
|
||||
group: ConfigDB.to_json_types(config.group),
|
||||
value: ConfigDB.to_json_types(config.value)
|
||||
}
|
||||
|
||||
if config.db != [] do
|
||||
|
|
|
|||
25
lib/pleroma/web/admin_api/views/invite_view.ex
Normal file
25
lib/pleroma/web/admin_api/views/invite_view.ex
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.InviteView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
def render("index.json", %{invites: invites}) do
|
||||
%{
|
||||
invites: render_many(invites, __MODULE__, "show.json", as: :invite)
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{invite: invite}) do
|
||||
%{
|
||||
"id" => invite.id,
|
||||
"token" => invite.token,
|
||||
"used" => invite.used,
|
||||
"expires_at" => invite.expires_at,
|
||||
"uses" => invite.uses,
|
||||
"max_use" => invite.max_use,
|
||||
"invite_type" => invite.invite_type
|
||||
}
|
||||
end
|
||||
end
|
||||
11
lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
Normal file
11
lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
def render("index.json", %{urls: urls}) do
|
||||
%{urls: urls}
|
||||
end
|
||||
end
|
||||
|
|
@ -18,7 +18,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
|||
%{
|
||||
reports:
|
||||
reports[:items]
|
||||
|> Enum.map(&Report.extract_report_info(&1))
|
||||
|> Enum.map(&Report.extract_report_info/1)
|
||||
|> Enum.map(&render(__MODULE__, "show.json", &1))
|
||||
|> Enum.reverse(),
|
||||
total: reports[:total]
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ defmodule Pleroma.Web.ApiSpec.Helpers do
|
|||
:string,
|
||||
"Return the newest items newer than this ID"
|
||||
),
|
||||
Operation.parameter(
|
||||
:offset,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 0},
|
||||
"Return items past this number of items"
|
||||
),
|
||||
Operation.parameter(
|
||||
:limit,
|
||||
:query,
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
|
||||
responses: %{
|
||||
200 => Operation.response("Account", "application/json", Account),
|
||||
401 => Operation.response("Error", "application/json", ApiError),
|
||||
404 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
|
|
@ -142,6 +143,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
] ++ pagination_params(),
|
||||
responses: %{
|
||||
200 => Operation.response("Statuses", "application/json", array_of_statuses()),
|
||||
401 => Operation.response("Error", "application/json", ApiError),
|
||||
404 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
142
lib/pleroma/web/api_spec/operations/admin/config_operation.ex
Normal file
142
lib/pleroma/web/api_spec/operations/admin/config_operation.ex
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
# 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.Admin.ConfigOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Config"],
|
||||
summary: "Get list of merged default settings with saved in database",
|
||||
operationId: "AdminAPI.ConfigController.show",
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:only_db,
|
||||
:query,
|
||||
%Schema{type: :boolean, default: false},
|
||||
"Get only saved in database settings"
|
||||
)
|
||||
],
|
||||
security: [%{"oAuth" => ["read"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Config", "application/json", config_response()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Config"],
|
||||
summary: "Update config settings",
|
||||
operationId: "AdminAPI.ConfigController.update",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
requestBody:
|
||||
request_body("Parameters", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
configs: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
group: %Schema{type: :string},
|
||||
key: %Schema{type: :string},
|
||||
value: any(),
|
||||
delete: %Schema{type: :boolean},
|
||||
subkeys: %Schema{type: :array, items: %Schema{type: :string}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
responses: %{
|
||||
200 => Operation.response("Config", "application/json", config_response()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def descriptions_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Config"],
|
||||
summary: "Get JSON with config descriptions.",
|
||||
operationId: "AdminAPI.ConfigController.descriptions",
|
||||
security: [%{"oAuth" => ["read"]}],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Config Descriptions", "application/json", %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
group: %Schema{type: :string},
|
||||
key: %Schema{type: :string},
|
||||
type: %Schema{oneOf: [%Schema{type: :string}, %Schema{type: :array}]},
|
||||
description: %Schema{type: :string},
|
||||
children: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
key: %Schema{type: :string},
|
||||
type: %Schema{oneOf: [%Schema{type: :string}, %Schema{type: :array}]},
|
||||
description: %Schema{type: :string},
|
||||
suggestions: %Schema{type: :array}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp any do
|
||||
%Schema{
|
||||
oneOf: [
|
||||
%Schema{type: :array},
|
||||
%Schema{type: :object},
|
||||
%Schema{type: :string},
|
||||
%Schema{type: :integer},
|
||||
%Schema{type: :boolean}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
defp config_response do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
configs: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
group: %Schema{type: :string},
|
||||
key: %Schema{type: :string},
|
||||
value: any()
|
||||
}
|
||||
}
|
||||
},
|
||||
need_reboot: %Schema{
|
||||
type: :boolean,
|
||||
description:
|
||||
"If `need_reboot` is `true`, instance must be restarted, so reboot time settings can take effect"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
148
lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
Normal file
148
lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# 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.Admin.InviteOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Invites"],
|
||||
summary: "Get a list of generated invites",
|
||||
operationId: "AdminAPI.InviteController.index",
|
||||
security: [%{"oAuth" => ["read:invites"]}],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Invites", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
invites: %Schema{type: :array, items: invite()}
|
||||
},
|
||||
example: %{
|
||||
"invites" => [
|
||||
%{
|
||||
"id" => 123,
|
||||
"token" => "kSQtDj_GNy2NZsL9AQDFIsHN5qdbguB6qRg3WHw6K1U=",
|
||||
"used" => true,
|
||||
"expires_at" => nil,
|
||||
"uses" => 0,
|
||||
"max_use" => nil,
|
||||
"invite_type" => "one_time"
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Invites"],
|
||||
summary: "Create an account registration invite token",
|
||||
operationId: "AdminAPI.InviteController.create",
|
||||
security: [%{"oAuth" => ["write:invites"]}],
|
||||
requestBody:
|
||||
request_body("Parameters", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
max_use: %Schema{type: :integer},
|
||||
expires_at: %Schema{type: :string, format: :date, example: "2020-04-20"}
|
||||
}
|
||||
}),
|
||||
responses: %{
|
||||
200 => Operation.response("Invite", "application/json", invite())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def revoke_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Invites"],
|
||||
summary: "Revoke invite by token",
|
||||
operationId: "AdminAPI.InviteController.revoke",
|
||||
security: [%{"oAuth" => ["write:invites"]}],
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:token],
|
||||
properties: %{
|
||||
token: %Schema{type: :string}
|
||||
}
|
||||
},
|
||||
required: true
|
||||
),
|
||||
responses: %{
|
||||
200 => Operation.response("Invite", "application/json", invite()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def email_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Invites"],
|
||||
summary: "Sends registration invite via email",
|
||||
operationId: "AdminAPI.InviteController.email",
|
||||
security: [%{"oAuth" => ["write:invites"]}],
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:email],
|
||||
properties: %{
|
||||
email: %Schema{type: :string, format: :email},
|
||||
name: %Schema{type: :string}
|
||||
}
|
||||
},
|
||||
required: true
|
||||
),
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp invite do
|
||||
%Schema{
|
||||
title: "Invite",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :integer},
|
||||
token: %Schema{type: :string},
|
||||
used: %Schema{type: :boolean},
|
||||
expires_at: %Schema{type: :string, format: :date, nullable: true},
|
||||
uses: %Schema{type: :integer},
|
||||
max_use: %Schema{type: :integer, nullable: true},
|
||||
invite_type: %Schema{
|
||||
type: :string,
|
||||
enum: ["one_time", "reusable", "date_limited", "reusable_date_limited"]
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"id" => 123,
|
||||
"token" => "kSQtDj_GNy2NZsL9AQDFIsHN5qdbguB6qRg3WHw6K1U=",
|
||||
"used" => true,
|
||||
"expires_at" => nil,
|
||||
"uses" => 0,
|
||||
"max_use" => nil,
|
||||
"invite_type" => "one_time"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# 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.Admin.MediaProxyCacheOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "MediaProxyCache"],
|
||||
summary: "Fetch a paginated list of all banned MediaProxy URLs in Cachex",
|
||||
operationId: "AdminAPI.MediaProxyCacheController.index",
|
||||
security: [%{"oAuth" => ["read:media_proxy_caches"]}],
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:page,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 1},
|
||||
"Page"
|
||||
),
|
||||
Operation.parameter(
|
||||
:page_size,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 50},
|
||||
"Number of statuses to return"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 => success_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "MediaProxyCache"],
|
||||
summary: "Remove a banned MediaProxy URL from Cachex",
|
||||
operationId: "AdminAPI.MediaProxyCacheController.delete",
|
||||
security: [%{"oAuth" => ["write:media_proxy_caches"]}],
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:urls],
|
||||
properties: %{
|
||||
urls: %Schema{type: :array, items: %Schema{type: :string, format: :uri}}
|
||||
}
|
||||
},
|
||||
required: true
|
||||
),
|
||||
responses: %{
|
||||
200 => success_response(),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def purge_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "MediaProxyCache"],
|
||||
summary: "Purge and optionally ban a MediaProxy URL",
|
||||
operationId: "AdminAPI.MediaProxyCacheController.purge",
|
||||
security: [%{"oAuth" => ["write:media_proxy_caches"]}],
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:urls],
|
||||
properties: %{
|
||||
urls: %Schema{type: :array, items: %Schema{type: :string, format: :uri}},
|
||||
ban: %Schema{type: :boolean, default: true}
|
||||
}
|
||||
},
|
||||
required: true
|
||||
),
|
||||
responses: %{
|
||||
200 => success_response(),
|
||||
400 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp success_response do
|
||||
Operation.response("Array of banned MediaProxy URLs in Cachex", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
urls: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
description: "MediaProxy URLs"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
215
lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
Normal file
215
lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
# 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.Admin.OAuthAppOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
summary: "List OAuth apps",
|
||||
tags: ["Admin", "oAuth Apps"],
|
||||
operationId: "AdminAPI.OAuthAppController.index",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
parameters: [
|
||||
Operation.parameter(:name, :query, %Schema{type: :string}, "App name"),
|
||||
Operation.parameter(:client_id, :query, %Schema{type: :string}, "Client ID"),
|
||||
Operation.parameter(:page, :query, %Schema{type: :integer, default: 1}, "Page"),
|
||||
Operation.parameter(
|
||||
:trusted,
|
||||
:query,
|
||||
%Schema{type: :boolean, default: false},
|
||||
"Trusted apps"
|
||||
),
|
||||
Operation.parameter(
|
||||
:page_size,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 50},
|
||||
"Number of apps to return"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("List of apps", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
apps: %Schema{type: :array, items: oauth_app()},
|
||||
count: %Schema{type: :integer},
|
||||
page_size: %Schema{type: :integer}
|
||||
},
|
||||
example: %{
|
||||
"apps" => [
|
||||
%{
|
||||
"id" => 1,
|
||||
"name" => "App name",
|
||||
"client_id" => "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
|
||||
"client_secret" => "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
|
||||
"redirect_uri" => "https://example.com/oauth-callback",
|
||||
"website" => "https://example.com",
|
||||
"trusted" => true
|
||||
}
|
||||
],
|
||||
"count" => 1,
|
||||
"page_size" => 50
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "oAuth Apps"],
|
||||
summary: "Create OAuth App",
|
||||
operationId: "AdminAPI.OAuthAppController.create",
|
||||
requestBody: request_body("Parameters", create_request()),
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("App", "application/json", oauth_app()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "oAuth Apps"],
|
||||
summary: "Update OAuth App",
|
||||
operationId: "AdminAPI.OAuthAppController.update",
|
||||
parameters: [id_param()],
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
requestBody: request_body("Parameters", update_request()),
|
||||
responses: %{
|
||||
200 => Operation.response("App", "application/json", oauth_app()),
|
||||
400 =>
|
||||
Operation.response("Bad Request", "application/json", %Schema{
|
||||
oneOf: [ApiError, %Schema{type: :string}]
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "oAuth Apps"],
|
||||
summary: "Delete OAuth App",
|
||||
operationId: "AdminAPI.OAuthAppController.delete",
|
||||
parameters: [id_param()],
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
400 => no_content_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_request do
|
||||
%Schema{
|
||||
title: "oAuthAppCreateRequest",
|
||||
type: :object,
|
||||
required: [:name, :redirect_uris],
|
||||
properties: %{
|
||||
name: %Schema{type: :string, description: "Application Name"},
|
||||
scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"},
|
||||
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."
|
||||
},
|
||||
website: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "A URL to the homepage of the app"
|
||||
},
|
||||
trusted: %Schema{
|
||||
type: :boolean,
|
||||
nullable: true,
|
||||
default: false,
|
||||
description: "Is the app trusted?"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"name" => "My App",
|
||||
"redirect_uris" => "https://myapp.com/auth/callback",
|
||||
"website" => "https://myapp.com/",
|
||||
"scopes" => ["read", "write"],
|
||||
"trusted" => true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_request do
|
||||
%Schema{
|
||||
title: "oAuthAppUpdateRequest",
|
||||
type: :object,
|
||||
properties: %{
|
||||
name: %Schema{type: :string, description: "Application Name"},
|
||||
scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"},
|
||||
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."
|
||||
},
|
||||
website: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "A URL to the homepage of the app"
|
||||
},
|
||||
trusted: %Schema{
|
||||
type: :boolean,
|
||||
nullable: true,
|
||||
default: false,
|
||||
description: "Is the app trusted?"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"name" => "My App",
|
||||
"redirect_uris" => "https://myapp.com/auth/callback",
|
||||
"website" => "https://myapp.com/",
|
||||
"scopes" => ["read", "write"],
|
||||
"trusted" => true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp oauth_app do
|
||||
%Schema{
|
||||
title: "oAuthApp",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :integer},
|
||||
name: %Schema{type: :string},
|
||||
client_id: %Schema{type: :string},
|
||||
client_secret: %Schema{type: :string},
|
||||
redirect_uri: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true},
|
||||
trusted: %Schema{type: :boolean}
|
||||
},
|
||||
example: %{
|
||||
"id" => 123,
|
||||
"name" => "My App",
|
||||
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
|
||||
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
|
||||
"redirect_uri" => "https://myapp.com/oauth-callback",
|
||||
"website" => "https://myapp.com/",
|
||||
"trusted" => false
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def id_param do
|
||||
Operation.parameter(:id, :path, :integer, "App ID",
|
||||
example: 1337,
|
||||
required: true
|
||||
)
|
||||
end
|
||||
end
|
||||
83
lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
Normal file
83
lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# 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.Admin.RelayOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Relays"],
|
||||
summary: "List Relays",
|
||||
operationId: "AdminAPI.RelayController.index",
|
||||
security: [%{"oAuth" => ["read"]}],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Response", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
relays: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{type: :string},
|
||||
example: ["lain.com", "mstdn.io"]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def follow_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Relays"],
|
||||
summary: "Follow a Relay",
|
||||
operationId: "AdminAPI.RelayController.follow",
|
||||
security: [%{"oAuth" => ["write:follows"]}],
|
||||
requestBody:
|
||||
request_body("Parameters", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
relay_url: %Schema{type: :string, format: :uri}
|
||||
}
|
||||
}),
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Status", "application/json", %Schema{
|
||||
type: :string,
|
||||
example: "http://mastodon.example.org/users/admin"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def unfollow_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Relays"],
|
||||
summary: "Unfollow a Relay",
|
||||
operationId: "AdminAPI.RelayController.unfollow",
|
||||
security: [%{"oAuth" => ["write:follows"]}],
|
||||
requestBody:
|
||||
request_body("Parameters", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
relay_url: %Schema{type: :string, format: :uri}
|
||||
}
|
||||
}),
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Status", "application/json", %Schema{
|
||||
type: :string,
|
||||
example: "http://mastodon.example.org/users/admin"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
237
lib/pleroma/web/api_spec/operations/admin/report_operation.ex
Normal file
237
lib/pleroma/web/api_spec/operations/admin/report_operation.ex
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
# 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.Admin.ReportOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Status
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Reports"],
|
||||
summary: "Get a list of reports",
|
||||
operationId: "AdminAPI.ReportController.index",
|
||||
security: [%{"oAuth" => ["read:reports"]}],
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:state,
|
||||
:query,
|
||||
report_state(),
|
||||
"Filter by report state"
|
||||
),
|
||||
Operation.parameter(
|
||||
:limit,
|
||||
:query,
|
||||
%Schema{type: :integer},
|
||||
"The number of records to retrieve"
|
||||
),
|
||||
Operation.parameter(
|
||||
:page,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 1},
|
||||
"Page number"
|
||||
),
|
||||
Operation.parameter(
|
||||
:page_size,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 50},
|
||||
"Number number of log entries per page"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Response", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
total: %Schema{type: :integer},
|
||||
reports: %Schema{
|
||||
type: :array,
|
||||
items: report()
|
||||
}
|
||||
}
|
||||
}),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Reports"],
|
||||
summary: "Get an individual report",
|
||||
operationId: "AdminAPI.ReportController.show",
|
||||
parameters: [id_param()],
|
||||
security: [%{"oAuth" => ["read:reports"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Report", "application/json", report()),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Reports"],
|
||||
summary: "Change the state of one or multiple reports",
|
||||
operationId: "AdminAPI.ReportController.update",
|
||||
security: [%{"oAuth" => ["write:reports"]}],
|
||||
requestBody: request_body("Parameters", update_request(), required: true),
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
400 => Operation.response("Bad Request", "application/json", update_400_response()),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def notes_create_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Reports"],
|
||||
summary: "Create report note",
|
||||
operationId: "AdminAPI.ReportController.notes_create",
|
||||
parameters: [id_param()],
|
||||
requestBody:
|
||||
request_body("Parameters", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
content: %Schema{type: :string, description: "The message"}
|
||||
}
|
||||
}),
|
||||
security: [%{"oAuth" => ["write:reports"]}],
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def notes_delete_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Reports"],
|
||||
summary: "Delete report note",
|
||||
operationId: "AdminAPI.ReportController.notes_delete",
|
||||
parameters: [
|
||||
Operation.parameter(:report_id, :path, :string, "Report ID"),
|
||||
Operation.parameter(:id, :path, :string, "Note ID")
|
||||
],
|
||||
security: [%{"oAuth" => ["write:reports"]}],
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp report_state do
|
||||
%Schema{type: :string, enum: ["open", "closed", "resolved"]}
|
||||
end
|
||||
|
||||
defp id_param do
|
||||
Operation.parameter(:id, :path, FlakeID, "Report ID",
|
||||
example: "9umDrYheeY451cQnEe",
|
||||
required: true
|
||||
)
|
||||
end
|
||||
|
||||
defp report do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: FlakeID,
|
||||
state: report_state(),
|
||||
account: account_admin(),
|
||||
actor: account_admin(),
|
||||
content: %Schema{type: :string},
|
||||
created_at: %Schema{type: :string, format: :"date-time"},
|
||||
statuses: %Schema{type: :array, items: Status},
|
||||
notes: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :integer},
|
||||
user_id: FlakeID,
|
||||
content: %Schema{type: :string},
|
||||
inserted_at: %Schema{type: :string, format: :"date-time"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp account_admin do
|
||||
%Schema{
|
||||
title: "Account",
|
||||
description: "Account view for admins",
|
||||
type: :object,
|
||||
properties:
|
||||
Map.merge(Account.schema().properties, %{
|
||||
nickname: %Schema{type: :string},
|
||||
deactivated: %Schema{type: :boolean},
|
||||
local: %Schema{type: :boolean},
|
||||
roles: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
admin: %Schema{type: :boolean},
|
||||
moderator: %Schema{type: :boolean}
|
||||
}
|
||||
},
|
||||
confirmation_pending: %Schema{type: :boolean}
|
||||
})
|
||||
}
|
||||
end
|
||||
|
||||
defp update_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:reports],
|
||||
properties: %{
|
||||
reports: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{allOf: [FlakeID], description: "Required, report ID"},
|
||||
state: %Schema{
|
||||
type: :string,
|
||||
description:
|
||||
"Required, the new state. Valid values are `open`, `closed` and `resolved`"
|
||||
}
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"reports" => [
|
||||
%{"id" => "123", "state" => "closed"},
|
||||
%{"id" => "1337", "state" => "resolved"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_400_response do
|
||||
%Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{allOf: [FlakeID], description: "Report ID"},
|
||||
error: %Schema{type: :string, description: "Error message"}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -74,7 +74,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
|
|||
parameters: [id_param()],
|
||||
security: [%{"oAuth" => ["read:statuses"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("Status", "application/json", Status),
|
||||
200 => Operation.response("Status", "application/json", status()),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +123,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do
|
|||
}
|
||||
end
|
||||
|
||||
defp admin_account do
|
||||
def admin_account do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
|
|
|
|||
355
lib/pleroma/web/api_spec/operations/chat_operation.ex
Normal file
355
lib/pleroma/web/api_spec/operations/chat_operation.ex
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
# 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.ChatOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Chat
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
|
||||
|
||||
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
|
||||
|
||||
def mark_as_read_operation do
|
||||
%Operation{
|
||||
tags: ["chat"],
|
||||
summary: "Mark all messages in the chat as read",
|
||||
operationId: "ChatController.mark_as_read",
|
||||
parameters: [Operation.parameter(:id, :path, :string, "The ID of the Chat")],
|
||||
requestBody: request_body("Parameters", mark_as_read()),
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"The updated chat",
|
||||
"application/json",
|
||||
Chat
|
||||
)
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["write:chats"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def mark_message_as_read_operation do
|
||||
%Operation{
|
||||
tags: ["chat"],
|
||||
summary: "Mark one message in the chat as read",
|
||||
operationId: "ChatController.mark_message_as_read",
|
||||
parameters: [
|
||||
Operation.parameter(:id, :path, :string, "The ID of the Chat"),
|
||||
Operation.parameter(:message_id, :path, :string, "The ID of the message")
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"The read ChatMessage",
|
||||
"application/json",
|
||||
ChatMessage
|
||||
)
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["write:chats"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["chat"],
|
||||
summary: "Create a chat",
|
||||
operationId: "ChatController.show",
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:id,
|
||||
:path,
|
||||
:string,
|
||||
"The id of the chat",
|
||||
required: true,
|
||||
example: "1234"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"The existing chat",
|
||||
"application/json",
|
||||
Chat
|
||||
)
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["read"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["chat"],
|
||||
summary: "Create a chat",
|
||||
operationId: "ChatController.create",
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:id,
|
||||
:path,
|
||||
:string,
|
||||
"The account id of the recipient of this chat",
|
||||
required: true,
|
||||
example: "someflakeid"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"The created or existing chat",
|
||||
"application/json",
|
||||
Chat
|
||||
)
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["write:chats"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["chat"],
|
||||
summary: "Get a list of chats that you participated in",
|
||||
operationId: "ChatController.index",
|
||||
parameters: pagination_params(),
|
||||
responses: %{
|
||||
200 => Operation.response("The chats of the user", "application/json", chats_response())
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["read:chats"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def messages_operation do
|
||||
%Operation{
|
||||
tags: ["chat"],
|
||||
summary: "Get the most recent messages of the chat",
|
||||
operationId: "ChatController.messages",
|
||||
parameters:
|
||||
[Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++
|
||||
pagination_params(),
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"The messages in the chat",
|
||||
"application/json",
|
||||
chat_messages_response()
|
||||
)
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["read:chats"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def post_chat_message_operation do
|
||||
%Operation{
|
||||
tags: ["chat"],
|
||||
summary: "Post a message to the chat",
|
||||
operationId: "ChatController.post_chat_message",
|
||||
parameters: [
|
||||
Operation.parameter(:id, :path, :string, "The ID of the Chat")
|
||||
],
|
||||
requestBody: request_body("Parameters", chat_message_create()),
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"The newly created ChatMessage",
|
||||
"application/json",
|
||||
ChatMessage
|
||||
),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["write:chats"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def delete_message_operation do
|
||||
%Operation{
|
||||
tags: ["chat"],
|
||||
summary: "delete_message",
|
||||
operationId: "ChatController.delete_message",
|
||||
parameters: [
|
||||
Operation.parameter(:id, :path, :string, "The ID of the Chat"),
|
||||
Operation.parameter(:message_id, :path, :string, "The ID of the message")
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"The deleted ChatMessage",
|
||||
"application/json",
|
||||
ChatMessage
|
||||
)
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["write:chats"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def chats_response do
|
||||
%Schema{
|
||||
title: "ChatsResponse",
|
||||
description: "Response schema for multiple Chats",
|
||||
type: :array,
|
||||
items: Chat,
|
||||
example: [
|
||||
%{
|
||||
"account" => %{
|
||||
"pleroma" => %{
|
||||
"is_admin" => false,
|
||||
"confirmation_pending" => false,
|
||||
"hide_followers_count" => false,
|
||||
"is_moderator" => false,
|
||||
"hide_favorites" => true,
|
||||
"ap_id" => "https://dontbulling.me/users/lain",
|
||||
"hide_follows_count" => false,
|
||||
"hide_follows" => false,
|
||||
"background_image" => nil,
|
||||
"skip_thread_containment" => false,
|
||||
"hide_followers" => false,
|
||||
"relationship" => %{},
|
||||
"tags" => []
|
||||
},
|
||||
"avatar" =>
|
||||
"https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
|
||||
"following_count" => 0,
|
||||
"header_static" => "https://originalpatchou.li/images/banner.png",
|
||||
"source" => %{
|
||||
"sensitive" => false,
|
||||
"note" => "lain",
|
||||
"pleroma" => %{
|
||||
"discoverable" => false,
|
||||
"actor_type" => "Person"
|
||||
},
|
||||
"fields" => []
|
||||
},
|
||||
"statuses_count" => 1,
|
||||
"locked" => false,
|
||||
"created_at" => "2020-04-16T13:40:15.000Z",
|
||||
"display_name" => "lain",
|
||||
"fields" => [],
|
||||
"acct" => "lain@dontbulling.me",
|
||||
"id" => "9u6Qw6TAZANpqokMkK",
|
||||
"emojis" => [],
|
||||
"avatar_static" =>
|
||||
"https://dontbulling.me/media/065a4dd3c6740dab13ff9c71ec7d240bb9f8be9205c9e7467fb2202117da1e32.jpg",
|
||||
"username" => "lain",
|
||||
"followers_count" => 0,
|
||||
"header" => "https://originalpatchou.li/images/banner.png",
|
||||
"bot" => false,
|
||||
"note" => "lain",
|
||||
"url" => "https://dontbulling.me/users/lain"
|
||||
},
|
||||
"id" => "1",
|
||||
"unread" => 2
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def chat_messages_response do
|
||||
%Schema{
|
||||
title: "ChatMessagesResponse",
|
||||
description: "Response schema for multiple ChatMessages",
|
||||
type: :array,
|
||||
items: ChatMessage,
|
||||
example: [
|
||||
%{
|
||||
"emojis" => [
|
||||
%{
|
||||
"static_url" => "https://dontbulling.me/emoji/Firefox.gif",
|
||||
"visible_in_picker" => false,
|
||||
"shortcode" => "firefox",
|
||||
"url" => "https://dontbulling.me/emoji/Firefox.gif"
|
||||
}
|
||||
],
|
||||
"created_at" => "2020-04-21T15:11:46.000Z",
|
||||
"content" => "Check this out :firefox:",
|
||||
"id" => "13",
|
||||
"chat_id" => "1",
|
||||
"actor_id" => "someflakeid",
|
||||
"unread" => false
|
||||
},
|
||||
%{
|
||||
"actor_id" => "someflakeid",
|
||||
"content" => "Whats' up?",
|
||||
"id" => "12",
|
||||
"chat_id" => "1",
|
||||
"emojis" => [],
|
||||
"created_at" => "2020-04-21T15:06:45.000Z",
|
||||
"unread" => false
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def chat_message_create do
|
||||
%Schema{
|
||||
title: "ChatMessageCreateRequest",
|
||||
description: "POST body for creating an chat message",
|
||||
type: :object,
|
||||
properties: %{
|
||||
content: %Schema{
|
||||
type: :string,
|
||||
description: "The content of your message. Optional if media_id is present"
|
||||
},
|
||||
media_id: %Schema{type: :string, description: "The id of an upload"}
|
||||
},
|
||||
example: %{
|
||||
"content" => "Hey wanna buy feet pics?",
|
||||
"media_id" => "134234"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def mark_as_read do
|
||||
%Schema{
|
||||
title: "MarkAsReadRequest",
|
||||
description: "POST body for marking a number of chat messages as read",
|
||||
type: :object,
|
||||
required: [:last_read_id],
|
||||
properties: %{
|
||||
last_read_id: %Schema{
|
||||
type: :string,
|
||||
description: "The content of your message."
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"last_read_id" => "abcdef12456"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -137,7 +137,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
|
|||
"background_upload_limit" => 4_000_000,
|
||||
"background_image" => "/static/image.png",
|
||||
"banner_upload_limit" => 4_000_000,
|
||||
"description" => "A Pleroma instance, an alternative fediverse server",
|
||||
"description" => "Pleroma: An efficient and flexible fediverse server",
|
||||
"email" => "lain@lain.com",
|
||||
"languages" => ["en"],
|
||||
"max_toot_chars" => 5000,
|
||||
|
|
|
|||
|
|
@ -163,6 +163,13 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
|||
description:
|
||||
"Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.",
|
||||
nullable: true
|
||||
},
|
||||
pleroma: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
is_seen: %Schema{type: :boolean},
|
||||
is_muted: %Schema{type: :boolean}
|
||||
}
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
|
|
@ -170,7 +177,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
|||
"type" => "mention",
|
||||
"created_at" => "2019-11-23T07:49:02.064Z",
|
||||
"account" => Account.schema().example,
|
||||
"status" => Status.schema().example
|
||||
"status" => Status.schema().example,
|
||||
"pleroma" => %{"is_seen" => false, "is_muted" => false}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -183,8 +191,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
|||
"favourite",
|
||||
"reblog",
|
||||
"mention",
|
||||
"poll",
|
||||
"pleroma:emoji_reaction",
|
||||
"pleroma:chat_mention",
|
||||
"move",
|
||||
"follow_request"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -33,6 +33,20 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
|
|||
tags: ["Emoji Packs"],
|
||||
summary: "Lists local custom emoji packs",
|
||||
operationId: "PleromaAPI.EmojiPackController.index",
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:page,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 1},
|
||||
"Page"
|
||||
),
|
||||
Operation.parameter(
|
||||
:page_size,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 50},
|
||||
"Number of emoji packs to return"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 => emoji_packs_response()
|
||||
}
|
||||
|
|
@ -44,7 +58,21 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
|
|||
tags: ["Emoji Packs"],
|
||||
summary: "Show emoji pack",
|
||||
operationId: "PleromaAPI.EmojiPackController.show",
|
||||
parameters: [name_param()],
|
||||
parameters: [
|
||||
name_param(),
|
||||
Operation.parameter(
|
||||
:page,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 1},
|
||||
"Page"
|
||||
),
|
||||
Operation.parameter(
|
||||
:page_size,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 30},
|
||||
"Number of emoji to return"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Emoji Pack", "application/json", emoji_pack()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
|
|
|
|||
|
|
@ -333,7 +333,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
%Operation{
|
||||
tags: ["Statuses"],
|
||||
summary: "Favourited statuses",
|
||||
description: "Statuses the user has favourited",
|
||||
description:
|
||||
"Statuses the user has favourited. Please note that you have to use the link headers to paginate this. You can not build the query parameters yourself.",
|
||||
operationId: "StatusController.favourites",
|
||||
parameters: pagination_params(),
|
||||
security: [%{"oAuth" => ["read:favourites"]}],
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue