Merge branch 'develop' into feature/database-compaction

This commit is contained in:
rinpatch 2019-04-17 12:22:32 +03:00
commit 627e5a0a49
1271 changed files with 42114 additions and 70683 deletions

View file

@ -6,9 +6,9 @@ defmodule Mix.Tasks.CompactDatabase do
require Logger
use Mix.Task
import Mix.Ecto
import Ecto.Query
alias Pleroma.{Repo, Object, Activity}
alias Pleroma.Activity
alias Pleroma.Repo
defp maybe_compact(%Activity{data: %{"object" => %{"id" => object_id}}} = activity) do
data =
@ -33,7 +33,7 @@ defmodule Mix.Tasks.CompactDatabase do
)
end
def run(args) do
def run(_args) do
Application.ensure_all_started(:pleroma)
max = Repo.aggregate(Activity, :max, :id)

View file

@ -1,19 +0,0 @@
defmodule Mix.Tasks.DeactivateUser do
use Mix.Task
alias Pleroma.User
@moduledoc """
Deactivates a user (local or remote)
Usage: ``mix deactivate_user <nickname>``
Example: ``mix deactivate_user lain``
"""
def run([nickname]) do
Mix.Task.run("app.start")
with user <- User.get_by_nickname(nickname) do
User.deactivate(user)
end
end
end

View file

@ -1,47 +0,0 @@
defmodule Mix.Tasks.GenerateConfig do
use Mix.Task
@moduledoc """
Generate a new config
## Usage
``mix generate_config``
This mix task is interactive, and will overwrite the config present at ``config/generated_config.exs``.
"""
def run(_) do
IO.puts("Answer a few questions to generate a new config\n")
IO.puts("--- THIS WILL OVERWRITE YOUR config/generated_config.exs! ---\n")
domain = IO.gets("What is your domain name? (e.g. pleroma.soykaf.com): ") |> String.trim()
name = IO.gets("What is the name of your instance? (e.g. Pleroma/Soykaf): ") |> String.trim()
email = IO.gets("What's your admin email address: ") |> String.trim()
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
dbpass = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
resultSql = EEx.eval_file("lib/mix/tasks/sample_psql.eex", dbpass: dbpass)
result =
EEx.eval_file(
"lib/mix/tasks/sample_config.eex",
domain: domain,
email: email,
name: name,
secret: secret,
dbpass: dbpass
)
IO.puts(
"\nWriting config to config/generated_config.exs.\n\nCheck it and configure your database, then copy it to either config/dev.secret.exs or config/prod.secret.exs"
)
File.write("config/generated_config.exs", result)
IO.puts(
"\nWriting setup_db.psql, please run it as postgre superuser, i.e.: sudo su postgres -c 'psql -f config/setup_db.psql'"
)
File.write("config/setup_db.psql", resultSql)
end
end

View file

@ -1,32 +0,0 @@
defmodule Mix.Tasks.GenerateInviteToken do
use Mix.Task
@moduledoc """
Generates invite token
This is in the form of a URL to be used by the Invited user to register themselves.
## Usage
``mix generate_invite_token``
"""
def run([]) do
Mix.Task.run("app.start")
with {:ok, token} <- Pleroma.UserInviteToken.create_token() do
IO.puts("Generated user invite token")
IO.puts(
"Url: #{
Pleroma.Web.Router.Helpers.redirect_url(
Pleroma.Web.Endpoint,
:registration_page,
token.token
)
}"
)
else
_ ->
IO.puts("Error creating token")
end
end
end

View file

@ -1,33 +0,0 @@
defmodule Mix.Tasks.GeneratePasswordReset do
use Mix.Task
alias Pleroma.User
@moduledoc """
Generate password reset link for user
Usage: ``mix generate_password_reset <nickname>``
Example: ``mix generate_password_reset lain``
"""
def run([nickname]) do
Mix.Task.run("app.start")
with %User{local: true} = user <- User.get_by_nickname(nickname),
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
IO.puts("Generated password reset token for #{user.nickname}")
IO.puts(
"Url: #{
Pleroma.Web.Router.Helpers.util_url(
Pleroma.Web.Endpoint,
:show_password_reset,
token.token
)
}"
)
else
_ ->
IO.puts("No local user #{nickname}")
end
end
end

