Commit cff7bb5c authored by Maksim's avatar Maksim

Merge branch 'develop' into issue/1933-update-dependency

parents cd2423d7 a6d3bb5f
...@@ -27,6 +27,8 @@ erl_crash.dump ...@@ -27,6 +27,8 @@ erl_crash.dump
# variables. # variables.
/config/*.secret.exs /config/*.secret.exs
/config/generated_config.exs /config/generated_config.exs
/config/*.env
# Database setup file, some may forget to delete it # Database setup file, some may forget to delete it
/config/setup_db.psql /config/setup_db.psql
......
...@@ -67,6 +67,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ...@@ -67,6 +67,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Support pagination in emoji packs API (for packs and for files in pack) - Support pagination in emoji packs API (for packs and for files in pack)
- Support for viewing instances favicons next to posts and accounts - Support for viewing instances favicons next to posts and accounts
- Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata. - Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata.
- "By approval" registrations mode.
- Configuration: Added `:welcome` settings for the welcome message to newly registered users. - Configuration: Added `:welcome` settings for the welcome message to newly registered users.
<details> <details>
......
...@@ -205,6 +205,7 @@ ...@@ -205,6 +205,7 @@
registrations_open: true, registrations_open: true,
invites_enabled: false, invites_enabled: false,
account_activation_required: false, account_activation_required: false,
account_approval_required: false,
federating: true, federating: true,
federation_incoming_replies_max_depth: 100, federation_incoming_replies_max_depth: 100,
federation_reachability_timeout_days: 7, federation_reachability_timeout_days: 7,
...@@ -237,6 +238,7 @@ ...@@ -237,6 +238,7 @@
max_remote_account_fields: 20, max_remote_account_fields: 20,
account_field_name_length: 512, account_field_name_length: 512,
account_field_value_length: 2048, account_field_value_length: 2048,
registration_reason_length: 500,
external_user_synchronization: true, external_user_synchronization: true,
extended_nickname_format: true, extended_nickname_format: true,
cleanup_attachments: false, cleanup_attachments: false,
......
...@@ -661,6 +661,11 @@ ...@@ -661,6 +661,11 @@
type: :boolean, type: :boolean,
description: "Require users to confirm their emails before signing in" description: "Require users to confirm their emails before signing in"
}, },
%{
key: :account_approval_required,
type: :boolean,
description: "Require users to be manually approved by an admin before signing in"
},
%{ %{
key: :federating, key: :federating,
type: :boolean, type: :boolean,
...@@ -874,6 +879,14 @@ ...@@ -874,6 +879,14 @@
2048 2048
] ]
}, },
%{
key: :registration_reason_length,
type: :integer,
description: "Maximum registration reason length. Default: 500.",
suggestions: [
500
]
},
%{ %{
key: :external_user_synchronization, key: :external_user_synchronization,
type: :boolean, type: :boolean,
......
...@@ -19,6 +19,7 @@ Configuration options: ...@@ -19,6 +19,7 @@ Configuration options:
- `local`: only local users - `local`: only local users
- `external`: only external users - `external`: only external users
- `active`: only active users - `active`: only active users
- `need_approval`: only unapproved users
- `deactivated`: only deactivated users - `deactivated`: only deactivated users
- `is_admin`: users with admin role - `is_admin`: users with admin role
- `is_moderator`: users with moderator role - `is_moderator`: users with moderator role
...@@ -46,7 +47,10 @@ Configuration options: ...@@ -46,7 +47,10 @@ Configuration options:
"local": bool, "local": bool,
"tags": array, "tags": array,
"avatar": string, "avatar": string,
"display_name": string "display_name": string,
"confirmation_pending": bool,
"approval_pending": bool,
"registration_reason": string,
}, },
... ...
] ]
...@@ -242,6 +246,24 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret ...@@ -242,6 +246,24 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
} }
``` ```
## `PATCH /api/pleroma/admin/users/approve`
### Approve user
- Params:
- `nicknames`: nicknames array
- Response:
```json
{
users: [
{
// user object
}
]
}
```
## `GET /api/pleroma/admin/users/:nickname_or_id` ## `GET /api/pleroma/admin/users/:nickname_or_id`
### Retrive the details of a user ### Retrive the details of a user
......
# Generate release environment file
```sh tab="OTP"
./bin/pleroma_ctl release_env gen
```
```sh tab="From Source"
mix pleroma.release_env gen
```
...@@ -33,6 +33,7 @@ To add configuration to your config file, you can copy it from the base config. ...@@ -33,6 +33,7 @@ To add configuration to your config file, you can copy it from the base config.
* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false. * `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`). * `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
* `account_activation_required`: Require users to confirm their emails before signing in. * `account_activation_required`: Require users to confirm their emails before signing in.
* `account_approval_required`: Require users to be manually approved by an admin before signing in.
* `federating`: Enable federation with other instances. * `federating`: Enable federation with other instances.
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes. * `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it. * `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
...@@ -58,6 +59,7 @@ To add configuration to your config file, you can copy it from the base config. ...@@ -58,6 +59,7 @@ To add configuration to your config file, you can copy it from the base config.
* `max_remote_account_fields`: The maximum number of custom fields in the remote user profile (default: `20`). * `max_remote_account_fields`: The maximum number of custom fields in the remote user profile (default: `20`).
* `account_field_name_length`: An account field name maximum length (default: `512`). * `account_field_name_length`: An account field name maximum length (default: `512`).
* `account_field_value_length`: An account field value maximum length (default: `2048`). * `account_field_value_length`: An account field value maximum length (default: `2048`).
* `registration_reason_length`: Maximum registration reason length (default: `500`).
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users. * `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances. * `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
......
...@@ -121,6 +121,9 @@ chown -R pleroma /etc/pleroma ...@@ -121,6 +121,9 @@ chown -R pleroma /etc/pleroma
# Run the config generator # Run the config generator
su pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql" su pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
# Run the environment file generator.
su pleroma -s $SHELL -lc "./bin/pleroma_ctl release_env gen"
# Create the postgres database # Create the postgres database
su postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql" su postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
...@@ -131,7 +134,7 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate" ...@@ -131,7 +134,7 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate"
# su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" # su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
# Start the instance to verify that everything is working as expected # Start the instance to verify that everything is working as expected
su pleroma -s $SHELL -lc "./bin/pleroma daemon" su pleroma -s $SHELL -lc "export $(cat /opt/pleroma/config/pleroma.env); ./bin/pleroma daemon"
# Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly # Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly
sleep 20 && curl http://localhost:4000/api/v1/instance sleep 20 && curl http://localhost:4000/api/v1/instance
...@@ -200,6 +203,7 @@ rc-update add pleroma ...@@ -200,6 +203,7 @@ rc-update add pleroma
# Copy the service into a proper directory # Copy the service into a proper directory
cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
# Start pleroma and enable it on boot # Start pleroma and enable it on boot
systemctl start pleroma systemctl start pleroma
systemctl enable pleroma systemctl enable pleroma
...@@ -275,4 +279,3 @@ This will create an account withe the username of 'joeuser' with the email addre ...@@ -275,4 +279,3 @@ This will create an account withe the username of 'joeuser' with the email addre
## Questions ## Questions
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**. Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
...@@ -8,6 +8,7 @@ pidfile="/var/run/pleroma.pid" ...@@ -8,6 +8,7 @@ pidfile="/var/run/pleroma.pid"
directory=/opt/pleroma directory=/opt/pleroma
healthcheck_delay=60 healthcheck_delay=60
healthcheck_timer=30 healthcheck_timer=30
export $(cat /opt/pleroma/config/pleroma.env)
: ${pleroma_port:-4000} : ${pleroma_port:-4000}
......
...@@ -17,6 +17,8 @@ Environment="MIX_ENV=prod" ...@@ -17,6 +17,8 @@ Environment="MIX_ENV=prod"
Environment="HOME=/var/lib/pleroma" Environment="HOME=/var/lib/pleroma"
; Path to the folder containing the Pleroma installation. ; Path to the folder containing the Pleroma installation.
WorkingDirectory=/opt/pleroma WorkingDirectory=/opt/pleroma
; Path to the environment file. the file contains RELEASE_COOKIE and etc
EnvironmentFile=/opt/pleroma/config/pleroma.env
; Path to the Mix binary. ; Path to the Mix binary.
ExecStart=/usr/bin/mix phx.server ExecStart=/usr/bin/mix phx.server
......
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.ReleaseEnv do
use Mix.Task
import Mix.Pleroma
@shortdoc "Generate Pleroma environment file."
@moduledoc File.read!("docs/administration/CLI_tasks/release_environments.md")
def run(["gen" | rest]) do
{options, [], []} =
OptionParser.parse(
rest,
strict: [
force: :boolean,
path: :string
],
aliases: [
p: :path,
f: :force
]
)
file_path =
get_option(
options,
:path,
"Environment file path",
"./config/pleroma.env"
)
env_path = Path.expand(file_path)
proceed? =
if File.exists?(env_path) do
get_option(
options,
:force,
"Environment file already exists. Do you want to overwrite the #{env_path} file? (y/n)",
"n"
) === "y"
else
true
end
if proceed? do
case do_generate(env_path) do
{:error, reason} ->
shell_error(
File.Error.message(%{action: "write to file", reason: reason, path: env_path})
)
_ ->
shell_info("\nThe file generated: #{env_path}.\n")
shell_info("""
WARNING: before start pleroma app please make sure to make the file read-only and non-modifiable.
Example:
chmod 0444 #{file_path}
chattr +i #{file_path}
""")
end
else
shell_info("\nThe file is exist. #{env_path}.\n")
end
end
def do_generate(path) do
content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}"
File.mkdir_p!(Path.dirname(path))
File.write(path, content)
end
end
...@@ -8,6 +8,7 @@ defmodule Pleroma.Emails.AdminEmail do ...@@ -8,6 +8,7 @@ defmodule Pleroma.Emails.AdminEmail do
import Swoosh.Email import Swoosh.Email
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.HTML
alias Pleroma.Web.Router.Helpers alias Pleroma.Web.Router.Helpers
defp instance_config, do: Config.get(:instance) defp instance_config, do: Config.get(:instance)
...@@ -82,4 +83,18 @@ def report(to, reporter, account, statuses, comment) do ...@@ -82,4 +83,18 @@ def report(to, reporter, account, statuses, comment) do
|> subject("#{instance_name()} Report") |> subject("#{instance_name()} Report")
|> html_body(html_body) |> html_body(html_body)
end end
def new_unapproved_registration(to, account) do
html_body = """
<p>New account for review: <a href="#{user_url(account)}">@#{account.nickname}</a></p>
<blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
<a href="#{Pleroma.Web.base_url()}/pleroma/admin">Visit AdminFE</a>
"""
new()
|> to({to.name, to.email})
|> from({instance_name(), instance_notify_email()})
|> subject("New account up for review on #{instance_name()} (@#{account.nickname})")
|> html_body(html_body)
end
end end
...@@ -409,6 +409,17 @@ def get_log_entry_message(%ModerationLog{ ...@@ -409,6 +409,17 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}" "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
end end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "approve",
"subject" => users
}
}) do
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t() @spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
......
...@@ -42,7 +42,12 @@ defmodule Pleroma.User do ...@@ -42,7 +42,12 @@ defmodule Pleroma.User do
require Logger require Logger
@type t :: %__MODULE__{} @type t :: %__MODULE__{}
@type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending @type account_status ::
:active
| :deactivated
| :password_reset_pending
| :confirmation_pending
| :approval_pending
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
...@@ -106,6 +111,8 @@ defmodule Pleroma.User do ...@@ -106,6 +111,8 @@ defmodule Pleroma.User do
field(:locked, :boolean, default: false) field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false) field(:confirmation_pending, :boolean, default: false)
field(:password_reset_pending, :boolean, default: false) field(:password_reset_pending, :boolean, default: false)
field(:approval_pending, :boolean, default: false)
field(:registration_reason, :string, default: nil)
field(:confirmation_token, :string, default: nil) field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public") field(:default_scope, :string, default: "public")
field(:domain_blocks, {:array, :string}, default: []) field(:domain_blocks, {:array, :string}, default: [])
...@@ -262,6 +269,7 @@ def binary_id(%User{} = user), do: binary_id(user.id) ...@@ -262,6 +269,7 @@ def binary_id(%User{} = user), do: binary_id(user.id)
@spec account_status(User.t()) :: account_status() @spec account_status(User.t()) :: account_status()
def account_status(%User{deactivated: true}), do: :deactivated def account_status(%User{deactivated: true}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
def account_status(%User{approval_pending: true}), do: :approval_pending
def account_status(%User{confirmation_pending: true}) do def account_status(%User{confirmation_pending: true}) do
if Config.get([:instance, :account_activation_required]) do if Config.get([:instance, :account_activation_required]) do
...@@ -633,6 +641,7 @@ def force_password_reset(user), do: update_password_reset_pending(user, true) ...@@ -633,6 +641,7 @@ def force_password_reset(user), do: update_password_reset_pending(user, true)
def register_changeset(struct, params \\ %{}, opts \\ []) do def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Config.get([:instance, :user_bio_length], 5000) bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100) name_limit = Config.get([:instance, :user_name_length], 100)
reason_limit = Config.get([:instance, :registration_reason_length], 500)
params = Map.put_new(params, :accepts_chat_messages, true) params = Map.put_new(params, :accepts_chat_messages, true)
need_confirmation? = need_confirmation? =
...@@ -642,8 +651,16 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do ...@@ -642,8 +651,16 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
opts[:need_confirmation] opts[:need_confirmation]
end end
need_approval? =
if is_nil(opts[:need_approval]) do
Config.get([:instance, :account_approval_required])
else
opts[:need_approval]
end
struct struct
|> confirmation_changeset(need_confirmation: need_confirmation?) |> confirmation_changeset(need_confirmation: need_confirmation?)
|> approval_changeset(need_approval: need_approval?)
|> cast(params, [ |> cast(params, [
:bio, :bio,
:raw_bio, :raw_bio,
...@@ -653,7 +670,8 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do ...@@ -653,7 +670,8 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
:password, :password,
:password_confirmation, :password_confirmation,
:emoji, :emoji,
:accepts_chat_messages :accepts_chat_messages,
:registration_reason
]) ])
|> validate_required([:name, :nickname, :password, :password_confirmation]) |> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password) |> validate_confirmation(:password)
...@@ -664,6 +682,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do ...@@ -664,6 +682,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|> validate_format(:email, @email_regex) |> validate_format(:email, @email_regex)
|> validate_length(:bio, max: bio_limit) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit) |> validate_length(:name, min: 1, max: name_limit)
|> validate_length(:registration_reason, max: reason_limit)
|> maybe_validate_required_email(opts[:external]) |> maybe_validate_required_email(opts[:external])
|> put_password_hash |> put_password_hash
|> put_ap_id() |> put_ap_id()
...@@ -1494,6 +1513,19 @@ def deactivate(%User{} = user, status) do ...@@ -1494,6 +1513,19 @@ def deactivate(%User{} = user, status) do
end end
end end
def approve(users) when is_list(users) do
Repo.transaction(fn ->
Enum.map(users, fn user ->
with {:ok, user} <- approve(user), do: user
end)
end)
end
def approve(%User{} = user) do
change(user, approval_pending: false)
|> update_and_set_cache()
end
def update_notification_settings(%User{} = user, settings) do def update_notification_settings(%User{} = user, settings) do
user user
|> cast(%{notification_settings: settings}, []) |> cast(%{notification_settings: settings}, [])
...@@ -1520,12 +1552,17 @@ defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate ...@@ -1520,12 +1552,17 @@ defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate
defp delete_or_deactivate(%User{local: true} = user) do defp delete_or_deactivate(%User{local: true} = user) do
status = account_status(user) status = account_status(user)
if status == :confirmation_pending do case status do
delete_and_invalidate_cache(user) :confirmation_pending ->
else delete_and_invalidate_cache(user)
user
|> change(%{deactivated: true, email: nil}) :approval_pending ->
|> update_and_set_cache() delete_and_invalidate_cache(user)
_ ->
user
|> change(%{deactivated: true, email: nil})
|> update_and_set_cache()
end end
end end
...@@ -2178,6 +2215,12 @@ def confirmation_changeset(user, need_confirmation: need_confirmation?) do ...@@ -2178,6 +2215,12 @@ def confirmation_changeset(user, need_confirmation: need_confirmation?) do
cast(user, params, [:confirmation_pending, :confirmation_token]) cast(user, params, [:confirmation_pending, :confirmation_token])
end end
@spec approval_changeset(User.t(), keyword()) :: Changeset.t()
def approval_changeset(user, need_approval: need_approval?) do
params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
cast(user, params, [:approval_pending])
end
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
if id not in user.pinned_activities do if id not in user.pinned_activities do
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0) max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
......
...@@ -42,6 +42,7 @@ defmodule Pleroma.User.Query do ...@@ -42,6 +42,7 @@ defmodule Pleroma.User.Query do
external: boolean(), external: boolean(),
active: boolean(), active: boolean(),
deactivated: boolean(), deactivated: boolean(),
need_approval: boolean(),
is_admin: boolean(), is_admin: boolean(),
is_moderator: boolean(), is_moderator: boolean(),
super_users: boolean(), super_users: boolean(),
...@@ -146,6 +147,10 @@ defp compose_query({:deactivated, true}, query) do ...@@ -146,6 +147,10 @@ defp compose_query({:deactivated, true}, query) do
|> where([u], not is_nil(u.nickname)) |> where([u], not is_nil(u.nickname))
end end
defp compose_query({:need_approval, _}, query) do
where(query, [u], u.approval_pending)
end
defp compose_query({:followers, %User{id: id}}, query) do defp compose_query({:followers, %User{id: id}}, query) do
query query
|> where([u], u.id != ^id) |> where([u], u.id != ^id)
......
...@@ -27,7 +27,8 @@ def filter_by_summary( ...@@ -27,7 +27,8 @@ def filter_by_summary(
def filter_by_summary(_in_reply_to, child), do: child def filter_by_summary(_in_reply_to, child), do: child
def filter(%{"type" => "Create", "object" => child_object} = object) do def filter(%{"type" => "Create", "object" => child_object} = object)
when is_map(child_object) do
child = child =
child_object["inReplyTo"] child_object["inReplyTo"]
|> Object.normalize(child_object["inReplyTo"]) |> Object.normalize(child_object["inReplyTo"])
......
...@@ -44,6 +44,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ...@@ -44,6 +44,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
:user_toggle_activation, :user_toggle_activation,
:user_activate, :user_activate,
:user_deactivate, :user_deactivate,
:user_approve,
:tag_users, :tag_users,
:untag_users, :untag_users,
:right_add, :right_add,
...@@ -303,6 +304,21 @@ def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nickname ...@@ -303,6 +304,21 @@ def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nickname
|> render("index.json", %{users: Keyword.values(updated_users)}) |> render("index.json", %{users: Keyword.values(updated_users)})
end end
def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)