aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/web/admin_api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/web/admin_api')
-rw-r--r--lib/pleroma/web/admin_api/controllers/admin_api_controller.ex445
-rw-r--r--lib/pleroma/web/admin_api/controllers/announcement_controller.ex83
-rw-r--r--lib/pleroma/web/admin_api/controllers/chat_controller.ex78
-rw-r--r--lib/pleroma/web/admin_api/controllers/config_controller.ex198
-rw-r--r--lib/pleroma/web/admin_api/controllers/fallback_controller.ex37
-rw-r--r--lib/pleroma/web/admin_api/controllers/frontend_controller.ex46
-rw-r--r--lib/pleroma/web/admin_api/controllers/instance_controller.ex63
-rw-r--r--lib/pleroma/web/admin_api/controllers/instance_document_controller.ex41
-rw-r--r--lib/pleroma/web/admin_api/controllers/invite_controller.ex78
-rw-r--r--lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex76
-rw-r--r--lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex76
-rw-r--r--lib/pleroma/web/admin_api/controllers/relay_controller.ex59
-rw-r--r--lib/pleroma/web/admin_api/controllers/report_controller.ex114
-rw-r--r--lib/pleroma/web/admin_api/controllers/status_controller.ex71
-rw-r--r--lib/pleroma/web/admin_api/controllers/user_controller.ex309
-rw-r--r--lib/pleroma/web/admin_api/report.ex60
-rw-r--r--lib/pleroma/web/admin_api/search.ex30
-rw-r--r--lib/pleroma/web/admin_api/views/account_view.ex161
-rw-r--r--lib/pleroma/web/admin_api/views/announcement_view.ex15
-rw-r--r--lib/pleroma/web/admin_api/views/chat_view.ex30
-rw-r--r--lib/pleroma/web/admin_api/views/config_view.ex30
-rw-r--r--lib/pleroma/web/admin_api/views/frontend_view.ex21
-rw-r--r--lib/pleroma/web/admin_api/views/invite_view.ex25
-rw-r--r--lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex15
-rw-r--r--lib/pleroma/web/admin_api/views/moderation_log_view.ex30
-rw-r--r--lib/pleroma/web/admin_api/views/o_auth_app_view.ex10
-rw-r--r--lib/pleroma/web/admin_api/views/report_view.ex74
-rw-r--r--lib/pleroma/web/admin_api/views/status_view.ex30
-rw-r--r--lib/pleroma/web/admin_api/views/user_view.ex10
29 files changed, 2315 insertions, 0 deletions
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
new file mode 100644
index 0000000..1894000
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -0,0 +1,445 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AdminAPIController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [json_response: 3, fetch_integer_param: 3]
+
+ alias Pleroma.Config
+ alias Pleroma.MFA
+ alias Pleroma.ModerationLog
+ alias Pleroma.Stats
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.AdminAPI.AccountView
+ alias Pleroma.Web.AdminAPI.ModerationLogView
+ alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Router
+
+ @users_page_size 50
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read:accounts"]}
+ when action in [:right_get, :show_user_credentials, :create_backup]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:accounts"]}
+ when action in [
+ :get_password_reset,
+ :force_password_reset,
+ :tag_users,
+ :untag_users,
+ :right_add,
+ :right_add_multiple,
+ :right_delete,
+ :disable_mfa,
+ :right_delete_multiple,
+ :update_user_credentials
+ ]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read:statuses"]}
+ when action in [:list_user_statuses]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read:chats"]}
+ when action in [:list_user_chats]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read"]}
+ when action in [
+ :list_log,
+ :stats,
+ :need_reboot
+ ]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write"]}
+ when action in [
+ :restart,
+ :resend_confirmation_email,
+ :confirm_email,
+ :reload_emoji
+ ]
+ )
+
+ action_fallback(AdminAPI.FallbackController)
+
+ def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
+ with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
+ godmode = params["godmode"] == "true" || params["godmode"] == true
+
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
+ {page, page_size} = page_params(params)
+
+ result =
+ ActivityPub.fetch_user_activities(user, nil, %{
+ limit: page_size,
+ offset: (page - 1) * page_size,
+ godmode: godmode,
+ exclude_reblogs: not with_reblogs,
+ pagination_type: :offset,
+ total: true
+ })
+
+ conn
+ |> put_view(AdminAPI.StatusView)
+ |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
+ def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
+ with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
+ chats =
+ Pleroma.Chat.for_user_query(user_id)
+ |> Pleroma.Repo.all()
+
+ conn
+ |> put_view(AdminAPI.ChatView)
+ |> render("index.json", chats: chats)
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
+ def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
+ with {:ok, _} <- User.tag(nicknames, tags) do
+ ModerationLog.insert_log(%{
+ actor: admin,
+ nicknames: nicknames,
+ tags: tags,
+ action: "tag"
+ })
+
+ json_response(conn, :no_content, "")
+ end
+ end
+
+ def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
+ with {:ok, _} <- User.untag(nicknames, tags) do
+ ModerationLog.insert_log(%{
+ actor: admin,
+ nicknames: nicknames,
+ tags: tags,
+ action: "untag"
+ })
+
+ json_response(conn, :no_content, "")
+ end
+ end
+
+ def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
+ "permission_group" => permission_group,
+ "nicknames" => nicknames
+ })
+ when permission_group in ["moderator", "admin"] do
+ update = %{:"is_#{permission_group}" => true}
+
+ users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+ for u <- users, do: User.admin_api_update(u, update)
+
+ ModerationLog.insert_log(%{
+ action: "grant",
+ actor: admin,
+ subject: users,
+ permission: permission_group
+ })
+
+ json(conn, update)
+ end
+
+ def right_add_multiple(conn, _) do
+ render_error(conn, :not_found, "No such permission_group")
+ end
+
+ def right_add(%{assigns: %{user: admin}} = conn, %{
+ "permission_group" => permission_group,
+ "nickname" => nickname
+ })
+ when permission_group in ["moderator", "admin"] do
+ fields = %{:"is_#{permission_group}" => true}
+
+ {:ok, user} =
+ nickname
+ |> User.get_cached_by_nickname()
+ |> User.admin_api_update(fields)
+
+ ModerationLog.insert_log(%{
+ action: "grant",
+ actor: admin,
+ subject: [user],
+ permission: permission_group
+ })
+
+ json(conn, fields)
+ end
+
+ def right_add(conn, _) do
+ render_error(conn, :not_found, "No such permission_group")
+ end
+
+ def right_get(conn, %{"nickname" => nickname}) do
+ user = User.get_cached_by_nickname(nickname)
+
+ conn
+ |> json(%{
+ is_moderator: user.is_moderator,
+ is_admin: user.is_admin
+ })
+ end
+
+ def right_delete_multiple(
+ %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
+ %{
+ "permission_group" => permission_group,
+ "nicknames" => nicknames
+ }
+ )
+ when permission_group in ["moderator", "admin"] do
+ with false <- Enum.member?(nicknames, admin_nickname) do
+ update = %{:"is_#{permission_group}" => false}
+
+ users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+ for u <- users, do: User.admin_api_update(u, update)
+
+ ModerationLog.insert_log(%{
+ action: "revoke",
+ actor: admin,
+ subject: users,
+ permission: permission_group
+ })
+
+ json(conn, update)
+ else
+ _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
+ end
+ end
+
+ def right_delete_multiple(conn, _) do
+ render_error(conn, :not_found, "No such permission_group")
+ end
+
+ def right_delete(
+ %{assigns: %{user: admin}} = conn,
+ %{
+ "permission_group" => permission_group,
+ "nickname" => nickname
+ }
+ )
+ when permission_group in ["moderator", "admin"] do
+ fields = %{:"is_#{permission_group}" => false}
+
+ {:ok, user} =
+ nickname
+ |> User.get_cached_by_nickname()
+ |> User.admin_api_update(fields)
+
+ ModerationLog.insert_log(%{
+ action: "revoke",
+ actor: admin,
+ subject: [user],
+ permission: permission_group
+ })
+
+ json(conn, fields)
+ end
+
+ def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
+ render_error(conn, :forbidden, "You can't revoke your own admin status.")
+ 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)
+ {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
+
+ conn
+ |> json(%{
+ token: token.token,
+ link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
+ })
+ end
+
+ @doc "Force password reset for a given user"
+ def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+ Enum.each(users, &User.force_password_reset_async/1)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "force_password_reset"
+ })
+
+ json_response(conn, :no_content, "")
+ end
+
+ @doc "Disable mfa for user's account."
+ def disable_mfa(conn, %{"nickname" => nickname}) do
+ case User.get_by_nickname(nickname) do
+ %User{} = user ->
+ MFA.disable(user)
+ json(conn, nickname)
+
+ _ ->
+ {:error, :not_found}
+ end
+ end
+
+ @doc "Show a given user's credentials"
+ def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
+ conn
+ |> put_view(AccountView)
+ |> render("credentials.json", %{user: user, for: admin})
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
+ @doc "Updates a given user"
+ def update_user_credentials(
+ %{assigns: %{user: admin}} = conn,
+ %{"nickname" => nickname} = params
+ ) do
+ with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
+ {:ok, _user} <-
+ User.update_as_admin(user, params) do
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: [user],
+ action: "updated_users"
+ })
+
+ if params["password"] do
+ User.force_password_reset_async(user)
+ end
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: [user],
+ action: "force_password_reset"
+ })
+
+ json(conn, %{status: "success"})
+ else
+ {:error, changeset} ->
+ errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
+
+ {:errors, errors}
+
+ _ ->
+ {:error, :not_found}
+ end
+ end
+
+ def list_log(conn, params) do
+ {page, page_size} = page_params(params)
+
+ log =
+ ModerationLog.get_all(%{
+ page: page,
+ page_size: page_size,
+ start_date: params["start_date"],
+ end_date: params["end_date"],
+ user_id: params["user_id"],
+ search: params["search"]
+ })
+
+ conn
+ |> put_view(ModerationLogView)
+ |> render("index.json", %{log: log})
+ end
+
+ def restart(conn, _params) do
+ with :ok <- configurable_from_database() do
+ Restarter.Pleroma.restart(Config.get(:env), 50)
+
+ json(conn, %{})
+ end
+ end
+
+ def need_reboot(conn, _params) do
+ json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
+ end
+
+ defp configurable_from_database do
+ if Config.get(:configurable_from_database) do
+ :ok
+ else
+ {:error, "You must enable configurable_from_database in your config file."}
+ end
+ end
+
+ def reload_emoji(conn, _params) do
+ Pleroma.Emoji.reload()
+
+ json(conn, "ok")
+ end
+
+ def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+
+ User.confirm(users)
+
+ ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
+
+ json(conn, "")
+ end
+
+ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users =
+ Enum.map(nicknames, fn nickname ->
+ nickname
+ |> User.get_cached_by_nickname()
+ |> User.send_confirmation_email()
+ end)
+
+ ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
+
+ json(conn, "")
+ end
+
+ def stats(conn, params) do
+ counters = Stats.get_status_visibility_count(params["instance"])
+
+ json(conn, %{"status_visibility" => counters})
+ end
+
+ def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+ with %User{} = user <- User.get_by_nickname(nickname),
+ {:ok, _} <- Pleroma.User.Backup.create(user, admin.id) do
+ ModerationLog.insert_log(%{actor: admin, subject: user, action: "create_backup"})
+
+ json(conn, "")
+ end
+ end
+
+ defp page_params(params) do
+ {
+ fetch_integer_param(params, "page", 1),
+ fetch_integer_param(params, "page_size", @users_page_size)
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/announcement_controller.ex b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
new file mode 100644
index 0000000..6ad5fc1
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
@@ -0,0 +1,83 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AnnouncementController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Announcement
+ alias Pleroma.Web.ControllerHelper
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:create, :delete, :change])
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action in [:index, :show])
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.AnnouncementOperation
+
+ defp default_limit, do: 20
+
+ def index(conn, params) do
+ limit = Map.get(params, :limit, default_limit())
+ offset = Map.get(params, :offset, 0)
+
+ announcements = Announcement.list_paginated(%{limit: limit, offset: offset})
+
+ render(conn, "index.json", announcements: announcements)
+ end
+
+ def show(conn, %{id: id} = _params) do
+ announcement = Announcement.get_by_id(id)
+
+ if is_nil(announcement) do
+ {:error, :not_found}
+ else
+ render(conn, "show.json", announcement: announcement)
+ end
+ end
+
+ def create(%{body_params: params} = conn, _params) do
+ with {:ok, announcement} <- Announcement.add(change_params(params)) do
+ render(conn, "show.json", announcement: announcement)
+ else
+ _ ->
+ {:error, 400}
+ end
+ end
+
+ def change_params(orig_params) do
+ data =
+ %{}
+ |> Pleroma.Maps.put_if_present("content", orig_params, &Map.fetch(&1, :content))
+ |> Pleroma.Maps.put_if_present("all_day", orig_params, &Map.fetch(&1, :all_day))
+
+ orig_params
+ |> Map.merge(%{data: data})
+ end
+
+ def change(%{body_params: params} = conn, %{id: id} = _params) do
+ with announcement <- Announcement.get_by_id(id),
+ {:exists, true} <- {:exists, not is_nil(announcement)},
+ {:ok, announcement} <- Announcement.update(announcement, change_params(params)) do
+ render(conn, "show.json", announcement: announcement)
+ else
+ {:exists, false} ->
+ {:error, :not_found}
+
+ _ ->
+ {:error, 400}
+ end
+ end
+
+ def delete(conn, %{id: id} = _params) do
+ case Announcement.delete_by_id(id) do
+ :ok ->
+ conn
+ |> ControllerHelper.json_response(:ok, %{})
+
+ _ ->
+ {:error, :not_found}
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex
new file mode 100644
index 0000000..298543f
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex
@@ -0,0 +1,78 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ChatController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Activity
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.Pagination
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read:chats"]} when action in [:show, :messages]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:chats"]} when action in [:delete_message]
+ )
+
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ChatOperation
+
+ def delete_message(%{assigns: %{user: user}} = conn, %{
+ message_id: message_id,
+ id: chat_id
+ }) do
+ with %MessageReference{object: %{data: %{"id" => object_ap_id}}} = cm_ref <-
+ MessageReference.get_by_id(message_id),
+ ^chat_id <- to_string(cm_ref.chat_id),
+ %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object_ap_id),
+ {:ok, _} <- CommonAPI.delete(activity_id, user) do
+ conn
+ |> put_view(MessageReferenceView)
+ |> render("show.json", chat_message_reference: cm_ref)
+ else
+ _e ->
+ {:error, :could_not_delete}
+ end
+ end
+
+ def messages(conn, %{id: id} = params) do
+ with %Chat{} = chat <- Chat.get_by_id(id) do
+ cm_refs =
+ chat
+ |> MessageReference.for_chat_query()
+ |> Pagination.fetch_paginated(params)
+
+ conn
+ |> put_view(MessageReferenceView)
+ |> render("index.json", chat_message_references: cm_refs)
+ else
+ _ ->
+ conn
+ |> put_status(:not_found)
+ |> json(%{error: "not found"})
+ end
+ end
+
+ def show(conn, %{id: id}) do
+ with %Chat{} = chat <- Chat.get_by_id(id) do
+ conn
+ |> put_view(AdminAPI.ChatView)
+ |> render("show.json", chat: chat)
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
new file mode 100644
index 0000000..a03318c
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex
@@ -0,0 +1,198 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action == :update)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read"]}
+ when action in [:show, :descriptions]
+ )
+
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
+
+ defp translate_descriptions(descriptions, path \\ []) do
+ Enum.map(descriptions, fn desc -> translate_item(desc, path) end)
+ end
+
+ defp translate_string(str, path, type) do
+ Gettext.dpgettext(
+ Pleroma.Web.Gettext,
+ "config_descriptions",
+ Pleroma.Docs.Translator.Compiler.msgctxt_for(path, type),
+ str
+ )
+ end
+
+ defp maybe_put_translated(item, key, path) do
+ if item[key] do
+ Map.put(
+ item,
+ key,
+ translate_string(
+ item[key],
+ path ++ [Pleroma.Docs.Translator.Compiler.key_for(item)],
+ to_string(key)
+ )
+ )
+ else
+ item
+ end
+ end
+
+ defp translate_item(item, path) do
+ item
+ |> maybe_put_translated(:label, path)
+ |> maybe_put_translated(:description, path)
+ |> translate_children(path)
+ end
+
+ defp translate_children(%{children: children} = item, path) when is_list(children) do
+ item
+ |> Map.put(
+ :children,
+ translate_descriptions(children, path ++ [Pleroma.Docs.Translator.Compiler.key_for(item)])
+ )
+ end
+
+ defp translate_children(item, _path) do
+ item
+ end
+
+ def descriptions(conn, _params) do
+ descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1)
+
+ json(conn, translate_descriptions(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, "You must enable configurable_from_database in your config file."}
+ 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
diff --git a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
new file mode 100644
index 0000000..e72f45c
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex
@@ -0,0 +1,37 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.FallbackController do
+ use Pleroma.Web, :controller
+
+ def call(conn, {:error, :not_found}) do
+ conn
+ |> put_status(:not_found)
+ |> json(%{error: dgettext("errors", "Not found")})
+ end
+
+ def call(conn, {:error, reason}) do
+ conn
+ |> put_status(:bad_request)
+ |> 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)
+ |> json(dgettext("errors", "Invalid parameters"))
+ end
+
+ def call(conn, _) do
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{error: dgettext("errors", "Something went wrong")})
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex
new file mode 100644
index 0000000..b4dbb82
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.FrontendController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Config
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action == :install)
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :index)
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.FrontendOperation
+
+ def index(conn, _params) do
+ installed = installed()
+
+ frontends =
+ [:frontends, :available]
+ |> Config.get([])
+ |> Enum.map(fn {name, desc} ->
+ Map.put(desc, "installed", name in installed)
+ end)
+
+ render(conn, "index.json", frontends: frontends)
+ end
+
+ def install(%{body_params: params} = conn, _params) do
+ with :ok <- Pleroma.Frontend.install(params.name, Map.delete(params, :name)) do
+ index(conn, %{})
+ end
+ end
+
+ defp installed do
+ frontend_directory = Pleroma.Frontend.dir()
+
+ if File.exists?(frontend_directory) do
+ File.ls!(frontend_directory)
+ else
+ []
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/instance_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_controller.ex
new file mode 100644
index 0000000..117a722
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/instance_controller.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.InstanceController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 3]
+
+ alias Pleroma.Instances.Instance
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ @default_page_size 50
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read:statuses"]}
+ when action in [:list_statuses]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:accounts", "admin:write:statuses"]}
+ when action in [:delete]
+ )
+
+ action_fallback(AdminAPI.FallbackController)
+
+ def list_statuses(conn, %{"instance" => instance} = params) do
+ with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
+ {page, page_size} = page_params(params)
+
+ result =
+ ActivityPub.fetch_statuses(nil, %{
+ instance: instance,
+ limit: page_size,
+ offset: (page - 1) * page_size,
+ exclude_reblogs: not with_reblogs,
+ total: true
+ })
+
+ conn
+ |> put_view(AdminAPI.StatusView)
+ |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
+ end
+
+ def delete(conn, %{"instance" => instance}) do
+ with {:ok, _job} <- Instance.delete_users_and_activities(instance) do
+ json(conn, instance)
+ end
+ end
+
+ defp page_params(params) do
+ {
+ fetch_integer_param(params, "page", 1),
+ fetch_integer_param(params, "page_size", @default_page_size)
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex
new file mode 100644
index 0000000..990a943
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Web.InstanceDocument
+ alias Pleroma.Web.Plugs.InstanceStatic
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation
+
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :show)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:update, :delete])
+
+ def show(conn, %{name: document_name}) do
+ with {:ok, url} <- InstanceDocument.get(document_name),
+ {:ok, content} <- File.read(InstanceStatic.file_path(url)) do
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_resp(200, content)
+ end
+ end
+
+ def update(%{body_params: %{file: file}} = conn, %{name: document_name}) do
+ with {:ok, url} <- InstanceDocument.put(document_name, file.path) do
+ json(conn, %{"url" => url})
+ end
+ end
+
+ def delete(conn, %{name: document_name}) do
+ with :ok <- InstanceDocument.delete(document_name) do
+ json(conn, %{})
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/invite_controller.ex b/lib/pleroma/web/admin_api/controllers/invite_controller.ex
new file mode 100644
index 0000000..c5d759b
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/invite_controller.ex
@@ -0,0 +1,78 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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.UserInviteToken
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["admin:read:invites"]} when action == :index)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:invites"]} 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
diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
new file mode 100644
index 0000000..4d53f54
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
@@ -0,0 +1,76 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Web.ApiSpec.Admin, as: Spec
+ alias Pleroma.Web.MediaProxy
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read:media_proxy_caches"]} when action in [:index]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:media_proxy_caches"]} 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
+ entries = fetch_entries(params)
+ urls = paginate_entries(entries, params.page, params.page_size)
+
+ render(conn, "index.json",
+ urls: urls,
+ page_size: params.page_size,
+ count: length(entries)
+ )
+ end
+
+ defp fetch_entries(params) do
+ MediaProxy.cache_table()
+ |> @cachex.stream!(Cachex.Query.create(true, :key))
+ |> filter_entries(params[:query])
+ end
+
+ defp filter_entries(stream, query) when is_binary(query) do
+ regex = ~r/#{query}/i
+
+ stream
+ |> Enum.filter(fn url -> String.match?(url, regex) end)
+ |> Enum.to_list()
+ end
+
+ defp filter_entries(stream, _), do: Enum.to_list(stream)
+
+ defp paginate_entries(entries, page, page_size) do
+ offset = page_size * (page - 1)
+ Enum.slice(entries, offset, page_size)
+ end
+
+ def delete(%{assigns: %{user: _}, body_params: %{urls: urls}} = conn, _) do
+ MediaProxy.remove_from_banned_urls(urls)
+ json(conn, %{})
+ 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
+
+ json(conn, %{})
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
new file mode 100644
index 0000000..879e8b2
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
@@ -0,0 +1,76 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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.Web.OAuth.App
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write"]}
+ 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
diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
new file mode 100644
index 0000000..2e83fe1
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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.Web.ActivityPub.Relay
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:follows"]}
+ when action in [:follow, :unfollow]
+ )
+
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} 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, %{actor: target, followed_back: target in Relay.following()})
+ else
+ _ ->
+ conn
+ |> put_status(500)
+ |> json(target)
+ end
+ end
+
+ def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target} = params} = conn, _) do
+ with {:ok, _message} <- Relay.unfollow(target, %{force: params[:force]}) do
+ ModerationLog.insert_log(%{action: "relay_unfollow", actor: admin, target: target})
+
+ json(conn, target)
+ else
+ _ ->
+ conn
+ |> put_status(500)
+ |> json(target)
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex
new file mode 100644
index 0000000..15cbbcc
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/report_controller.ex
@@ -0,0 +1,114 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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.ReportNote
+ alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.AdminAPI.Report
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["admin:read:reports"]} when action in [:index, :show])
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:reports"]}
+ 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_report(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} ->
+ report = Activity.get_by_id_with_user_actor(activity.id)
+
+ ModerationLog.insert_log(%{
+ action: "report_update",
+ actor: admin,
+ subject: activity,
+ subject_actor: report.user_actor
+ })
+
+ 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),
+ report <- Activity.get_by_id_with_user_actor(report_id) do
+ ModerationLog.insert_log(%{
+ action: "report_note",
+ actor: user,
+ subject: report,
+ subject_actor: report.user_actor,
+ 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),
+ report <- Activity.get_by_id_with_user_actor(report_id) do
+ ModerationLog.insert_log(%{
+ action: "report_note_delete",
+ actor: user,
+ subject: report,
+ subject_actor: report.user_actor,
+ text: note.content
+ })
+
+ json_response(conn, :no_content, "")
+ else
+ _ -> json_response(conn, :bad_request, "")
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex
new file mode 100644
index 0000000..9a3d49b
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/status_controller.ex
@@ -0,0 +1,71 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.StatusController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Activity
+ alias Pleroma.ModerationLog
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["admin:read:statuses"]} when action in [:index, :show])
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:statuses"]} when action in [:update, :delete]
+ )
+
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.StatusOperation
+
+ 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
+ })
+
+ render(conn, "index.json", activities: activities, as: :activity)
+ end
+
+ def show(conn, %{id: id}) do
+ with %Activity{} = activity <- Activity.get_by_id(id) do
+ render(conn, "show.json", %{activity: activity})
+ else
+ nil -> {:error, :not_found}
+ end
+ end
+
+ def update(%{assigns: %{user: admin}, body_params: params} = conn, %{id: id}) do
+ with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
+ ModerationLog.insert_log(%{
+ action: "status_update",
+ actor: admin,
+ subject: activity,
+ sensitive: params[:sensitive],
+ visibility: params[:visibility]
+ })
+
+ conn
+ |> put_view(MastodonAPI.StatusView)
+ |> render("show.json", %{activity: activity})
+ end
+ end
+
+ def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
+ with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
+ json(conn, %{})
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/user_controller.ex b/lib/pleroma/web/admin_api/controllers/user_controller.ex
new file mode 100644
index 0000000..7b4ee46
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/user_controller.ex
@@ -0,0 +1,309 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.UserController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [fetch_integer_param: 3]
+
+ alias Pleroma.ModerationLog
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.Pipeline
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.AdminAPI.Search
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ @users_page_size 50
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:read:accounts"]}
+ when action in [:index, :show]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:accounts"]}
+ when action in [
+ :delete,
+ :create,
+ :toggle_activation,
+ :activate,
+ :deactivate,
+ :approve,
+ :suggest,
+ :unsuggest
+ ]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["admin:write:follows"]}
+ when action in [:follow, :unfollow]
+ )
+
+ action_fallback(AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation
+
+ def delete(conn, %{nickname: nickname}) do
+ conn
+ |> Map.put(:body_params, %{nicknames: [nickname]})
+ |> delete(%{})
+ end
+
+ def delete(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+
+ Enum.each(users, fn user ->
+ {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
+ Pipeline.common_pipeline(delete_data, local: true)
+ end)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "delete"
+ })
+
+ json(conn, nicknames)
+ end
+
+ def follow(
+ %{
+ assigns: %{user: admin},
+ body_params: %{
+ follower: follower_nick,
+ followed: followed_nick
+ }
+ } = conn,
+ _
+ ) do
+ with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
+ %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
+ User.follow(follower, followed)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ followed: followed,
+ follower: follower,
+ action: "follow"
+ })
+ end
+
+ json(conn, "ok")
+ end
+
+ def unfollow(
+ %{
+ assigns: %{user: admin},
+ body_params: %{
+ follower: follower_nick,
+ followed: followed_nick
+ }
+ } = conn,
+ _
+ ) do
+ with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
+ %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
+ User.unfollow(follower, followed)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ followed: followed,
+ follower: follower,
+ action: "unfollow"
+ })
+ end
+
+ json(conn, "ok")
+ end
+
+ def create(%{assigns: %{user: admin}, body_params: %{users: users}} = conn, _) do
+ changesets =
+ users
+ |> Enum.map(fn %{nickname: nickname, email: email, password: password} ->
+ user_data = %{
+ nickname: nickname,
+ name: nickname,
+ email: email,
+ password: password,
+ password_confirmation: password,
+ bio: "."
+ }
+
+ User.register_changeset(%User{}, user_data, need_confirmation: false)
+ end)
+ |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
+ Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
+ end)
+
+ case Pleroma.Repo.transaction(changesets) do
+ {:ok, users_map} ->
+ users =
+ users_map
+ |> Map.values()
+ |> Enum.map(fn user ->
+ {:ok, user} = User.post_register_action(user)
+
+ user
+ end)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subjects: users,
+ action: "create"
+ })
+
+ render(conn, "created_many.json", users: users)
+
+ {:error, id, changeset, _} ->
+ changesets =
+ Enum.map(changesets.operations, fn
+ {^id, {:changeset, _current_changeset, _}} ->
+ changeset
+
+ {_, {:changeset, current_changeset, _}} ->
+ current_changeset
+ end)
+
+ conn
+ |> put_status(:conflict)
+ |> render("create_errors.json", changesets: changesets)
+ end
+ end
+
+ def show(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
+ render(conn, "show.json", %{user: user})
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
+ def toggle_activation(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
+ user = User.get_cached_by_nickname(nickname)
+
+ {:ok, updated_user} = User.set_activation(user, !user.is_active)
+
+ action = if !user.is_active, do: "activate", else: "deactivate"
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: [user],
+ action: action
+ })
+
+ render(conn, "show.json", user: updated_user)
+ end
+
+ def activate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.set_activation(users, true)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "activate"
+ })
+
+ render(conn, "index.json", users: Keyword.values(updated_users))
+ end
+
+ def deactivate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.set_activation(users, false)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "deactivate"
+ })
+
+ render(conn, "index.json", users: Keyword.values(updated_users))
+ end
+
+ def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.approve(users)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "approve"
+ })
+
+ render(conn, "index.json", users: updated_users)
+ end
+
+ def suggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.set_suggestion(users, true)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "add_suggestion"
+ })
+
+ render(conn, "index.json", users: updated_users)
+ end
+
+ def unsuggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.set_suggestion(users, false)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "remove_suggestion"
+ })
+
+ render(conn, "index.json", users: updated_users)
+ end
+
+ def index(conn, params) do
+ {page, page_size} = page_params(params)
+ filters = maybe_parse_filters(params[:filters])
+
+ search_params =
+ %{
+ query: params[:query],
+ page: page,
+ page_size: page_size,
+ tags: params[:tags],
+ name: params[:name],
+ email: params[:email],
+ actor_types: params[:actor_types]
+ }
+ |> Map.merge(filters)
+
+ with {:ok, users, count} <- Search.user(search_params) do
+ render(conn, "index.json", users: users, count: count, page_size: page_size)
+ end
+ end
+
+ @filters ~w(local external active deactivated need_approval unconfirmed is_admin is_moderator)
+
+ @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
+ defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
+
+ defp maybe_parse_filters(filters) do
+ filters
+ |> String.split(",")
+ |> Enum.filter(&Enum.member?(@filters, &1))
+ |> Map.new(&{String.to_existing_atom(&1), true})
+ end
+
+ defp page_params(params) do
+ {
+ fetch_integer_param(params, :page, 1),
+ fetch_integer_param(params, :page_size, @users_page_size)
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/report.ex b/lib/pleroma/web/admin_api/report.ex
new file mode 100644
index 0000000..fa89e34
--- /dev/null
+++ b/lib/pleroma/web/admin_api/report.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.Report do
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.User
+
+ def extract_report_info(
+ %{data: %{"actor" => actor, "object" => [account_ap_id | status_ap_ids]}} = report
+ ) do
+ user = User.get_cached_by_ap_id(actor)
+ account = User.get_cached_by_ap_id(account_ap_id)
+
+ statuses =
+ status_ap_ids
+ |> Enum.reject(&is_nil(&1))
+ |> Enum.map(fn
+ act when is_map(act) ->
+ Activity.get_create_by_object_ap_id_with_object(act["id"]) ||
+ Activity.get_by_ap_id_with_object(act["id"]) || make_fake_activity(act, user)
+
+ act when is_binary(act) ->
+ Activity.get_create_by_object_ap_id_with_object(act) ||
+ Activity.get_by_ap_id_with_object(act)
+ end)
+
+ %{report: report, user: user, account: account, statuses: statuses}
+ end
+
+ defp make_fake_activity(act, user) do
+ %Activity{
+ id: "pleroma:fake:#{act["id"]}",
+ data: %{
+ "actor" => user.ap_id,
+ "type" => "Create",
+ "to" => [],
+ "cc" => [],
+ "object" => act["id"],
+ "published" => act["published"],
+ "id" => act["id"],
+ "context" => "pleroma:fake"
+ },
+ recipients: [user.ap_id],
+ object: %Object{
+ data: %{
+ "actor" => user.ap_id,
+ "type" => "Note",
+ "content" => act["content"],
+ "published" => act["published"],
+ "to" => [],
+ "cc" => [],
+ "id" => act["id"],
+ "context" => "pleroma:fake"
+ }
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex
new file mode 100644
index 0000000..f5195ac
--- /dev/null
+++ b/lib/pleroma/web/admin_api/search.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.Search do
+ import Ecto.Query
+
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ @page_size 50
+
+ @spec user(map()) :: {:ok, [User.t()], pos_integer()}
+ def user(params \\ %{}) do
+ query =
+ params
+ |> Map.drop([:page, :page_size])
+ |> Map.put(:invisible, false)
+ |> User.Query.build()
+ |> order_by(desc: :id)
+
+ paginated_query =
+ User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size)
+
+ count = Repo.aggregate(query, :count, :id)
+
+ results = Repo.all(paginated_query)
+ {:ok, results, count}
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
new file mode 100644
index 0000000..2801522
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -0,0 +1,161 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AccountView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.User
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.AdminAPI.AccountView
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI
+ alias Pleroma.Web.MediaProxy
+
+ def render("index.json", %{users: users, count: count, page_size: page_size}) do
+ %{
+ users: render_many(users, AccountView, "show.json", as: :user),
+ count: count,
+ page_size: page_size
+ }
+ end
+
+ def render("index.json", %{users: users}) do
+ %{
+ users: render_many(users, AccountView, "show.json", as: :user)
+ }
+ end
+
+ def render("credentials.json", %{user: user, for: for_user}) do
+ user = User.sanitize_html(user, User.html_filter_policy(for_user))
+ avatar = User.avatar_url(user) |> MediaProxy.url()
+ banner = User.banner_url(user) |> MediaProxy.url()
+ background = image_url(user.background) |> MediaProxy.url()
+
+ user
+ |> Map.take([
+ :id,
+ :bio,
+ :email,
+ :fields,
+ :name,
+ :nickname,
+ :is_locked,
+ :no_rich_text,
+ :default_scope,
+ :hide_follows,
+ :hide_followers_count,
+ :hide_follows_count,
+ :hide_followers,
+ :hide_favorites,
+ :allow_following_move,
+ :show_role,
+ :skip_thread_containment,
+ :pleroma_settings_store,
+ :raw_fields,
+ :is_discoverable,
+ :actor_type
+ ])
+ |> Map.merge(%{
+ "avatar" => avatar,
+ "banner" => banner,
+ "background" => background
+ })
+ end
+
+ def render("show.json", %{user: user}) do
+ avatar = User.avatar_url(user) |> MediaProxy.url()
+ display_name = Pleroma.HTML.strip_tags(user.name || user.nickname)
+ user = User.sanitize_html(user, FastSanitize.Sanitizer.StripTags)
+
+ %{
+ "id" => user.id,
+ "email" => user.email,
+ "avatar" => avatar,
+ "nickname" => user.nickname,
+ "display_name" => display_name,
+ "is_active" => user.is_active,
+ "local" => user.local,
+ "roles" => roles(user),
+ "tags" => user.tags || [],
+ "is_confirmed" => user.is_confirmed,
+ "is_approved" => user.is_approved,
+ "is_suggested" => user.is_suggested,
+ "url" => user.uri || user.ap_id,
+ "registration_reason" => user.registration_reason,
+ "actor_type" => user.actor_type,
+ "created_at" => CommonAPI.Utils.to_masto_date(user.inserted_at)
+ }
+ end
+
+ def render("created_many.json", %{users: users}) do
+ render_many(users, AccountView, "created.json", as: :user)
+ end
+
+ def render("created.json", %{user: user}) do
+ %{
+ type: "success",
+ code: 200,
+ data: %{
+ nickname: user.nickname,
+ email: user.email
+ }
+ }
+ end
+
+ def render("create_errors.json", %{changesets: changesets}) do
+ render_many(changesets, AccountView, "create_error.json", as: :changeset)
+ end
+
+ def render("create_error.json", %{changeset: %Ecto.Changeset{changes: changes, errors: errors}}) do
+ %{
+ type: "error",
+ code: 409,
+ error: parse_error(errors),
+ data: %{
+ nickname: Map.get(changes, :nickname),
+ email: Map.get(changes, :email)
+ }
+ }
+ end
+
+ def merge_account_views(%User{} = user) do
+ MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true})
+ |> Map.merge(AdminAPI.AccountView.render("show.json", %{user: user}))
+ end
+
+ def merge_account_views(_), do: %{}
+
+ defp parse_error([]), do: ""
+
+ defp parse_error(errors) do
+ ## when nickname is duplicate ap_id constraint error is raised
+ nickname_error = Keyword.get(errors, :nickname) || Keyword.get(errors, :ap_id)
+ email_error = Keyword.get(errors, :email)
+ password_error = Keyword.get(errors, :password)
+
+ cond do
+ nickname_error ->
+ "nickname #{elem(nickname_error, 0)}"
+
+ email_error ->
+ "email #{elem(email_error, 0)}"
+
+ password_error ->
+ "password #{elem(password_error, 0)}"
+
+ true ->
+ ""
+ end
+ end
+
+ defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
+ defp image_url(_), do: nil
+
+ defp roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
+ %{
+ admin: is_admin,
+ moderator: is_moderator
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/announcement_view.ex b/lib/pleroma/web/admin_api/views/announcement_view.ex
new file mode 100644
index 0000000..a35bd60
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/announcement_view.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AnnouncementView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{announcements: announcements}) do
+ render_many(announcements, __MODULE__, "show.json")
+ end
+
+ def render("show.json", %{announcement: announcement}) do
+ Pleroma.Announcement.render_json(announcement, admin: true)
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/chat_view.ex b/lib/pleroma/web/admin_api/views/chat_view.ex
new file mode 100644
index 0000000..d58bf8e
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/chat_view.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ChatView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.Chat
+ alias Pleroma.User
+ alias Pleroma.Web.MastodonAPI
+ alias Pleroma.Web.PleromaAPI
+
+ def render("index.json", %{chats: chats} = opts) do
+ render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats))
+ end
+
+ def render("show.json", %{chat: %Chat{user_id: user_id}} = opts) do
+ user = User.get_by_id(user_id)
+ sender = MastodonAPI.AccountView.render("show.json", user: user, skip_visibility_check: true)
+
+ serialized_chat = PleromaAPI.ChatView.render("show.json", opts)
+
+ serialized_chat
+ |> Map.put(:sender, sender)
+ |> Map.put(:receiver, serialized_chat[:account])
+ |> Map.delete(:account)
+ end
+
+ def render(view, opts), do: PleromaAPI.ChatView.render(view, opts)
+end
diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex
new file mode 100644
index 0000000..f582ad4
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/config_view.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ConfigView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.ConfigDB
+
+ 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: ConfigDB.to_json_types(config.key),
+ group: ConfigDB.to_json_types(config.group),
+ value: ConfigDB.to_json_types(config.value)
+ }
+
+ if config.db != [] do
+ Map.put(map, :db, config.db)
+ else
+ map
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/frontend_view.ex b/lib/pleroma/web/admin_api/views/frontend_view.ex
new file mode 100644
index 0000000..0ca3d67
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/frontend_view.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.FrontendView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{frontends: frontends}) do
+ render_many(frontends, __MODULE__, "show.json")
+ end
+
+ def render("show.json", %{frontend: frontend}) do
+ %{
+ name: frontend["name"],
+ git: frontend["git"],
+ build_url: frontend["build_url"],
+ ref: frontend["ref"],
+ installed: frontend["installed"]
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/invite_view.ex b/lib/pleroma/web/admin_api/views/invite_view.ex
new file mode 100644
index 0000000..76cee3b
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/invite_view.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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
diff --git a/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex b/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
new file mode 100644
index 0000000..b46f54e
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/media_proxy_cache_view.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 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, page_size: page_size, count: count}) do
+ %{
+ urls: urls,
+ count: count,
+ page_size: page_size
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/moderation_log_view.ex b/lib/pleroma/web/admin_api/views/moderation_log_view.ex
new file mode 100644
index 0000000..1f25f19
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/moderation_log_view.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ModerationLogView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.ModerationLog
+
+ def render("index.json", %{log: log}) do
+ %{
+ items: render_many(log.items, __MODULE__, "show.json", as: :log_entry),
+ total: log.count
+ }
+ end
+
+ def render("show.json", %{log_entry: log_entry}) do
+ time =
+ log_entry.inserted_at
+ |> DateTime.from_naive!("Etc/UTC")
+ |> DateTime.to_unix()
+
+ %{
+ id: log_entry.id,
+ data: log_entry.data,
+ time: time,
+ message: ModerationLog.get_log_entry_message(log_entry)
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/o_auth_app_view.ex b/lib/pleroma/web/admin_api/views/o_auth_app_view.ex
new file mode 100644
index 0000000..d1aef0e
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/o_auth_app_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.OAuthAppView do
+ use Pleroma.Web, :view
+ alias Pleroma.Web.MastodonAPI
+
+ def render(view, opts), do: MastodonAPI.AppView.render(view, opts)
+end
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
new file mode 100644
index 0000000..b761dbb
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -0,0 +1,74 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ReportView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.HTML
+ alias Pleroma.User
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.AdminAPI.Report
+ alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ defdelegate merge_account_views(user), to: AdminAPI.AccountView
+
+ def render("index.json", %{reports: reports}) do
+ %{
+ reports:
+ reports[:items]
+ |> Enum.map(&Report.extract_report_info/1)
+ |> Enum.map(&render(__MODULE__, "show.json", &1)),
+ total: reports[:total]
+ }
+ end
+
+ def render("show.json", %{report: report, user: user, account: account, statuses: statuses}) do
+ created_at = Utils.to_masto_date(report.data["published"])
+
+ content =
+ unless is_nil(report.data["content"]) do
+ HTML.filter_tags(report.data["content"])
+ else
+ nil
+ end
+
+ %{
+ id: report.id,
+ account: merge_account_views(account),
+ actor: merge_account_views(user),
+ content: content,
+ created_at: created_at,
+ statuses:
+ StatusView.render("index.json", %{
+ activities: statuses,
+ as: :activity
+ }),
+ state: report.data["state"],
+ notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
+ }
+ end
+
+ def render("index_notes.json", %{notes: notes}) when is_list(notes) do
+ Enum.map(notes, &render(__MODULE__, "show_note.json", Map.from_struct(&1)))
+ end
+
+ def render("index_notes.json", _), do: []
+
+ def render("show_note.json", %{
+ id: id,
+ content: content,
+ user_id: user_id,
+ inserted_at: inserted_at
+ }) do
+ user = User.get_by_id(user_id)
+
+ %{
+ id: id,
+ content: content,
+ user: merge_account_views(user),
+ created_at: Utils.to_masto_date(inserted_at)
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex
new file mode 100644
index 0000000..03b5c44
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/status_view.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.StatusView do
+ use Pleroma.Web, :view
+
+ require Pleroma.Constants
+
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI
+
+ defdelegate merge_account_views(user), to: AdminAPI.AccountView
+
+ def render("index.json", %{total: total} = opts) do
+ %{total: total, activities: safe_render_many(opts.activities, __MODULE__, "show.json", opts)}
+ end
+
+ def render("index.json", opts) do
+ safe_render_many(opts.activities, __MODULE__, "show.json", opts)
+ end
+
+ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
+ user = CommonAPI.get_user(activity.data["actor"])
+
+ MastodonAPI.StatusView.render("show.json", opts)
+ |> Map.merge(%{account: merge_account_views(user)})
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/user_view.ex b/lib/pleroma/web/admin_api/views/user_view.ex
new file mode 100644
index 0000000..f198921
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/user_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.UserView do
+ use Pleroma.Web, :view
+ alias Pleroma.Web.AdminAPI
+
+ def render(view, opts), do: AdminAPI.AccountView.render(view, opts)
+end