View file

@ -1,37 +0,0 @@
defmodule Mix.Tasks.SetModerator do
@moduledoc """
Set moderator to a local user
Usage: ``mix set_moderator <nickname>``
Example: ``mix set_moderator lain``
"""
use Mix.Task
import Mix.Ecto
alias Pleroma.{Repo, User}
def run([nickname | rest]) do
Application.ensure_all_started(:pleroma)
moderator =
case rest do
[moderator] -> moderator == "true"
_ -> true
end
with %User{local: true} = user <- User.get_by_nickname(nickname) do
info =
user.info
|> Map.put("is_moderator", !!moderator)
cng = User.info_changeset(user, %{info: info})
{:ok, user} = User.update_and_set_cache(cng)
IO.puts("Moderator status of #{nickname}: #{user.info["is_moderator"]}")
else
_ ->
IO.puts("No local user #{nickname}")
end
end
end

View file

@ -0,0 +1,28 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Common do
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Mix.Task.run("app.start")
end
def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
Keyword.get(options, opt) ||
case Mix.shell().prompt("#{prompt} [#{defname || defval}]") do
"\n" ->
case defval do
nil -> get_option(options, opt, prompt, defval)
defval -> defval
end
opt ->
opt |> String.trim()
end
end
def escape_sh_path(path) do
~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
end
end

View file

@ -0,0 +1,213 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Instance do
use Mix.Task
alias Mix.Tasks.Pleroma.Common
@shortdoc "Manages Pleroma instance"
@moduledoc """
Manages Pleroma instance.
## Generate a new instance config.
mix pleroma.instance gen [OPTION...]
If any options are left unspecified, you will be prompted interactively
## Options
- `-f`, `--force` - overwrite any output files
- `-o PATH`, `--output PATH` - the output file for the generated configuration
- `--output-psql PATH` - the output file for the generated PostgreSQL setup
- `--domain DOMAIN` - the domain of your instance
- `--instance-name INSTANCE_NAME` - the name of your instance
- `--admin-email ADMIN_EMAIL` - the email address of the instance admin
- `--notify-email NOTIFY_EMAIL` - email address for notifications
- `--dbhost HOSTNAME` - the hostname of the PostgreSQL database to use
- `--dbname DBNAME` - the name of the database to use
- `--dbuser DBUSER` - the user (aka role) to use for the database connection
- `--dbpass DBPASS` - the password to use for the database connection
- `--indexable Y/N` - Allow/disallow indexing site by search engines
"""
def run(["gen" | rest]) do
{options, [], []} =
OptionParser.parse(
rest,
strict: [
force: :boolean,
output: :string,
output_psql: :string,
domain: :string,
instance_name: :string,
admin_email: :string,
notify_email: :string,
dbhost: :string,
dbname: :string,
dbuser: :string,
dbpass: :string,
indexable: :string
],
aliases: [
o: :output,
f: :force
]
)
paths =
[config_path, psql_path] = [
Keyword.get(options, :output, "config/generated_config.exs"),
Keyword.get(options, :output_psql, "config/setup_db.psql")
]
will_overwrite = Enum.filter(paths, &File.exists?/1)
proceed? = Enum.empty?(will_overwrite) or Keyword.get(options, :force, false)
if proceed? do
[domain, port | _] =
String.split(
Common.get_option(
options,
:domain,
"What domain will your instance use? (e.g pleroma.soykaf.com)"
),
":"
) ++ [443]
name =
Common.get_option(
options,
:instance_name,
"What is the name of your instance? (e.g. Pleroma/Soykaf)"
)
email = Common.get_option(options, :admin_email, "What is your admin email address?")
notify_email =
Common.get_option(
options,
:notify_email,
"What email address do you want to use for sending email notifications?",
email
)
indexable =
Common.get_option(
options,
:indexable,
"Do you want search engines to index your site? (y/n)",
"y"
) === "y"
dbhost =
Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
dbname =
Common.get_option(options, :dbname, "What is the name of your database?", "pleroma_dev")
dbuser =
Common.get_option(
options,
:dbuser,
"What is the user used to connect to your database?",
"pleroma"
)
dbpass =
Common.get_option(
options,
:dbpass,
"What is the password used to connect to your database?",
:crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64),
"autogenerated"
)
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
result_config =
EEx.eval_file(
"sample_config.eex" |> Path.expand(__DIR__),
domain: domain,
port: port,
email: email,
notify_email: notify_email,
name: name,
dbhost: dbhost,
dbname: dbname,
dbuser: dbuser,
dbpass: dbpass,
version: Pleroma.Mixfile.project() |> Keyword.get(:version),
secret: secret,
signing_salt: signing_salt,
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false)
)
result_psql =
EEx.eval_file(
"sample_psql.eex" |> Path.expand(__DIR__),
dbname: dbname,
dbuser: dbuser,
dbpass: dbpass
)
Mix.shell().info(
"Writing config to #{config_path}. You should rename it to config/prod.secret.exs or config/dev.secret.exs."
)
File.write(config_path, result_config)
Mix.shell().info("Writing #{psql_path}.")
File.write(psql_path, result_psql)
write_robots_txt(indexable)
Mix.shell().info(
"\n" <>
"""
To get started:
1. Verify the contents of the generated files.
2. Run `sudo -u postgres psql -f #{Common.escape_sh_path(psql_path)}`.
""" <>
if config_path in ["config/dev.secret.exs", "config/prod.secret.exs"] do
""
else
"3. Run `mv #{Common.escape_sh_path(config_path)} 'config/prod.secret.exs'`."
end
)
else
Mix.shell().error(
"The task would have overwritten the following files:\n" <>
(Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <>
"Rerun with `--force` to overwrite them."
)
end
end
defp write_robots_txt(indexable) do
robots_txt =
EEx.eval_file(
Path.expand("robots_txt.eex", __DIR__),
indexable: indexable
)
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
unless File.exists?(static_dir) do
File.mkdir_p!(static_dir)
end
robots_txt_path = Path.join(static_dir, "robots.txt")
if File.exists?(robots_txt_path) do
File.cp!(robots_txt_path, "#{robots_txt_path}.bak")
Mix.shell().info("Backing up existing robots.txt to #{robots_txt_path}.bak")
end
File.write(robots_txt_path, robots_txt)
Mix.shell().info("Writing #{robots_txt_path}.")
end
end

View file

@ -0,0 +1,47 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Relay do
use Mix.Task
alias Mix.Tasks.Pleroma.Common
alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Manages remote relays"
@moduledoc """
Manages remote relays
## Follow a remote relay
``mix pleroma.relay follow <relay_url>``
Example: ``mix pleroma.relay follow https://example.org/relay``
## Unfollow a remote relay
``mix pleroma.relay unfollow <relay_url>``
Example: ``mix pleroma.relay unfollow https://example.org/relay``
"""
def run(["follow", target]) do
Common.start_pleroma()
with {:ok, _activity} <- Relay.follow(target) do
# put this task to sleep to allow the genserver to push out the messages
:timer.sleep(500)
else
{:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
end
end
def run(["unfollow", target]) do
Common.start_pleroma()
with {:ok, _activity} <- Relay.unfollow(target) do
# put this task to sleep to allow the genserver to push out the messages
:timer.sleep(500)
else
{:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
end
end
end

View file

@ -0,0 +1,2 @@
User-Agent: *
Disallow: <%= if indexable, do: "", else: "/" %>

View file

@ -0,0 +1,32 @@
# Pleroma: A lightweight social networking server
# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.RobotsTxt do
use Mix.Task
@shortdoc "Generate robots.txt"
@moduledoc """
Generates robots.txt
## Overwrite robots.txt to disallow all
mix pleroma.robots_txt disallow_all
This will write a robots.txt that will hide all paths on your instance
from search engines and other robots that obey robots.txt
"""
def run(["disallow_all"]) do
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
if !File.exists?(static_dir) do
File.mkdir_p!(static_dir)
end
robots_txt_path = Path.join(static_dir, "robots.txt")
robots_txt_content = "User-Agent: *\nDisallow: /\n"
File.write!(robots_txt_path, robots_txt_content, [:write])
end
end

View file

@ -1,12 +1,19 @@
# Pleroma instance configuration
# NOTE: This file should not be committed to a repo or otherwise made public
# without removing sensitive information.
use Mix.Config
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "<%= domain %>", scheme: "https", port: 443],
secret_key_base: "<%= secret %>"
url: [host: "<%= domain %>", scheme: "https", port: <%= port %>],
secret_key_base: "<%= secret %>",
signing_salt: "<%= signing_salt %>"
config :pleroma, :instance,
name: "<%= name %>",
email: "<%= email %>",
notify_email: "<%= notify_email %>",
limit: 5000,
registrations_open: true,
dedupe_media: false
@ -16,15 +23,20 @@ config :pleroma, :media_proxy,
redirect_on_failure: true
#base_url: "https://cache.pleroma.social"
# Configure your database
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: "pleroma",
username: "<%= dbuser %>",
password: "<%= dbpass %>",
database: "pleroma_dev",
hostname: "localhost",
database: "<%= dbname %>",
hostname: "<%= dbhost %>",
pool_size: 10
# Configure web push notifications
config :web_push_encryption, :vapid_details,
subject: "mailto:<%= email %>",
public_key: "<%= web_push_public_key %>",
private_key: "<%= web_push_private_key %>"
# Enable Strict-Transport-Security once SSL is working:
# config :pleroma, :http_security,
# sts: true
@ -50,9 +62,9 @@ config :pleroma, Pleroma.Repo,
# Configure Openstack Swift support if desired.
#
# Many openstack deployments are different, so config is left very open with
# no assumptions made on which provider you're using. This should allow very
#
# Many openstack deployments are different, so config is left very open with
# no assumptions made on which provider you're using. This should allow very
# wide support without needing separate handlers for OVH, Rackspace, etc.
#
# config :pleroma, Pleroma.Uploaders.Swift,
@ -64,4 +76,3 @@ config :pleroma, Pleroma.Repo,
# storage_url: "https://swift-endpoint.prodider.com/v1/AUTH_<tenant>/<container>",
# object_url: "https://cdn-endpoint.provider.com/<container>"
#

View file

@ -1,6 +1,6 @@
CREATE USER pleroma WITH ENCRYPTED PASSWORD '<%= dbpass %>';
CREATE DATABASE pleroma_dev OWNER pleroma;
\c pleroma_dev;
CREATE USER <%= dbuser %> WITH ENCRYPTED PASSWORD '<%= dbpass %>';
CREATE DATABASE <%= dbname %> OWNER <%= dbuser %>;
\c <%= dbname %>;
--Extensions made by ecto.migrate that need superuser access
CREATE EXTENSION IF NOT EXISTS citext;
CREATE EXTENSION IF NOT EXISTS pg_trgm;

View file

@ -1,16 +1,30 @@
defmodule Mix.Tasks.MigrateLocalUploads do
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Uploads do
use Mix.Task
import Mix.Ecto
alias Pleroma.{Upload, Uploaders.Local, Uploaders.S3}
alias Mix.Tasks.Pleroma.Common
alias Pleroma.Upload
alias Pleroma.Uploaders.Local
require Logger
@log_every 50
@shortdoc "Migrate uploads from local to remote storage"
def run([target_uploader | args]) do
@shortdoc "Migrates uploads from local to remote storage"
@moduledoc """
Manages uploads
## Migrate uploads from local to remote storage
mix pleroma.uploads migrate_local TARGET_UPLOADER [OPTIONS...]
Options:
- `--delete` - delete local uploads after migrating them to the target uploader
A list of available uploaders can be seen in config.exs
"""
def run(["migrate_local", target_uploader | args]) do
delete? = Enum.member?(args, "--delete")
Application.ensure_all_started(:pleroma)
Common.start_pleroma()
local_path = Pleroma.Config.get!([Local, :uploads])
uploader = Module.concat(Pleroma.Uploaders, target_uploader)
@ -24,10 +38,10 @@ defmodule Mix.Tasks.MigrateLocalUploads do
Pleroma.Config.put([Upload, :uploader], uploader)
end
Logger.info("Migrating files from local #{local_path} to #{to_string(uploader)}")
Mix.shell().info("Migrating files from local #{local_path} to #{to_string(uploader)}")
if delete? do
Logger.warn(
Mix.shell().info(
"Attention: uploaded files will be deleted, hope you have backups! (--delete ; cancel with ^C)"
)
@ -54,7 +68,7 @@ defmodule Mix.Tasks.MigrateLocalUploads do
File.exists?(root_path) ->
file = Path.basename(id)
[hash, ext] = String.split(id, ".")
hash = Path.rootname(id)
{%Pleroma.Upload{id: hash, name: file, path: file, tempfile: root_path}, root_path}
true ->
@ -64,7 +78,7 @@ defmodule Mix.Tasks.MigrateLocalUploads do
|> Enum.filter(& &1)
total_count = length(uploads)
Logger.info("Found #{total_count} uploads")
Mix.shell().info("Found #{total_count} uploads")
uploads
|> Task.async_stream(
@ -76,22 +90,19 @@ defmodule Mix.Tasks.MigrateLocalUploads do
:ok
error ->
Logger.error("failed to upload #{inspect(upload.path)}: #{inspect(error)}")
Mix.shell().error("failed to upload #{inspect(upload.path)}: #{inspect(error)}")
end
end,
timeout: 150_000
)
|> Stream.chunk_every(@log_every)
# credo:disable-for-next-line Credo.Check.Warning.UnusedEnumOperation
|> Enum.reduce(0, fn done, count ->
count = count + length(done)
Logger.info("Uploaded #{count}/#{total_count} files")
Mix.shell().info("Uploaded #{count}/#{total_count} files")
count
end)
Logger.info("Done!")
end
def run(_) do
Logger.error("Usage: migrate_local_uploads S3|Swift [--delete]")
Mix.shell().info("Done!")
end
end

View file

@ -0,0 +1,429 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.User do
use Mix.Task
import Ecto.Changeset
alias Mix.Tasks.Pleroma.Common
alias Pleroma.User
alias Pleroma.UserInviteToken
@shortdoc "Manages Pleroma users"
@moduledoc """
Manages Pleroma users.
## Create a new user.
mix pleroma.user new NICKNAME EMAIL [OPTION...]
Options:
- `--name NAME` - the user's name (i.e., "Lain Iwakura")
- `--bio BIO` - the user's bio
- `--password PASSWORD` - the user's password
- `--moderator`/`--no-moderator` - whether the user is a moderator
- `--admin`/`--no-admin` - whether the user is an admin
- `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
## Generate an invite link.
mix pleroma.user invite [OPTION...]
Options:
- `--expires_at DATE` - last day on which token is active (e.g. "2019-04-05")
- `--max_use NUMBER` - maximum numbers of token uses
## List generated invites
mix pleroma.user invites
## Revoke invite
mix pleroma.user revoke_invite TOKEN OR TOKEN_ID
## Delete the user's account.
mix pleroma.user rm NICKNAME
## Delete the user's activities.
mix pleroma.user delete_activities NICKNAME
## Deactivate or activate the user's account.
mix pleroma.user toggle_activated NICKNAME
## Unsubscribe local users from user's account and deactivate it
mix pleroma.user unsubscribe NICKNAME
## Create a password reset link.
mix pleroma.user reset_password NICKNAME
## Set the value of the given user's settings.
mix pleroma.user set NICKNAME [OPTION...]
Options:
- `--locked`/`--no-locked` - whether the user's account is locked
- `--moderator`/`--no-moderator` - whether the user is a moderator
- `--admin`/`--no-admin` - whether the user is an admin
## Add tags to a user.
mix pleroma.user tag NICKNAME TAGS
## Delete tags from a user.
mix pleroma.user untag NICKNAME TAGS
"""
def run(["new", nickname, email | rest]) do
{options, [], []} =
OptionParser.parse(
rest,
strict: [
name: :string,
bio: :string,
password: :string,
moderator: :boolean,
admin: :boolean,
assume_yes: :boolean
],
aliases: [
y: :assume_yes
]
)
name = Keyword.get(options, :name, nickname)
bio = Keyword.get(options, :bio, "")
{password, generated_password?} =
case Keyword.get(options, :password) do
nil ->
{:crypto.strong_rand_bytes(16) |> Base.encode64(), true}
password ->
{password, false}
end
moderator? = Keyword.get(options, :moderator, false)
admin? = Keyword.get(options, :admin, false)
assume_yes? = Keyword.get(options, :assume_yes, false)
Mix.shell().info("""
A user will be created with the following information:
- nickname: #{nickname}
- email: #{email}
- password: #{
if(generated_password?, do: "[generated; a reset link will be created]", else: password)
}
- name: #{name}
- bio: #{bio}
- moderator: #{if(moderator?, do: "true", else: "false")}
- admin: #{if(admin?, do: "true", else: "false")}
""")
proceed? = assume_yes? or Mix.shell().yes?("Continue?")
unless not proceed? do
Common.start_pleroma()
params = %{
nickname: nickname,
email: email,
password: password,
password_confirmation: password,
name: name,
bio: bio
}
changeset = User.register_changeset(%User{}, params, confirmed: true)
{:ok, _user} = User.register(changeset)
Mix.shell().info("User #{nickname} created")
if moderator? do
run(["set", nickname, "--moderator"])
end
if admin? do
run(["set", nickname, "--admin"])
end
if generated_password? do
run(["reset_password", nickname])
end
else
Mix.shell().info("User will not be created.")
end
end
def run(["rm", nickname]) do
Common.start_pleroma()
with %User{local: true} = user <- User.get_by_nickname(nickname) do
User.delete(user)
Mix.shell().info("User #{nickname} deleted.")
else
_ ->
Mix.shell().error("No local user #{nickname}")
end
end
def run(["toggle_activated", nickname]) do
Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do
{:ok, user} = User.deactivate(user, !user.info.deactivated)
Mix.shell().info(
"Activation status of #{nickname}: #{if(user.info.deactivated, do: "de", else: "")}activated"
)
else
_ ->
Mix.shell().error("No user #{nickname}")
end
end
def run(["reset_password", nickname]) do
Common.start_pleroma()
with %User{local: true} = user <- User.get_by_nickname(nickname),
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
Mix.shell().info("Generated password reset token for #{user.nickname}")
IO.puts(
"URL: #{
Pleroma.Web.Router.Helpers.util_url(
Pleroma.Web.Endpoint,
:show_password_reset,
token.token
)
}"
)
else
_ ->
Mix.shell().error("No local user #{nickname}")
end
end
def run(["unsubscribe", nickname]) do
Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do
Mix.shell().info("Deactivating #{user.nickname}")
User.deactivate(user)
{:ok, friends} = User.get_friends(user)
Enum.each(friends, fn friend ->
user = User.get_by_id(user.id)
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
User.unfollow(user, friend)
end)
:timer.sleep(500)
user = User.get_by_id(user.id)
if Enum.empty?(user.following) do
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
end
else
_ ->
Mix.shell().error("No user #{nickname}")
end
end
def run(["set", nickname | rest]) do
Common.start_pleroma()
{options, [], []} =
OptionParser.parse(
rest,
strict: [
moderator: :boolean,
admin: :boolean,
locked: :boolean
]
)
with %User{local: true} = user <- User.get_by_nickname(nickname) do
user =
case Keyword.get(options, :moderator) do
nil -> user
value -> set_moderator(user, value)
end
user =
case Keyword.get(options, :locked) do
nil -> user
value -> set_locked(user, value)
end
_user =
case Keyword.get(options, :admin) do
nil -> user
value -> set_admin(user, value)
end
else
_ ->
Mix.shell().error("No local user #{nickname}")
end
end
def run(["tag", nickname | tags]) do
Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do
user = user |> User.tag(tags)
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
else
_ ->
Mix.shell().error("Could not change user tags for #{nickname}")
end
end
def run(["untag", nickname | tags]) do
Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do
user = user |> User.untag(tags)
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
else
_ ->
Mix.shell().error("Could not change user tags for #{nickname}")
end
end
def run(["invite" | rest]) do
{options, [], []} =
OptionParser.parse(rest,
strict: [
expires_at: :string,
max_use: :integer
]
)
options =
options
|> Keyword.update(:expires_at, {:ok, nil}, fn
nil -> {:ok, nil}
val -> Date.from_iso8601(val)
end)
|> Enum.into(%{})
Common.start_pleroma()
with {:ok, val} <- options[:expires_at],
options = Map.put(options, :expires_at, val),
{:ok, invite} <- UserInviteToken.create_invite(options) do
Mix.shell().info(
"Generated user invite token " <> String.replace(invite.invite_type, "_", " ")
)
url =
Pleroma.Web.Router.Helpers.redirect_url(
Pleroma.Web.Endpoint,
:registration_page,
invite.token
)
IO.puts(url)
else
error ->
Mix.shell().error("Could not create invite token: #{inspect(error)}")
end
end
def run(["invites"]) do
Common.start_pleroma()
Mix.shell().info("Invites list:")
UserInviteToken.list_invites()
|> Enum.each(fn invite ->
expire_info =
with expires_at when not is_nil(expires_at) <- invite.expires_at do
" | Expires at: #{Date.to_string(expires_at)}"
end
using_info =
with max_use when not is_nil(max_use) <- invite.max_use do
" | Max use: #{max_use} Left use: #{max_use - invite.uses}"
end
Mix.shell().info(
"ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{
invite.used
}#{expire_info}#{using_info}"
)
end)
end
def run(["revoke_invite", token]) do
Common.start_pleroma()
with {:ok, invite} <- UserInviteToken.find_by_token(token),
{:ok, _} <- UserInviteToken.update_invite(invite, %{used: true}) do
Mix.shell().info("Invite for token #{token} was revoked.")
else
_ -> Mix.shell().error("No invite found with token #{token}")
end
end
def run(["delete_activities", nickname]) do
Common.start_pleroma()
with %User{local: true} = user <- User.get_by_nickname(nickname) do
User.delete_user_activities(user)
Mix.shell().info("User #{nickname} statuses deleted.")
else
_ ->
Mix.shell().error("No local user #{nickname}")
end
end
defp set_moderator(user, value) do
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
user_cng =
Ecto.Changeset.change(user)
|> put_embed(:info, info_cng)
{:ok, user} = User.update_and_set_cache(user_cng)
Mix.shell().info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
user
end
defp set_admin(user, value) do
info_cng = User.Info.admin_api_update(user.info, %{is_admin: value})
user_cng =
Ecto.Changeset.change(user)
|> put_embed(:info, info_cng)
{:ok, user} = User.update_and_set_cache(user_cng)
Mix.shell().info("Admin status of #{user.nickname}: #{user.info.is_admin}")
user
end
defp set_locked(user, value) do
info_cng = User.Info.user_upgrade(user.info, %{locked: value})
user_cng =
Ecto.Changeset.change(user)
|> put_embed(:info, info_cng)
{:ok, user} = User.update_and_set_cache(user_cng)
Mix.shell().info("Locked status of #{user.nickname}: #{user.info.locked}")
user
end
end

View file

@ -1,19 +0,0 @@
defmodule Mix.Tasks.ReactivateUser do
use Mix.Task
alias Pleroma.User
@moduledoc """
Reactivate a user
Usage: ``mix reactivate_user <nickname>``
Example: ``mix reactivate_user lain``
"""
def run([nickname]) do
Mix.Task.run("app.start")
with user <- User.get_by_nickname(nickname) do
User.deactivate(user, false)
end
end
end

View file

@ -1,30 +0,0 @@
defmodule Mix.Tasks.RegisterUser do
@moduledoc """
Manually register a local user
Usage: ``mix register_user <name> <nickname> <email> <bio> <password>``
Example: ``mix register_user lain lain@example.org "blushy-crushy fediverse idol + pleroma dev" pleaseDontHeckLain``
"""
use Mix.Task
alias Pleroma.{Repo, User}
@shortdoc "Register user"
def run([name, nickname, email, bio, password]) do
Mix.Task.run("app.start")
params = %{
name: name,
nickname: nickname,
email: email,
password: password,
password_confirmation: password,
bio: bio
}
user = User.register_changeset(%User{}, params)
Repo.insert!(user)
end
end

View file

@ -1,24 +0,0 @@
defmodule Mix.Tasks.RelayFollow do
use Mix.Task
require Logger
alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Follows a remote relay"
@moduledoc """
Follows a remote relay
Usage: ``mix relay_follow <relay_url>``
Example: ``mix relay_follow https://example.org/relay``
"""
def run([target]) do
Mix.Task.run("app.start")
with {:ok, activity} <- Relay.follow(target) do
# put this task to sleep to allow the genserver to push out the messages
:timer.sleep(500)
else
{:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
end
end
end

View file

@ -1,23 +0,0 @@
defmodule Mix.Tasks.RelayUnfollow do
use Mix.Task
require Logger
alias Pleroma.Web.ActivityPub.Relay
@moduledoc """
Unfollows a remote relay
Usage: ``mix relay_follow <relay_url>``
Example: ``mix relay_follow https://example.org/relay``
"""
def run([target]) do
Mix.Task.run("app.start")
with {:ok, activity} <- Relay.follow(target) do
# put this task to sleep to allow the genserver to push out the messages
:timer.sleep(500)
else
{:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
end
end
end

View file

@ -1,19 +0,0 @@
defmodule Mix.Tasks.RmUser do
use Mix.Task
alias Pleroma.User
@moduledoc """
Permanently deletes a user
Usage: ``mix rm_user [nickname]``
Example: ``mix rm_user lain``
"""
def run([nickname]) do
Mix.Task.run("app.start")
with %User{local: true} = user <- User.get_by_nickname(nickname) do
{:ok, _} = User.delete(user)
end
end
end

View file

@ -1,32 +0,0 @@
defmodule Mix.Tasks.SetAdmin do
use Mix.Task
alias Pleroma.User
@doc """
Sets admin status
Usage: set_admin nickname [true|false]
"""
def run([nickname | rest]) do
Application.ensure_all_started(:pleroma)
status =
case rest do
[status] -> status == "true"
_ -> true
end
with %User{local: true} = user <- User.get_by_nickname(nickname) do
info =
user.info
|> Map.put("is_admin", !!status)
cng = User.info_changeset(user, %{info: info})
{:ok, user} = User.update_and_set_cache(cng)
IO.puts("Admin status of #{nickname}: #{user.info["is_admin"]}")
else
_ ->
IO.puts("No local user #{nickname}")
end
end
end

View file

@ -1,39 +0,0 @@
defmodule Mix.Tasks.SetLocked do
@moduledoc """
Lock a local user
The local user will then have to manually accept/reject followers. This can also be done by the user into their settings.
Usage: ``mix set_locked <username>``
Example: ``mix set_locked lain``
"""
use Mix.Task
import Mix.Ecto
alias Pleroma.{Repo, User}
def run([nickname | rest]) do
ensure_started(Repo, [])
locked =
case rest do
[locked] -> locked == "true"
_ -> true
end
with %User{local: true} = user <- User.get_by_nickname(nickname) do
info =
user.info
|> Map.put("locked", !!locked)
cng = User.info_changeset(user, %{info: info})
user = Repo.update!(cng)
IO.puts("locked status of #{nickname}: #{user.info["locked"]}")
else
_ ->
IO.puts("No local user #{nickname}")
end
end
end

View file

@ -1,38 +0,0 @@
defmodule Mix.Tasks.UnsubscribeUser do
use Mix.Task
alias Pleroma.{User, Repo}
require Logger
@moduledoc """
Deactivate and Unsubscribe local users from a user
Usage: ``mix unsubscribe_user <nickname>``
Example: ``mix unsubscribe_user lain``
"""
def run([nickname]) do
Mix.Task.run("app.start")
with %User{} = user <- User.get_by_nickname(nickname) do
Logger.info("Deactivating #{user.nickname}")
User.deactivate(user)
{:ok, friends} = User.get_friends(user)
Enum.each(friends, fn friend ->
user = Repo.get(User, user.id)
Logger.info("Unsubscribing #{friend.nickname} from #{user.nickname}")
User.unfollow(user, friend)
end)
:timer.sleep(500)
user = Repo.get(User, user.id)
if length(user.following) == 0 do
Logger.info("Successfully unsubscribed all followers from #{user.nickname}")
end
end
end
end