Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
This commit is contained in:
commit
5f62b55a6f
87 changed files with 2663 additions and 577 deletions
|
@ -25,13 +25,13 @@ before_script:
|
||||||
- apt-get update && apt-get install -y cmake
|
- apt-get update && apt-get install -y cmake
|
||||||
- mix local.hex --force
|
- mix local.hex --force
|
||||||
- mix local.rebar --force
|
- mix local.rebar --force
|
||||||
|
- mix deps.get
|
||||||
- apt-get -qq update
|
- apt-get -qq update
|
||||||
- apt-get install -y libmagic-dev
|
- apt-get install -y libmagic-dev
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- mix deps.get
|
|
||||||
- mix compile --force
|
- mix compile --force
|
||||||
|
|
||||||
spec-build:
|
spec-build:
|
||||||
|
@ -52,7 +52,6 @@ benchmark:
|
||||||
alias: postgres
|
alias: postgres
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
script:
|
script:
|
||||||
- mix deps.get
|
|
||||||
- mix ecto.create
|
- mix ecto.create
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- mix pleroma.load_testing
|
- mix pleroma.load_testing
|
||||||
|
@ -70,7 +69,6 @@ unit-testing:
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
script:
|
script:
|
||||||
- apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
|
- apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
|
||||||
- mix deps.get
|
|
||||||
- mix ecto.create
|
- mix ecto.create
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- mix coveralls --preload-modules
|
- mix coveralls --preload-modules
|
||||||
|
@ -104,7 +102,6 @@ unit-testing-rum:
|
||||||
RUM_ENABLED: "true"
|
RUM_ENABLED: "true"
|
||||||
script:
|
script:
|
||||||
- apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
|
- apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
|
||||||
- mix deps.get
|
|
||||||
- mix ecto.create
|
- mix ecto.create
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
- "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
||||||
|
@ -120,7 +117,6 @@ analysis:
|
||||||
stage: test
|
stage: test
|
||||||
cache: *testing_cache_policy
|
cache: *testing_cache_policy
|
||||||
script:
|
script:
|
||||||
- mix deps.get
|
|
||||||
- mix credo --strict --only=warnings,todo,fixme,consistency,readability
|
- mix credo --strict --only=warnings,todo,fixme,consistency,readability
|
||||||
|
|
||||||
docs-deploy:
|
docs-deploy:
|
||||||
|
@ -393,4 +389,4 @@ docker-adhoc:
|
||||||
tags:
|
tags:
|
||||||
- dind
|
- dind
|
||||||
only:
|
only:
|
||||||
- /^build-docker/.*$/@pleroma/pleroma
|
- /^build-docker/.*$/@pleroma/pleroma
|
||||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
|
||||||
|
|
||||||
|
## Unreleased (Patch)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable.
|
||||||
|
|
||||||
|
## [2.3.0] - 2020-03-01
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fixed client user agent leaking through MediaProxy
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- `:auth, :enforce_oauth_admin_scope_usage` configuration option.
|
- `:auth, :enforce_oauth_admin_scope_usage` configuration option.
|
||||||
|
@ -14,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
- **Breaking**: Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
|
- **Breaking**: Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
|
||||||
- **Breaking**: Changed `mix pleroma.user toggle_activated` to `mix pleroma.user activate/deactivate`
|
- **Breaking**: Changed `mix pleroma.user toggle_activated` to `mix pleroma.user activate/deactivate`
|
||||||
|
- **Breaking:** NSFW hashtag is no longer added on sensitive posts
|
||||||
- Polls now always return a `voters_count`, even if they are single-choice.
|
- Polls now always return a `voters_count`, even if they are single-choice.
|
||||||
- Admin Emails: The ap id is used as the user link in emails now.
|
- Admin Emails: The ap id is used as the user link in emails now.
|
||||||
- Improved registration workflow for email confirmation and account approval modes.
|
- Improved registration workflow for email confirmation and account approval modes.
|
||||||
|
@ -40,6 +55,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Pleroma API: Reroute `/api/pleroma/*` to `/api/v1/pleroma/*`
|
- Pleroma API: Reroute `/api/pleroma/*` to `/api/v1/pleroma/*`
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
- Improved hashtag timeline performance (requires a background migration).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
@ -59,11 +75,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Ability to define custom HTTP headers per each frontend
|
- Ability to define custom HTTP headers per each frontend
|
||||||
- MRF (`NoEmptyPolicy`): New MRF Policy which will deny empty statuses or statuses of only mentions from being created by local users
|
- MRF (`NoEmptyPolicy`): New MRF Policy which will deny empty statuses or statuses of only mentions from being created by local users
|
||||||
- New users will receive a simple email confirming their registration if no other emails will be dispatched. (e.g., Welcome, Confirmation, or Approval Required)
|
- New users will receive a simple email confirming their registration if no other emails will be dispatched. (e.g., Welcome, Confirmation, or Approval Required)
|
||||||
- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
- Admin API: (`GET /api/pleroma/admin/users`) filter users by `unconfirmed` status and `actor_type`.
|
- Admin API: (`GET /api/pleroma/admin/users`) filter users by `unconfirmed` status and `actor_type`.
|
||||||
|
- Admin API: OpenAPI spec for the user-related operations
|
||||||
- Pleroma API: `GET /api/v2/pleroma/chats` added. It is exactly like `GET /api/v1/pleroma/chats` except supports pagination.
|
- Pleroma API: `GET /api/v2/pleroma/chats` added. It is exactly like `GET /api/v1/pleroma/chats` except supports pagination.
|
||||||
- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
|
- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
|
||||||
- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
|
- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
|
||||||
|
@ -99,9 +115,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Support for expires_in/expires_at in the Filters.
|
- Mastodon API: Support for expires_in/expires_at in the Filters.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Unreleased (Patch)
|
|
||||||
|
|
||||||
|
|
||||||
## [2.2.2] - 2020-01-18
|
## [2.2.2] - 2020-01-18
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -498,7 +511,6 @@ switched to a new configuration mechanism, however it was not officially removed
|
||||||
- Static-FE: Fix remote posts not being sanitized
|
- Static-FE: Fix remote posts not being sanitized
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
=======
|
|
||||||
- Rate limiter crashes when there is no explicitly specified ip in the config
|
- Rate limiter crashes when there is no explicitly specified ip in the config
|
||||||
- 500 errors when no `Accept` header is present if Static-FE is enabled
|
- 500 errors when no `Accept` header is present if Static-FE is enabled
|
||||||
- Instance panel not being updated immediately due to wrong `Cache-Control` headers
|
- Instance panel not being updated immediately due to wrong `Cache-Control` headers
|
||||||
|
|
|
@ -394,6 +394,11 @@ config :pleroma, :mrf_keyword,
|
||||||
federated_timeline_removal: [],
|
federated_timeline_removal: [],
|
||||||
replace: []
|
replace: []
|
||||||
|
|
||||||
|
config :pleroma, :mrf_hashtag,
|
||||||
|
sensitive: ["nsfw"],
|
||||||
|
reject: [],
|
||||||
|
federated_timeline_removal: []
|
||||||
|
|
||||||
config :pleroma, :mrf_subchain, match_actor: %{}
|
config :pleroma, :mrf_subchain, match_actor: %{}
|
||||||
|
|
||||||
config :pleroma, :mrf_activity_expiration, days: 365
|
config :pleroma, :mrf_activity_expiration, days: 365
|
||||||
|
@ -657,6 +662,10 @@ config :pleroma, :oauth2,
|
||||||
|
|
||||||
config :pleroma, :database, rum_enabled: false
|
config :pleroma, :database, rum_enabled: false
|
||||||
|
|
||||||
|
config :pleroma, :features, improved_hashtag_timeline: :auto
|
||||||
|
|
||||||
|
config :pleroma, :populate_hashtags_table, fault_rate_allowance: 0.01
|
||||||
|
|
||||||
config :pleroma, :env, Mix.env()
|
config :pleroma, :env, Mix.env()
|
||||||
|
|
||||||
config :http_signatures,
|
config :http_signatures,
|
||||||
|
|
|
@ -459,6 +459,42 @@ config :pleroma, :config_description, [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: :features,
|
||||||
|
type: :group,
|
||||||
|
description: "Customizable features",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :improved_hashtag_timeline,
|
||||||
|
type: {:dropdown, :atom},
|
||||||
|
description:
|
||||||
|
"Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes).",
|
||||||
|
suggestions: [:auto, :enabled, :disabled]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: :populate_hashtags_table,
|
||||||
|
type: :group,
|
||||||
|
description: "`populate_hashtags_table` background migration settings",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :fault_rate_allowance,
|
||||||
|
type: :float,
|
||||||
|
description:
|
||||||
|
"Max accepted rate of objects that failed in the migration. Any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if hashtags transfer failed for all records.",
|
||||||
|
suggestions: [0.01]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :sleep_interval_ms,
|
||||||
|
type: :integer,
|
||||||
|
description:
|
||||||
|
"Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances)."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :instance,
|
key: :instance,
|
||||||
|
|
|
@ -32,16 +32,20 @@
|
||||||
config :pleroma, configurable_from_database: false
|
config :pleroma, configurable_from_database: false
|
||||||
```
|
```
|
||||||
|
|
||||||
To delete transferred settings from database optional flag `-d` can be used. `<env>` is `prod` by default.
|
Options:
|
||||||
|
|
||||||
|
- `<path>` - where to save migrated config. E.g. `--path=/tmp`. If file saved into non standart folder, you must manually copy file into directory where Pleroma can read it. For OTP install path will be `PLEROMA_CONFIG_PATH` or `/etc/pleroma`. For installation from source - `config` directory in the pleroma folder.
|
||||||
|
- `<env>` - environment, for which is migrated config. By default is `prod`.
|
||||||
|
- To delete transferred settings from database optional flag `-d` can be used
|
||||||
|
|
||||||
=== "OTP"
|
=== "OTP"
|
||||||
```sh
|
```sh
|
||||||
./bin/pleroma_ctl config migrate_from_db [--env=<env>] [-d]
|
./bin/pleroma_ctl config migrate_from_db [--env=<env>] [-d] [--path=<path>]
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "From Source"
|
=== "From Source"
|
||||||
```sh
|
```sh
|
||||||
mix pleroma.config migrate_from_db [--env=<env>] [-d]
|
mix pleroma.config migrate_from_db [--env=<env>] [-d] [--path=<path>]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dump all of the config settings defined in the database
|
## Dump all of the config settings defined in the database
|
||||||
|
|
|
@ -65,6 +65,13 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
|
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
|
||||||
* `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
|
* `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
|
||||||
|
|
||||||
|
## :database
|
||||||
|
* `improved_hashtag_timeline`: Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes).
|
||||||
|
|
||||||
|
## Background migrations
|
||||||
|
* `populate_hashtags_table/sleep_interval_ms`: Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances).
|
||||||
|
* `populate_hashtags_table/fault_rate_allowance`: Max rate of failed objects to actually processed objects in order to enable the feature (any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if hashtags transfer failed for all records).
|
||||||
|
|
||||||
## Welcome
|
## Welcome
|
||||||
* `direct_message`: - welcome message sent as a direct message.
|
* `direct_message`: - welcome message sent as a direct message.
|
||||||
* `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`.
|
* `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`.
|
||||||
|
@ -203,6 +210,16 @@ config :pleroma, :mrf_user_allowlist, %{
|
||||||
|
|
||||||
* `days`: Default global expiration time for all local Create activities (in days)
|
* `days`: Default global expiration time for all local Create activities (in days)
|
||||||
|
|
||||||
|
#### :mrf_hashtag
|
||||||
|
|
||||||
|
* `sensitive`: List of hashtags to mark activities as sensitive (default: `nsfw`)
|
||||||
|
* `federated_timeline_removal`: List of hashtags to remove activities from the federated timeline (aka TWNK)
|
||||||
|
* `reject`: List of hashtags to reject activities from
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- The hashtags in the configuration do not have a leading `#`.
|
||||||
|
- This MRF Policy is always enabled, if you want to disable it you have to set empty lists
|
||||||
|
|
||||||
### :activitypub
|
### :activitypub
|
||||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||||
* `outgoing_blocks`: Whether to federate blocks to other instances
|
* `outgoing_blocks`: Whether to federate blocks to other instances
|
||||||
|
|
|
@ -27,7 +27,7 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||||
|
|
||||||
{opts, _} =
|
{opts, _} =
|
||||||
OptionParser.parse!(options,
|
OptionParser.parse!(options,
|
||||||
strict: [env: :string, delete: :boolean],
|
strict: [env: :string, delete: :boolean, path: :string],
|
||||||
aliases: [d: :delete]
|
aliases: [d: :delete]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -259,18 +259,43 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||||
defp migrate_from_db(opts) do
|
defp migrate_from_db(opts) do
|
||||||
env = opts[:env] || Pleroma.Config.get(:env)
|
env = opts[:env] || Pleroma.Config.get(:env)
|
||||||
|
|
||||||
|
filename = "#{env}.exported_from_db.secret.exs"
|
||||||
|
|
||||||
config_path =
|
config_path =
|
||||||
if Pleroma.Config.get(:release) do
|
cond do
|
||||||
:config_path
|
opts[:path] ->
|
||||||
|> Pleroma.Config.get()
|
opts[:path]
|
||||||
|> Path.dirname()
|
|
||||||
else
|
Pleroma.Config.get(:release) ->
|
||||||
"config"
|
:config_path
|
||||||
|
|> Pleroma.Config.get()
|
||||||
|
|> Path.dirname()
|
||||||
|
|
||||||
|
true ->
|
||||||
|
"config"
|
||||||
end
|
end
|
||||||
|> Path.join("#{env}.exported_from_db.secret.exs")
|
|> Path.join(filename)
|
||||||
|
|
||||||
file = File.open!(config_path, [:write, :utf8])
|
with {:ok, file} <- File.open(config_path, [:write, :utf8]) do
|
||||||
|
write_config(file, config_path, opts)
|
||||||
|
shell_info("Database configuration settings have been exported to #{config_path}")
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
shell_error("Impossible to save settings to this directory #{Path.dirname(config_path)}")
|
||||||
|
tmp_config_path = Path.join(System.tmp_dir!(), filename)
|
||||||
|
file = File.open!(tmp_config_path)
|
||||||
|
|
||||||
|
shell_info(
|
||||||
|
"Saving database configuration settings to #{tmp_config_path}. Copy it to the #{
|
||||||
|
Path.dirname(config_path)
|
||||||
|
} manually."
|
||||||
|
)
|
||||||
|
|
||||||
|
write_config(file, tmp_config_path, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp write_config(file, path, opts) do
|
||||||
IO.write(file, config_header())
|
IO.write(file, config_header())
|
||||||
|
|
||||||
ConfigDB
|
ConfigDB
|
||||||
|
@ -278,11 +303,7 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||||
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
|
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
|
||||||
|
|
||||||
:ok = File.close(file)
|
:ok = File.close(file)
|
||||||
System.cmd("mix", ["format", config_path])
|
System.cmd("mix", ["format", path])
|
||||||
|
|
||||||
shell_info(
|
|
||||||
"Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if Code.ensure_loaded?(Config.Reader) do
|
if Code.ensure_loaded?(Config.Reader) do
|
||||||
|
|
|
@ -8,10 +8,13 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Mix.Pleroma
|
import Mix.Pleroma
|
||||||
|
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
@shortdoc "A collection of database related tasks"
|
@shortdoc "A collection of database related tasks"
|
||||||
|
@ -214,4 +217,32 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||||
shell_info('Done.')
|
shell_info('Done.')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Rolls back a specific migration (leaving subsequent migrations applied).
|
||||||
|
# WARNING: imposes a risk of unrecoverable data loss — proceed at your own responsibility.
|
||||||
|
# Based on https://stackoverflow.com/a/53825840
|
||||||
|
def run(["rollback", version]) do
|
||||||
|
prompt = "SEVERE WARNING: this operation may result in unrecoverable data loss. Continue?"
|
||||||
|
|
||||||
|
if shell_prompt(prompt, "n") in ~w(Yn Y y) do
|
||||||
|
{_, result, _} =
|
||||||
|
Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
|
||||||
|
version = String.to_integer(version)
|
||||||
|
re = ~r/^#{version}_.*\.exs/
|
||||||
|
path = Ecto.Migrator.migrations_path(repo)
|
||||||
|
|
||||||
|
with {_, "" <> file} <- {:find, Enum.find(File.ls!(path), &String.match?(&1, re))},
|
||||||
|
{_, [{mod, _} | _]} <- {:compile, Code.compile_file(Path.join(path, file))},
|
||||||
|
{_, :ok} <- {:rollback, Ecto.Migrator.down(repo, version, mod)} do
|
||||||
|
{:ok, "Reversed migration: #{file}"}
|
||||||
|
else
|
||||||
|
{:find, _} -> {:error, "No migration found with version prefix: #{version}"}
|
||||||
|
{:compile, e} -> {:error, "Problem compiling migration module: #{inspect(e)}"}
|
||||||
|
{:rollback, e} -> {:error, "Problem reversing migration: #{inspect(e)}"}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
shell_info(inspect(result))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -113,6 +113,7 @@ defmodule Pleroma.Activity do
|
||||||
from([a] in query,
|
from([a] in query,
|
||||||
left_join: b in Bookmark,
|
left_join: b in Bookmark,
|
||||||
on: b.user_id == ^user.id and b.activity_id == a.id,
|
on: b.user_id == ^user.id and b.activity_id == a.id,
|
||||||
|
as: :bookmark,
|
||||||
preload: [bookmark: b]
|
preload: [bookmark: b]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -123,6 +124,7 @@ defmodule Pleroma.Activity do
|
||||||
from([a] in query,
|
from([a] in query,
|
||||||
left_join: r in ReportNote,
|
left_join: r in ReportNote,
|
||||||
on: a.id == r.activity_id,
|
on: a.id == r.activity_id,
|
||||||
|
as: :report_note,
|
||||||
preload: [report_notes: r]
|
preload: [report_notes: r]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,14 +48,12 @@ defmodule Pleroma.Activity.Ir.Topics do
|
||||||
tags
|
tags
|
||||||
end
|
end
|
||||||
|
|
||||||
defp hashtags_to_topics(%{data: %{"tag" => tags}}) do
|
defp hashtags_to_topics(object) do
|
||||||
tags
|
object
|
||||||
|> Enum.filter(&is_bitstring(&1))
|
|> Object.hashtags()
|
||||||
|> Enum.map(fn tag -> "hashtag:" <> tag end)
|
|> Enum.map(fn hashtag -> "hashtag:" <> hashtag end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp hashtags_to_topics(_), do: []
|
|
||||||
|
|
||||||
defp remote_topics(%{local: true}), do: []
|
defp remote_topics(%{local: true}), do: []
|
||||||
|
|
||||||
defp remote_topics(%{actor: actor}) when is_binary(actor),
|
defp remote_topics(%{actor: actor}) when is_binary(actor),
|
||||||
|
|
|
@ -103,9 +103,7 @@ defmodule Pleroma.Application do
|
||||||
task_children(@mix_env) ++
|
task_children(@mix_env) ++
|
||||||
dont_run_in_test(@mix_env) ++
|
dont_run_in_test(@mix_env) ++
|
||||||
chat_child(chat_enabled?()) ++
|
chat_child(chat_enabled?()) ++
|
||||||
[
|
[Pleroma.Gopher.Server]
|
||||||
Pleroma.Gopher.Server
|
|
||||||
]
|
|
||||||
|
|
||||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||||
# for other strategies and supported options
|
# for other strategies and supported options
|
||||||
|
@ -230,6 +228,12 @@ defmodule Pleroma.Application do
|
||||||
keys: :duplicate,
|
keys: :duplicate,
|
||||||
partitions: System.schedulers_online()
|
partitions: System.schedulers_online()
|
||||||
]}
|
]}
|
||||||
|
] ++ background_migrators()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp background_migrators do
|
||||||
|
[
|
||||||
|
Pleroma.Migrators.HashtagsTableMigrator
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -99,4 +99,8 @@ defmodule Pleroma.Config do
|
||||||
def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
|
def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
|
||||||
|
|
||||||
def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
|
def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
|
||||||
|
|
||||||
|
def feature_enabled?(feature_name) do
|
||||||
|
get([:features, feature_name]) not in [nil, false, :disabled, :auto]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
45
lib/pleroma/data_migration.ex
Normal file
45
lib/pleroma/data_migration.ex
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.DataMigration do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.DataMigration
|
||||||
|
alias Pleroma.DataMigration.State
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
schema "data_migrations" do
|
||||||
|
field(:name, :string)
|
||||||
|
field(:state, State, default: :pending)
|
||||||
|
field(:feature_lock, :boolean, default: false)
|
||||||
|
field(:params, :map, default: %{})
|
||||||
|
field(:data, :map, default: %{})
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(data_migration, params \\ %{}) do
|
||||||
|
data_migration
|
||||||
|
|> cast(params, [:name, :state, :feature_lock, :params, :data])
|
||||||
|
|> validate_required([:name])
|
||||||
|
|> unique_constraint(:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_one_by_id(id, params \\ %{}) do
|
||||||
|
with {1, _} <-
|
||||||
|
from(dm in DataMigration, where: dm.id == ^id)
|
||||||
|
|> Repo.update_all(set: params) do
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_by_name(name) do
|
||||||
|
Repo.get_by(DataMigration, name: name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_hashtags_table, do: get_by_name("populate_hashtags_table")
|
||||||
|
end
|
|
@ -9,7 +9,6 @@ defmodule Pleroma.Delivery do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.User
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
|
@ -17,3 +17,11 @@ defenum(Pleroma.FollowingRelationship.State,
|
||||||
follow_accept: 2,
|
follow_accept: 2,
|
||||||
follow_reject: 3
|
follow_reject: 3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
defenum(Pleroma.DataMigration.State,
|
||||||
|
pending: 1,
|
||||||
|
running: 2,
|
||||||
|
complete: 3,
|
||||||
|
failed: 4,
|
||||||
|
manual: 5
|
||||||
|
)
|
||||||
|
|
106
lib/pleroma/hashtag.ex
Normal file
106
lib/pleroma/hashtag.ex
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Hashtag do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Ecto.Multi
|
||||||
|
alias Pleroma.Hashtag
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
schema "hashtags" do
|
||||||
|
field(:name, :string)
|
||||||
|
|
||||||
|
many_to_many(:objects, Object, join_through: "hashtags_objects", on_replace: :delete)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_name(name) do
|
||||||
|
name
|
||||||
|
|> String.downcase()
|
||||||
|
|> String.trim()
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_or_create_by_name(name) do
|
||||||
|
changeset = changeset(%Hashtag{}, %{name: name})
|
||||||
|
|
||||||
|
Repo.insert(
|
||||||
|
changeset,
|
||||||
|
on_conflict: [set: [name: get_field(changeset, :name)]],
|
||||||
|
conflict_target: :name,
|
||||||
|
returning: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_or_create_by_names(names) when is_list(names) do
|
||||||
|
names = Enum.map(names, &normalize_name/1)
|
||||||
|
timestamp = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||||
|
|
||||||
|
structs =
|
||||||
|
Enum.map(names, fn name ->
|
||||||
|
%Hashtag{}
|
||||||
|
|> changeset(%{name: name})
|
||||||
|
|> Map.get(:changes)
|
||||||
|
|> Map.merge(%{inserted_at: timestamp, updated_at: timestamp})
|
||||||
|
end)
|
||||||
|
|
||||||
|
try do
|
||||||
|
with {:ok, %{query_op: hashtags}} <-
|
||||||
|
Multi.new()
|
||||||
|
|> Multi.insert_all(:insert_all_op, Hashtag, structs,
|
||||||
|
on_conflict: :nothing,
|
||||||
|
conflict_target: :name
|
||||||
|
)
|
||||||
|
|> Multi.run(:query_op, fn _repo, _changes ->
|
||||||
|
{:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
|
||||||
|
end)
|
||||||
|
|> Repo.transaction() do
|
||||||
|
{:ok, hashtags}
|
||||||
|
else
|
||||||
|
{:error, _name, value, _changes_so_far} -> {:error, value}
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
e -> {:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(%Hashtag{} = struct, params) do
|
||||||
|
struct
|
||||||
|
|> cast(params, [:name])
|
||||||
|
|> update_change(:name, &normalize_name/1)
|
||||||
|
|> validate_required([:name])
|
||||||
|
|> unique_constraint(:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unlink(%Object{id: object_id}) do
|
||||||
|
with {_, hashtag_ids} <-
|
||||||
|
from(hto in "hashtags_objects",
|
||||||
|
where: hto.object_id == ^object_id,
|
||||||
|
select: hto.hashtag_id
|
||||||
|
)
|
||||||
|
|> Repo.delete_all(),
|
||||||
|
{:ok, unreferenced_count} <- delete_unreferenced(hashtag_ids) do
|
||||||
|
{:ok, length(hashtag_ids), unreferenced_count}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@delete_unreferenced_query """
|
||||||
|
DELETE FROM hashtags WHERE id IN
|
||||||
|
(SELECT hashtags.id FROM hashtags
|
||||||
|
LEFT OUTER JOIN hashtags_objects
|
||||||
|
ON hashtags_objects.hashtag_id = hashtags.id
|
||||||
|
WHERE hashtags_objects.hashtag_id IS NULL AND hashtags.id = ANY($1));
|
||||||
|
"""
|
||||||
|
|
||||||
|
def delete_unreferenced(ids) do
|
||||||
|
with {:ok, %{num_rows: deleted_count}} <- Repo.query(@delete_unreferenced_query, [ids]) do
|
||||||
|
{:ok, deleted_count}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
208
lib/pleroma/migrators/hashtags_table_migrator.ex
Normal file
208
lib/pleroma/migrators/hashtags_table_migrator.ex
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Migrators.HashtagsTableMigrator do
|
||||||
|
defmodule State do
|
||||||
|
use Pleroma.Migrators.Support.BaseMigratorState
|
||||||
|
|
||||||
|
@impl Pleroma.Migrators.Support.BaseMigratorState
|
||||||
|
defdelegate data_migration(), to: Pleroma.DataMigration, as: :populate_hashtags_table
|
||||||
|
end
|
||||||
|
|
||||||
|
use Pleroma.Migrators.Support.BaseMigrator
|
||||||
|
|
||||||
|
alias Pleroma.Hashtag
|
||||||
|
alias Pleroma.Migrators.Support.BaseMigrator
|
||||||
|
alias Pleroma.Object
|
||||||
|
|
||||||
|
@impl BaseMigrator
|
||||||
|
def feature_config_path, do: [:features, :improved_hashtag_timeline]
|
||||||
|
|
||||||
|
@impl BaseMigrator
|
||||||
|
def fault_rate_allowance, do: Config.get([:populate_hashtags_table, :fault_rate_allowance], 0)
|
||||||
|
|
||||||
|
@impl BaseMigrator
|
||||||
|
def perform do
|
||||||
|
data_migration_id = data_migration_id()
|
||||||
|
max_processed_id = get_stat(:max_processed_id, 0)
|
||||||
|
|
||||||
|
Logger.info("Transferring embedded hashtags to `hashtags` (from oid: #{max_processed_id})...")
|
||||||
|
|
||||||
|
query()
|
||||||
|
|> where([object], object.id > ^max_processed_id)
|
||||||
|
|> Repo.chunk_stream(100, :batches, timeout: :infinity)
|
||||||
|
|> Stream.each(fn objects ->
|
||||||
|
object_ids = Enum.map(objects, & &1.id)
|
||||||
|
|
||||||
|
results = Enum.map(objects, &transfer_object_hashtags(&1))
|
||||||
|
|
||||||
|
failed_ids =
|
||||||
|
results
|
||||||
|
|> Enum.filter(&(elem(&1, 0) == :error))
|
||||||
|
|> Enum.map(&elem(&1, 1))
|
||||||
|
|
||||||
|
# Count of objects with hashtags: `{:noop, id}` is returned for objects having other AS2 tags
|
||||||
|
chunk_affected_count =
|
||||||
|
results
|
||||||
|
|> Enum.filter(&(elem(&1, 0) == :ok))
|
||||||
|
|> length()
|
||||||
|
|
||||||
|
for failed_id <- failed_ids do
|
||||||
|
_ =
|
||||||
|
Repo.query(
|
||||||
|
"INSERT INTO data_migration_failed_ids(data_migration_id, record_id) " <>
|
||||||
|
"VALUES ($1, $2) ON CONFLICT DO NOTHING;",
|
||||||
|
[data_migration_id, failed_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
_ =
|
||||||
|
Repo.query(
|
||||||
|
"DELETE FROM data_migration_failed_ids " <>
|
||||||
|
"WHERE data_migration_id = $1 AND record_id = ANY($2)",
|
||||||
|
[data_migration_id, object_ids -- failed_ids]
|
||||||
|
)
|
||||||
|
|
||||||
|
max_object_id = Enum.at(object_ids, -1)
|
||||||
|
|
||||||
|
put_stat(:max_processed_id, max_object_id)
|
||||||
|
increment_stat(:iteration_processed_count, length(object_ids))
|
||||||
|
increment_stat(:processed_count, length(object_ids))
|
||||||
|
increment_stat(:failed_count, length(failed_ids))
|
||||||
|
increment_stat(:affected_count, chunk_affected_count)
|
||||||
|
put_stat(:records_per_second, records_per_second())
|
||||||
|
persist_state()
|
||||||
|
|
||||||
|
# A quick and dirty approach to controlling the load this background migration imposes
|
||||||
|
sleep_interval = Config.get([:populate_hashtags_table, :sleep_interval_ms], 0)
|
||||||
|
Process.sleep(sleep_interval)
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl BaseMigrator
|
||||||
|
def query do
|
||||||
|
# Note: most objects have Mention-type AS2 tags and no hashtags (but we can't filter them out)
|
||||||
|
# Note: not checking activity type, expecting remove_non_create_objects_hashtags/_ to clean up
|
||||||
|
from(
|
||||||
|
object in Object,
|
||||||
|
where:
|
||||||
|
fragment("(?)->'tag' IS NOT NULL AND (?)->'tag' != '[]'::jsonb", object.data, object.data),
|
||||||
|
select: %{
|
||||||
|
id: object.id,
|
||||||
|
tag: fragment("(?)->'tag'", object.data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> join(:left, [o], hashtags_objects in fragment("SELECT object_id FROM hashtags_objects"),
|
||||||
|
on: hashtags_objects.object_id == o.id
|
||||||
|
)
|
||||||
|
|> where([_o, hashtags_objects], is_nil(hashtags_objects.object_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec transfer_object_hashtags(Map.t()) :: {:noop | :ok | :error, integer()}
|
||||||
|
defp transfer_object_hashtags(object) do
|
||||||
|
embedded_tags = if Map.has_key?(object, :tag), do: object.tag, else: object.data["tag"]
|
||||||
|
hashtags = Object.object_data_hashtags(%{"tag" => embedded_tags})
|
||||||
|
|
||||||
|
if Enum.any?(hashtags) do
|
||||||
|
transfer_object_hashtags(object, hashtags)
|
||||||
|
else
|
||||||
|
{:noop, object.id}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp transfer_object_hashtags(object, hashtags) do
|
||||||
|
Repo.transaction(fn ->
|
||||||
|
with {:ok, hashtag_records} <- Hashtag.get_or_create_by_names(hashtags) do
|
||||||
|
maps = Enum.map(hashtag_records, &%{hashtag_id: &1.id, object_id: object.id})
|
||||||
|
base_error = "ERROR when inserting hashtags_objects for object with id #{object.id}"
|
||||||
|
|
||||||
|
try do
|
||||||
|
with {rows_count, _} when is_integer(rows_count) <-
|
||||||
|
Repo.insert_all("hashtags_objects", maps, on_conflict: :nothing) do
|
||||||
|
object.id
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
Logger.error("#{base_error}: #{inspect(e)}")
|
||||||
|
Repo.rollback(object.id)
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
e ->
|
||||||
|
Logger.error("#{base_error}: #{inspect(e)}")
|
||||||
|
Repo.rollback(object.id)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
error = "ERROR: could not create hashtags for object #{object.id}: #{inspect(e)}"
|
||||||
|
Logger.error(error)
|
||||||
|
Repo.rollback(object.id)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl BaseMigrator
|
||||||
|
def retry_failed do
|
||||||
|
data_migration_id = data_migration_id()
|
||||||
|
|
||||||
|
failed_objects_query()
|
||||||
|
|> Repo.chunk_stream(100, :one)
|
||||||
|
|> Stream.each(fn object ->
|
||||||
|
with {res, _} when res != :error <- transfer_object_hashtags(object) do
|
||||||
|
_ =
|
||||||
|
Repo.query(
|
||||||
|
"DELETE FROM data_migration_failed_ids " <>
|
||||||
|
"WHERE data_migration_id = $1 AND record_id = $2",
|
||||||
|
[data_migration_id, object.id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
|
||||||
|
put_stat(:failed_count, failures_count())
|
||||||
|
persist_state()
|
||||||
|
|
||||||
|
force_continue()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp failed_objects_query do
|
||||||
|
from(o in Object)
|
||||||
|
|> join(:inner, [o], dmf in fragment("SELECT * FROM data_migration_failed_ids"),
|
||||||
|
on: dmf.record_id == o.id
|
||||||
|
)
|
||||||
|
|> where([_o, dmf], dmf.data_migration_id == ^data_migration_id())
|
||||||
|
|> order_by([o], asc: o.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Service func to delete `hashtags_objects` for legacy objects not associated with Create activity.
|
||||||
|
Also deletes unreferenced `hashtags` records (might occur after deletion of `hashtags_objects`).
|
||||||
|
"""
|
||||||
|
def delete_non_create_activities_hashtags do
|
||||||
|
hashtags_objects_cleanup_query = """
|
||||||
|
DELETE FROM hashtags_objects WHERE object_id IN
|
||||||
|
(SELECT DISTINCT objects.id FROM objects
|
||||||
|
JOIN hashtags_objects ON hashtags_objects.object_id = objects.id LEFT JOIN activities
|
||||||
|
ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') =
|
||||||
|
(objects.data->>'id')
|
||||||
|
AND activities.data->>'type' = 'Create'
|
||||||
|
WHERE activities.id IS NULL);
|
||||||
|
"""
|
||||||
|
|
||||||
|
hashtags_cleanup_query = """
|
||||||
|
DELETE FROM hashtags WHERE id IN
|
||||||
|
(SELECT hashtags.id FROM hashtags
|
||||||
|
LEFT OUTER JOIN hashtags_objects
|
||||||
|
ON hashtags_objects.hashtag_id = hashtags.id
|
||||||
|
WHERE hashtags_objects.hashtag_id IS NULL);
|
||||||
|
"""
|
||||||
|
|
||||||
|
{:ok, %{num_rows: hashtags_objects_count}} =
|
||||||
|
Repo.query(hashtags_objects_cleanup_query, [], timeout: :infinity)
|
||||||
|
|
||||||
|
{:ok, %{num_rows: hashtags_count}} =
|
||||||
|
Repo.query(hashtags_cleanup_query, [], timeout: :infinity)
|
||||||
|
|
||||||
|
{:ok, hashtags_objects_count, hashtags_count}
|
||||||
|
end
|
||||||
|
end
|
210
lib/pleroma/migrators/support/base_migrator.ex
Normal file
210
lib/pleroma/migrators/support/base_migrator.ex
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Migrators.Support.BaseMigrator do
|
||||||
|
@moduledoc """
|
||||||
|
Base background migrator functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@callback perform() :: any()
|
||||||
|
@callback retry_failed() :: any()
|
||||||
|
@callback feature_config_path() :: list(atom())
|
||||||
|
@callback query() :: Ecto.Query.t()
|
||||||
|
@callback fault_rate_allowance() :: integer() | float()
|
||||||
|
|
||||||
|
defmacro __using__(_opts) do
|
||||||
|
quote do
|
||||||
|
use GenServer
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias __MODULE__.State
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
@behaviour Pleroma.Migrators.Support.BaseMigrator
|
||||||
|
|
||||||
|
defdelegate data_migration(), to: State
|
||||||
|
defdelegate data_migration_id(), to: State
|
||||||
|
defdelegate state(), to: State
|
||||||
|
defdelegate persist_state(), to: State, as: :persist_to_db
|
||||||
|
defdelegate get_stat(key, value \\ nil), to: State, as: :get_data_key
|
||||||
|
defdelegate put_stat(key, value), to: State, as: :put_data_key
|
||||||
|
defdelegate increment_stat(key, increment), to: State, as: :increment_data_key
|
||||||
|
|
||||||
|
@reg_name {:global, __MODULE__}
|
||||||
|
|
||||||
|
def whereis, do: GenServer.whereis(@reg_name)
|
||||||
|
|
||||||
|
def start_link(_) do
|
||||||
|
case whereis() do
|
||||||
|
nil ->
|
||||||
|
GenServer.start_link(__MODULE__, nil, name: @reg_name)
|
||||||
|
|
||||||
|
pid ->
|
||||||
|
{:ok, pid}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(_) do
|
||||||
|
{:ok, nil, {:continue, :init_state}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_continue(:init_state, _state) do
|
||||||
|
{:ok, _} = State.start_link(nil)
|
||||||
|
|
||||||
|
data_migration = data_migration()
|
||||||
|
manual_migrations = Config.get([:instance, :manual_data_migrations], [])
|
||||||
|
|
||||||
|
cond do
|
||||||
|
Config.get(:env) == :test ->
|
||||||
|
update_status(:noop)
|
||||||
|
|
||||||
|
is_nil(data_migration) ->
|
||||||
|
message = "Data migration does not exist."
|
||||||
|
update_status(:failed, message)
|
||||||
|
Logger.error("#{__MODULE__}: #{message}")
|
||||||
|
|
||||||
|
data_migration.state == :manual or data_migration.name in manual_migrations ->
|
||||||
|
message = "Data migration is in manual execution or manual fix mode."
|
||||||
|
update_status(:manual, message)
|
||||||
|
Logger.warn("#{__MODULE__}: #{message}")
|
||||||
|
|
||||||
|
data_migration.state == :complete ->
|
||||||
|
on_complete(data_migration)
|
||||||
|
|
||||||
|
true ->
|
||||||
|
send(self(), :perform)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply, nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info(:perform, state) do
|
||||||
|
State.reinit()
|
||||||
|
|
||||||
|
update_status(:running)
|
||||||
|
put_stat(:iteration_processed_count, 0)
|
||||||
|
put_stat(:started_at, NaiveDateTime.utc_now())
|
||||||
|
|
||||||
|
perform()
|
||||||
|
|
||||||
|
fault_rate = fault_rate()
|
||||||
|
put_stat(:fault_rate, fault_rate)
|
||||||
|
fault_rate_allowance = fault_rate_allowance()
|
||||||
|
|
||||||
|
cond do
|
||||||
|
fault_rate == 0 ->
|
||||||
|
set_complete()
|
||||||
|
|
||||||
|
is_float(fault_rate) and fault_rate <= fault_rate_allowance ->
|
||||||
|
message = """
|
||||||
|
Done with fault rate of #{fault_rate} which doesn't exceed #{fault_rate_allowance}.
|
||||||
|
Putting data migration to manual fix mode. Try running `#{__MODULE__}.retry_failed/0`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Logger.warn("#{__MODULE__}: #{message}")
|
||||||
|
update_status(:manual, message)
|
||||||
|
on_complete(data_migration())
|
||||||
|
|
||||||
|
true ->
|
||||||
|
message = "Too many failures. Try running `#{__MODULE__}.retry_failed/0`."
|
||||||
|
Logger.error("#{__MODULE__}: #{message}")
|
||||||
|
update_status(:failed, message)
|
||||||
|
end
|
||||||
|
|
||||||
|
persist_state()
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp on_complete(data_migration) do
|
||||||
|
if data_migration.feature_lock || feature_state() == :disabled do
|
||||||
|
Logger.warn(
|
||||||
|
"#{__MODULE__}: migration complete but feature is locked; consider enabling."
|
||||||
|
)
|
||||||
|
|
||||||
|
:noop
|
||||||
|
else
|
||||||
|
Config.put(feature_config_path(), :enabled)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Approximate count for current iteration (including processed records count)"
|
||||||
|
def count(force \\ false, timeout \\ :infinity) do
|
||||||
|
stored_count = get_stat(:count)
|
||||||
|
|
||||||
|
if stored_count && !force do
|
||||||
|
stored_count
|
||||||
|
else
|
||||||
|
processed_count = get_stat(:processed_count, 0)
|
||||||
|
max_processed_id = get_stat(:max_processed_id, 0)
|
||||||
|
query = where(query(), [entity], entity.id > ^max_processed_id)
|
||||||
|
|
||||||
|
count = Repo.aggregate(query, :count, :id, timeout: timeout) + processed_count
|
||||||
|
put_stat(:count, count)
|
||||||
|
persist_state()
|
||||||
|
|
||||||
|
count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def failures_count do
|
||||||
|
with {:ok, %{rows: [[count]]}} <-
|
||||||
|
Repo.query(
|
||||||
|
"SELECT COUNT(record_id) FROM data_migration_failed_ids WHERE data_migration_id = $1;",
|
||||||
|
[data_migration_id()]
|
||||||
|
) do
|
||||||
|
count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def feature_state, do: Config.get(feature_config_path())
|
||||||
|
|
||||||
|
def force_continue do
|
||||||
|
send(whereis(), :perform)
|
||||||
|
end
|
||||||
|
|
||||||
|
def force_restart do
|
||||||
|
:ok = State.reset()
|
||||||
|
force_continue()
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_complete do
|
||||||
|
update_status(:complete)
|
||||||
|
persist_state()
|
||||||
|
on_complete(data_migration())
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_status(status, message \\ nil) do
|
||||||
|
put_stat(:state, status)
|
||||||
|
put_stat(:message, message)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fault_rate do
|
||||||
|
with failures_count when is_integer(failures_count) <- failures_count() do
|
||||||
|
failures_count / Enum.max([get_stat(:affected_count, 0), 1])
|
||||||
|
else
|
||||||
|
_ -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp records_per_second do
|
||||||
|
get_stat(:iteration_processed_count, 0) / Enum.max([running_time(), 1])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp running_time do
|
||||||
|
NaiveDateTime.diff(
|
||||||
|
NaiveDateTime.utc_now(),
|
||||||
|
get_stat(:started_at, NaiveDateTime.utc_now())
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
117
lib/pleroma/migrators/support/base_migrator_state.ex
Normal file
117
lib/pleroma/migrators/support/base_migrator_state.ex
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Migrators.Support.BaseMigratorState do
|
||||||
|
@moduledoc """
|
||||||
|
Base background migrator state functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@callback data_migration() :: Pleroma.DataMigration.t()
|
||||||
|
|
||||||
|
defmacro __using__(_opts) do
|
||||||
|
quote do
|
||||||
|
use Agent
|
||||||
|
|
||||||
|
alias Pleroma.DataMigration
|
||||||
|
|
||||||
|
@behaviour Pleroma.Migrators.Support.BaseMigratorState
|
||||||
|
@reg_name {:global, __MODULE__}
|
||||||
|
|
||||||
|
def start_link(_) do
|
||||||
|
Agent.start_link(fn -> load_state_from_db() end, name: @reg_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def data_migration, do: raise("data_migration/0 is not implemented")
|
||||||
|
defoverridable data_migration: 0
|
||||||
|
|
||||||
|
defp load_state_from_db do
|
||||||
|
data_migration = data_migration()
|
||||||
|
|
||||||
|
data =
|
||||||
|
if data_migration do
|
||||||
|
Map.new(data_migration.data, fn {k, v} -> {String.to_atom(k), v} end)
|
||||||
|
else
|
||||||
|
%{}
|
||||||
|
end
|
||||||
|
|
||||||
|
%{
|
||||||
|
data_migration_id: data_migration && data_migration.id,
|
||||||
|
data: data
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def persist_to_db do
|
||||||
|
%{data_migration_id: data_migration_id, data: data} = state()
|
||||||
|
|
||||||
|
if data_migration_id do
|
||||||
|
DataMigration.update_one_by_id(data_migration_id, data: data)
|
||||||
|
else
|
||||||
|
{:error, :nil_data_migration_id}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset do
|
||||||
|
%{data_migration_id: data_migration_id} = state()
|
||||||
|
|
||||||
|
with false <- is_nil(data_migration_id),
|
||||||
|
:ok <-
|
||||||
|
DataMigration.update_one_by_id(data_migration_id,
|
||||||
|
state: :pending,
|
||||||
|
data: %{}
|
||||||
|
) do
|
||||||
|
reinit()
|
||||||
|
else
|
||||||
|
true -> {:error, :nil_data_migration_id}
|
||||||
|
e -> e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def reinit do
|
||||||
|
Agent.update(@reg_name, fn _state -> load_state_from_db() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def state do
|
||||||
|
Agent.get(@reg_name, & &1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_data_key(key, default \\ nil) do
|
||||||
|
get_in(state(), [:data, key]) || default
|
||||||
|
end
|
||||||
|
|
||||||
|
def put_data_key(key, value) do
|
||||||
|
_ = persist_non_data_change(key, value)
|
||||||
|
|
||||||
|
Agent.update(@reg_name, fn state ->
|
||||||
|
put_in(state, [:data, key], value)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def increment_data_key(key, increment \\ 1) do
|
||||||
|
Agent.update(@reg_name, fn state ->
|
||||||
|
initial_value = get_in(state, [:data, key]) || 0
|
||||||
|
updated_value = initial_value + increment
|
||||||
|
put_in(state, [:data, key], updated_value)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp persist_non_data_change(:state, value) do
|
||||||
|
with true <- get_data_key(:state) != value,
|
||||||
|
true <- value in Pleroma.DataMigration.State.__valid_values__(),
|
||||||
|
%{data_migration_id: data_migration_id} when not is_nil(data_migration_id) <-
|
||||||
|
state() do
|
||||||
|
DataMigration.update_one_by_id(data_migration_id, state: value)
|
||||||
|
else
|
||||||
|
false -> :ok
|
||||||
|
_ -> {:error, :nil_data_migration_id}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp persist_non_data_change(_, _) do
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def data_migration_id, do: Map.get(state(), :data_migration_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Object do
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Hashtag
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
alias Pleroma.ObjectTombstone
|
alias Pleroma.ObjectTombstone
|
||||||
|
@ -28,6 +29,8 @@ defmodule Pleroma.Object do
|
||||||
schema "objects" do
|
schema "objects" do
|
||||||
field(:data, :map)
|
field(:data, :map)
|
||||||
|
|
||||||
|
many_to_many(:hashtags, Hashtag, join_through: "hashtags_objects", on_replace: :delete)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -49,7 +52,8 @@ defmodule Pleroma.Object do
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(data) do
|
def create(data) do
|
||||||
Object.change(%Object{}, %{data: data})
|
%Object{}
|
||||||
|
|> Object.change(%{data: data})
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -58,8 +62,41 @@ defmodule Pleroma.Object do
|
||||||
|> cast(params, [:data])
|
|> cast(params, [:data])
|
||||||
|> validate_required([:data])
|
|> validate_required([:data])
|
||||||
|> unique_constraint(:ap_id, name: :objects_unique_apid_index)
|
|> unique_constraint(:ap_id, name: :objects_unique_apid_index)
|
||||||
|
# Expecting `maybe_handle_hashtags_change/1` to run last:
|
||||||
|
|> maybe_handle_hashtags_change(struct)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Note: not checking activity type (assuming non-legacy objects are associated with Create act.)
|
||||||
|
defp maybe_handle_hashtags_change(changeset, struct) do
|
||||||
|
with %Ecto.Changeset{valid?: true} <- changeset,
|
||||||
|
data_hashtags_change = get_change(changeset, :data),
|
||||||
|
{_, true} <- {:changed, hashtags_changed?(struct, data_hashtags_change)},
|
||||||
|
{:ok, hashtag_records} <-
|
||||||
|
data_hashtags_change
|
||||||
|
|> object_data_hashtags()
|
||||||
|
|> Hashtag.get_or_create_by_names() do
|
||||||
|
put_assoc(changeset, :hashtags, hashtag_records)
|
||||||
|
else
|
||||||
|
%{valid?: false} ->
|
||||||
|
changeset
|
||||||
|
|
||||||
|
{:changed, false} ->
|
||||||
|
changeset
|
||||||
|
|
||||||
|
{:error, _} ->
|
||||||
|
validate_change(changeset, :data, fn _, _ ->
|
||||||
|
[data: "error referencing hashtags"]
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp hashtags_changed?(%Object{} = struct, %{"tag" => _} = data) do
|
||||||
|
Enum.sort(embedded_hashtags(struct)) !=
|
||||||
|
Enum.sort(object_data_hashtags(data))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp hashtags_changed?(_, _), do: false
|
||||||
|
|
||||||
def get_by_id(nil), do: nil
|
def get_by_id(nil), do: nil
|
||||||
def get_by_id(id), do: Repo.get(Object, id)
|
def get_by_id(id), do: Repo.get(Object, id)
|
||||||
|
|
||||||
|
@ -187,9 +224,13 @@ defmodule Pleroma.Object do
|
||||||
def swap_object_with_tombstone(object) do
|
def swap_object_with_tombstone(object) do
|
||||||
tombstone = make_tombstone(object)
|
tombstone = make_tombstone(object)
|
||||||
|
|
||||||
object
|
with {:ok, object} <-
|
||||||
|> Object.change(%{data: tombstone})
|
object
|
||||||
|> Repo.update()
|
|> Object.change(%{data: tombstone})
|
||||||
|
|> Repo.update() do
|
||||||
|
Hashtag.unlink(object)
|
||||||
|
{:ok, object}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(%Object{data: %{"id" => id}} = object) do
|
def delete(%Object{data: %{"id" => id}} = object) do
|
||||||
|
@ -349,4 +390,39 @@ defmodule Pleroma.Object do
|
||||||
|
|
||||||
def self_replies(object, opts \\ []),
|
def self_replies(object, opts \\ []),
|
||||||
do: replies(object, Keyword.put(opts, :self_only, true))
|
do: replies(object, Keyword.put(opts, :self_only, true))
|
||||||
|
|
||||||
|
def tags(%Object{data: %{"tag" => tags}}) when is_list(tags), do: tags
|
||||||
|
|
||||||
|
def tags(_), do: []
|
||||||
|
|
||||||
|
def hashtags(%Object{} = object) do
|
||||||
|
# Note: always using embedded hashtags regardless whether they are migrated to hashtags table
|
||||||
|
# (embedded hashtags stay in sync anyways, and we avoid extra joins and preload hassle)
|
||||||
|
embedded_hashtags(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
def embedded_hashtags(%Object{data: data}) do
|
||||||
|
object_data_hashtags(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def embedded_hashtags(_), do: []
|
||||||
|
|
||||||
|
def object_data_hashtags(%{"tag" => tags}) when is_list(tags) do
|
||||||
|
tags
|
||||||
|
|> Enum.filter(fn
|
||||||
|
%{"type" => "Hashtag"} = data -> Map.has_key?(data, "name")
|
||||||
|
plain_text when is_bitstring(plain_text) -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
|> Enum.map(fn
|
||||||
|
%{"name" => "#" <> hashtag} -> String.downcase(hashtag)
|
||||||
|
%{"name" => hashtag} -> String.downcase(hashtag)
|
||||||
|
hashtag when is_bitstring(hashtag) -> String.downcase(hashtag)
|
||||||
|
end)
|
||||||
|
|> Enum.uniq()
|
||||||
|
# Note: "" elements (plain text) might occur in `data.tag` for incoming objects
|
||||||
|
|> Enum.filter(&(&1 not in [nil, ""]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_data_hashtags(_), do: []
|
||||||
end
|
end
|
||||||
|
|
|
@ -93,6 +93,7 @@ defmodule Pleroma.Pagination do
|
||||||
max_id: :string,
|
max_id: :string,
|
||||||
offset: :integer,
|
offset: :integer,
|
||||||
limit: :integer,
|
limit: :integer,
|
||||||
|
skip_extra_order: :boolean,
|
||||||
skip_order: :boolean
|
skip_order: :boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +115,8 @@ defmodule Pleroma.Pagination do
|
||||||
|
|
||||||
defp restrict(query, :order, %{skip_order: true}, _), do: query
|
defp restrict(query, :order, %{skip_order: true}, _), do: query
|
||||||
|
|
||||||
|
defp restrict(%{order_bys: [_ | _]} = query, :order, %{skip_extra_order: true}, _), do: query
|
||||||
|
|
||||||
defp restrict(query, :order, %{min_id: _}, table_binding) do
|
defp restrict(query, :order, %{min_id: _}, table_binding) do
|
||||||
order_by(
|
order_by(
|
||||||
query,
|
query,
|
||||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Repo do
|
||||||
adapter: Ecto.Adapters.Postgres,
|
adapter: Ecto.Adapters.Postgres,
|
||||||
migration_timestamps: [type: :naive_datetime_usec]
|
migration_timestamps: [type: :naive_datetime_usec]
|
||||||
|
|
||||||
|
use Ecto.Explain
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -63,8 +65,8 @@ defmodule Pleroma.Repo do
|
||||||
iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500, :batches)
|
iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500, :batches)
|
||||||
"""
|
"""
|
||||||
@spec chunk_stream(Ecto.Query.t(), integer(), atom()) :: Enumerable.t()
|
@spec chunk_stream(Ecto.Query.t(), integer(), atom()) :: Enumerable.t()
|
||||||
def chunk_stream(query, chunk_size, returns_as \\ :one) do
|
def chunk_stream(query, chunk_size, returns_as \\ :one, query_options \\ []) do
|
||||||
# We don't actually need start and end funcitons of resource streaming,
|
# We don't actually need start and end functions of resource streaming,
|
||||||
# but it seems to be the only way to not fetch records one-by-one and
|
# but it seems to be the only way to not fetch records one-by-one and
|
||||||
# have individual records be the elements of the stream, instead of
|
# have individual records be the elements of the stream, instead of
|
||||||
# lists of records
|
# lists of records
|
||||||
|
@ -76,7 +78,7 @@ defmodule Pleroma.Repo do
|
||||||
|> order_by(asc: :id)
|
|> order_by(asc: :id)
|
||||||
|> where([r], r.id > ^last_id)
|
|> where([r], r.id > ^last_id)
|
||||||
|> limit(^chunk_size)
|
|> limit(^chunk_size)
|
||||||
|> all()
|
|> all(query_options)
|
||||||
|> case do
|
|> case do
|
||||||
[] ->
|
[] ->
|
||||||
{:halt, last_id}
|
{:halt, last_id}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.ReverseProxy do
|
defmodule Pleroma.ReverseProxy do
|
||||||
@range_headers ~w(range if-range)
|
@range_headers ~w(range if-range)
|
||||||
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
|
@keep_req_headers ~w(accept accept-encoding cache-control if-modified-since) ++
|
||||||
~w(if-unmodified-since if-none-match) ++ @range_headers
|
~w(if-unmodified-since if-none-match) ++ @range_headers
|
||||||
@resp_cache_headers ~w(etag date last-modified)
|
@resp_cache_headers ~w(etag date last-modified)
|
||||||
@keep_resp_headers @resp_cache_headers ++
|
@keep_resp_headers @resp_cache_headers ++
|
||||||
|
@ -57,9 +57,6 @@ defmodule Pleroma.ReverseProxy do
|
||||||
* `false` will add `content-disposition: attachment` to any request,
|
* `false` will add `content-disposition: attachment` to any request,
|
||||||
* a list of whitelisted content types
|
* a list of whitelisted content types
|
||||||
|
|
||||||
* `keep_user_agent` will forward the client's user-agent to the upstream. This may be useful if the upstream is
|
|
||||||
doing content transformation (encoding, …) depending on the request.
|
|
||||||
|
|
||||||
* `req_headers`, `resp_headers` additional headers.
|
* `req_headers`, `resp_headers` additional headers.
|
||||||
|
|
||||||
* `http`: options for [hackney](https://github.com/benoitc/hackney) or [gun](https://github.com/ninenines/gun).
|
* `http`: options for [hackney](https://github.com/benoitc/hackney) or [gun](https://github.com/ninenines/gun).
|
||||||
|
@ -84,8 +81,7 @@ defmodule Pleroma.ReverseProxy do
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
@type option() ::
|
@type option() ::
|
||||||
{:keep_user_agent, boolean}
|
{:max_read_duration, :timer.time() | :infinity}
|
||||||
| {:max_read_duration, :timer.time() | :infinity}
|
|
||||||
| {:max_body_length, non_neg_integer() | :infinity}
|
| {:max_body_length, non_neg_integer() | :infinity}
|
||||||
| {:failed_request_ttl, :timer.time() | :infinity}
|
| {:failed_request_ttl, :timer.time() | :infinity}
|
||||||
| {:http, []}
|
| {:http, []}
|
||||||
|
@ -291,17 +287,13 @@ defmodule Pleroma.ReverseProxy do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_req_user_agent_header(headers, opts) do
|
defp build_req_user_agent_header(headers, _opts) do
|
||||||
if Keyword.get(opts, :keep_user_agent, false) do
|
List.keystore(
|
||||||
List.keystore(
|
headers,
|
||||||
headers,
|
"user-agent",
|
||||||
"user-agent",
|
0,
|
||||||
0,
|
{"user-agent", Pleroma.Application.user_agent()}
|
||||||
{"user-agent", Pleroma.Application.user_agent()}
|
)
|
||||||
)
|
|
||||||
else
|
|
||||||
headers
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_resp_headers(headers, opts) do
|
defp build_resp_headers(headers, opts) do
|
||||||
|
|
|
@ -2255,13 +2255,6 @@ defmodule Pleroma.User do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
|
|
||||||
%{
|
|
||||||
admin: is_admin,
|
|
||||||
moderator: is_moderator
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_fields(changeset, remote? \\ false) do
|
def validate_fields(changeset, remote? \\ false) do
|
||||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||||
limit = Config.get([:instance, limit_name], 0)
|
limit = Config.get([:instance, limit_name], 0)
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Conversation
|
alias Pleroma.Conversation
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Filter
|
alias Pleroma.Filter
|
||||||
|
alias Pleroma.Hashtag
|
||||||
alias Pleroma.Maps
|
alias Pleroma.Maps
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -465,6 +466,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp fetch_paginated_optimized(query, opts, pagination) do
|
||||||
|
# Note: tag-filtering funcs may apply "ORDER BY objects.id DESC",
|
||||||
|
# and extra sorting on "activities.id DESC NULLS LAST" would worse the query plan
|
||||||
|
opts = Map.put(opts, :skip_extra_order, true)
|
||||||
|
|
||||||
|
Pagination.fetch_paginated(query, opts, pagination)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
|
||||||
|
list_memberships = Pleroma.List.memberships(opts[:user])
|
||||||
|
|
||||||
|
fetch_activities_query(recipients ++ list_memberships, opts)
|
||||||
|
|> fetch_paginated_optimized(opts, pagination)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|> maybe_update_cc(list_memberships, opts[:user])
|
||||||
|
end
|
||||||
|
|
||||||
@spec fetch_public_or_unlisted_activities(map(), Pagination.type()) :: [Activity.t()]
|
@spec fetch_public_or_unlisted_activities(map(), Pagination.type()) :: [Activity.t()]
|
||||||
def fetch_public_or_unlisted_activities(opts \\ %{}, pagination \\ :keyset) do
|
def fetch_public_or_unlisted_activities(opts \\ %{}, pagination \\ :keyset) do
|
||||||
opts = Map.delete(opts, :user)
|
opts = Map.delete(opts, :user)
|
||||||
|
@ -472,7 +490,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
[Constants.as_public()]
|
[Constants.as_public()]
|
||||||
|> fetch_activities_query(opts)
|
|> fetch_activities_query(opts)
|
||||||
|> restrict_unlisted(opts)
|
|> restrict_unlisted(opts)
|
||||||
|> Pagination.fetch_paginated(opts, pagination)
|
|> fetch_paginated_optimized(opts, pagination)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]
|
@spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]
|
||||||
|
@ -693,52 +711,144 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp restrict_since(query, _), do: query
|
defp restrict_since(query, _), do: query
|
||||||
|
|
||||||
defp restrict_tag_reject(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
|
defp restrict_embedded_tag_all(_query, %{tag_all: _tag_all, skip_preload: true}) do
|
||||||
raise "Can't use the child object without preloading!"
|
raise_on_missing_preload()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_tag_reject(query, %{tag_reject: [_ | _] = tag_reject}) do
|
defp restrict_embedded_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
|
||||||
from(
|
|
||||||
[_activity, object] in query,
|
|
||||||
where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp restrict_tag_reject(query, _), do: query
|
|
||||||
|
|
||||||
defp restrict_tag_all(_query, %{tag_all: _tag_all, skip_preload: true}) do
|
|
||||||
raise "Can't use the child object without preloading!"
|
|
||||||
end
|
|
||||||
|
|
||||||
defp restrict_tag_all(query, %{tag_all: [_ | _] = tag_all}) do
|
|
||||||
from(
|
from(
|
||||||
[_activity, object] in query,
|
[_activity, object] in query,
|
||||||
where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
|
where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_tag_all(query, _), do: query
|
defp restrict_embedded_tag_all(query, %{tag_all: tag}) when is_binary(tag) do
|
||||||
|
restrict_embedded_tag_any(query, %{tag: tag})
|
||||||
|
end
|
||||||
|
|
||||||
defp restrict_tag(_query, %{tag: _tag, skip_preload: true}) do
|
defp restrict_embedded_tag_all(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_embedded_tag_any(_query, %{tag: _tag, skip_preload: true}) do
|
||||||
|
raise_on_missing_preload()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_embedded_tag_any(query, %{tag: [_ | _] = tag_any}) do
|
||||||
|
from(
|
||||||
|
[_activity, object] in query,
|
||||||
|
where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag_any)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_embedded_tag_any(query, %{tag: tag}) when is_binary(tag) do
|
||||||
|
restrict_embedded_tag_any(query, %{tag: [tag]})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_embedded_tag_any(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_embedded_tag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
|
||||||
|
raise_on_missing_preload()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_embedded_tag_reject_any(query, %{tag_reject: [_ | _] = tag_reject}) do
|
||||||
|
from(
|
||||||
|
[_activity, object] in query,
|
||||||
|
where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_embedded_tag_reject_any(query, %{tag_reject: tag_reject})
|
||||||
|
when is_binary(tag_reject) do
|
||||||
|
restrict_embedded_tag_reject_any(query, %{tag_reject: [tag_reject]})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_embedded_tag_reject_any(query, _), do: query
|
||||||
|
|
||||||
|
defp object_ids_query_for_tags(tags) do
|
||||||
|
from(hto in "hashtags_objects")
|
||||||
|
|> join(:inner, [hto], ht in Pleroma.Hashtag, on: hto.hashtag_id == ht.id)
|
||||||
|
|> where([hto, ht], ht.name in ^tags)
|
||||||
|
|> select([hto], hto.object_id)
|
||||||
|
|> distinct([hto], true)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_all(_query, %{tag_all: _tag, skip_preload: true}) do
|
||||||
|
raise_on_missing_preload()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_all(query, %{tag_all: [single_tag]}) do
|
||||||
|
restrict_hashtag_any(query, %{tag: single_tag})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_all(query, %{tag_all: [_ | _] = tags}) do
|
||||||
|
from(
|
||||||
|
[_activity, object] in query,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"""
|
||||||
|
(SELECT array_agg(hashtags.name) FROM hashtags JOIN hashtags_objects
|
||||||
|
ON hashtags_objects.hashtag_id = hashtags.id WHERE hashtags.name = ANY(?)
|
||||||
|
AND hashtags_objects.object_id = ?) @> ?
|
||||||
|
""",
|
||||||
|
^tags,
|
||||||
|
object.id,
|
||||||
|
^tags
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_all(query, %{tag_all: tag}) when is_binary(tag) do
|
||||||
|
restrict_hashtag_all(query, %{tag_all: [tag]})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_all(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_hashtag_any(_query, %{tag: _tag, skip_preload: true}) do
|
||||||
|
raise_on_missing_preload()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_any(query, %{tag: [_ | _] = tags}) do
|
||||||
|
hashtag_ids =
|
||||||
|
from(ht in Hashtag, where: ht.name in ^tags, select: ht.id)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
# Note: NO extra ordering should be done on "activities.id desc nulls last" for optimal plan
|
||||||
|
from(
|
||||||
|
[_activity, object] in query,
|
||||||
|
join: hto in "hashtags_objects",
|
||||||
|
on: hto.object_id == object.id,
|
||||||
|
where: hto.hashtag_id in ^hashtag_ids,
|
||||||
|
distinct: [desc: object.id],
|
||||||
|
order_by: [desc: object.id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_any(query, %{tag: tag}) when is_binary(tag) do
|
||||||
|
restrict_hashtag_any(query, %{tag: [tag]})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_any(query, _), do: query
|
||||||
|
|
||||||
|
defp restrict_hashtag_reject_any(_query, %{tag_reject: _tag_reject, skip_preload: true}) do
|
||||||
|
raise_on_missing_preload()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_reject_any(query, %{tag_reject: [_ | _] = tags_reject}) do
|
||||||
|
from(
|
||||||
|
[_activity, object] in query,
|
||||||
|
where: object.id not in subquery(object_ids_query_for_tags(tags_reject))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_reject_any(query, %{tag_reject: tag_reject}) when is_binary(tag_reject) do
|
||||||
|
restrict_hashtag_reject_any(query, %{tag_reject: [tag_reject]})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_hashtag_reject_any(query, _), do: query
|
||||||
|
|
||||||
|
defp raise_on_missing_preload do
|
||||||
raise "Can't use the child object without preloading!"
|
raise "Can't use the child object without preloading!"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_tag(query, %{tag: tag}) when is_list(tag) do
|
|
||||||
from(
|
|
||||||
[_activity, object] in query,
|
|
||||||
where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp restrict_tag(query, %{tag: tag}) when is_binary(tag) do
|
|
||||||
from(
|
|
||||||
[_activity, object] in query,
|
|
||||||
where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp restrict_tag(query, _), do: query
|
|
||||||
|
|
||||||
defp restrict_recipients(query, [], _user), do: query
|
defp restrict_recipients(query, [], _user), do: query
|
||||||
|
|
||||||
defp restrict_recipients(query, recipients, nil) do
|
defp restrict_recipients(query, recipients, nil) do
|
||||||
|
@ -1098,6 +1208,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp maybe_order(query, _), do: query
|
defp maybe_order(query, _), do: query
|
||||||
|
|
||||||
|
defp normalize_fetch_activities_query_opts(opts) do
|
||||||
|
Enum.reduce([:tag, :tag_all, :tag_reject], opts, fn key, opts ->
|
||||||
|
case opts[key] do
|
||||||
|
value when is_bitstring(value) ->
|
||||||
|
Map.put(opts, key, Hashtag.normalize_name(value))
|
||||||
|
|
||||||
|
value when is_list(value) ->
|
||||||
|
normalized_value =
|
||||||
|
value
|
||||||
|
|> Enum.map(&Hashtag.normalize_name/1)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
Map.put(opts, key, normalized_value)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
defp fetch_activities_query_ap_ids_ops(opts) do
|
defp fetch_activities_query_ap_ids_ops(opts) do
|
||||||
source_user = opts[:muting_user]
|
source_user = opts[:muting_user]
|
||||||
ap_id_relationships = if source_user, do: [:mute, :reblog_mute], else: []
|
ap_id_relationships = if source_user, do: [:mute, :reblog_mute], else: []
|
||||||
|
@ -1121,6 +1251,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|
opts = normalize_fetch_activities_query_opts(opts)
|
||||||
|
|
||||||
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
|
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
|
||||||
fetch_activities_query_ap_ids_ops(opts)
|
fetch_activities_query_ap_ids_ops(opts)
|
||||||
|
|
||||||
|
@ -1128,50 +1260,51 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
|
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
|
||||||
}
|
}
|
||||||
|
|
||||||
Activity
|
query =
|
||||||
|> maybe_preload_objects(opts)
|
Activity
|
||||||
|> maybe_preload_bookmarks(opts)
|
|> maybe_preload_objects(opts)
|
||||||
|> maybe_preload_report_notes(opts)
|
|> maybe_preload_bookmarks(opts)
|
||||||
|> maybe_set_thread_muted_field(opts)
|
|> maybe_preload_report_notes(opts)
|
||||||
|> maybe_order(opts)
|
|> maybe_set_thread_muted_field(opts)
|
||||||
|> restrict_recipients(recipients, opts[:user])
|
|> maybe_order(opts)
|
||||||
|> restrict_replies(opts)
|
|> restrict_recipients(recipients, opts[:user])
|
||||||
|> restrict_tag(opts)
|
|> restrict_replies(opts)
|
||||||
|> restrict_tag_reject(opts)
|
|> restrict_since(opts)
|
||||||
|> restrict_tag_all(opts)
|
|> restrict_local(opts)
|
||||||
|> restrict_since(opts)
|
|> restrict_remote(opts)
|
||||||
|> restrict_local(opts)
|
|> restrict_actor(opts)
|
||||||
|> restrict_remote(opts)
|
|> restrict_type(opts)
|
||||||
|> restrict_actor(opts)
|
|> restrict_state(opts)
|
||||||
|> restrict_type(opts)
|
|> restrict_favorited_by(opts)
|
||||||
|> restrict_state(opts)
|
|> restrict_blocked(restrict_blocked_opts)
|
||||||
|> restrict_favorited_by(opts)
|
|> restrict_muted(restrict_muted_opts)
|
||||||
|> restrict_blocked(restrict_blocked_opts)
|
|> restrict_filtered(opts)
|
||||||
|> restrict_muted(restrict_muted_opts)
|
|> restrict_media(opts)
|
||||||
|> restrict_filtered(opts)
|
|> restrict_visibility(opts)
|
||||||
|> restrict_media(opts)
|
|> restrict_thread_visibility(opts, config)
|
||||||
|> restrict_visibility(opts)
|
|> restrict_reblogs(opts)
|
||||||
|> restrict_thread_visibility(opts, config)
|
|> restrict_pinned(opts)
|
||||||
|> restrict_reblogs(opts)
|
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
||||||
|> restrict_pinned(opts)
|
|> restrict_instance(opts)
|
||||||
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|
|> restrict_announce_object_actor(opts)
|
||||||
|> restrict_instance(opts)
|
|> restrict_filtered(opts)
|
||||||
|> restrict_announce_object_actor(opts)
|
|> Activity.restrict_deactivated_users()
|
||||||
|> restrict_filtered(opts)
|
|> exclude_poll_votes(opts)
|
||||||
|> Activity.restrict_deactivated_users()
|
|> exclude_chat_messages(opts)
|
||||||
|> exclude_poll_votes(opts)
|
|> exclude_invisible_actors(opts)
|
||||||
|> exclude_chat_messages(opts)
|
|> exclude_visibility(opts)
|
||||||
|> exclude_invisible_actors(opts)
|
|
||||||
|> exclude_visibility(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
|
if Config.feature_enabled?(:improved_hashtag_timeline) do
|
||||||
list_memberships = Pleroma.List.memberships(opts[:user])
|
query
|
||||||
|
|> restrict_hashtag_any(opts)
|
||||||
fetch_activities_query(recipients ++ list_memberships, opts)
|
|> restrict_hashtag_all(opts)
|
||||||
|> Pagination.fetch_paginated(opts, pagination)
|
|> restrict_hashtag_reject_any(opts)
|
||||||
|> Enum.reverse()
|
else
|
||||||
|> maybe_update_cc(list_memberships, opts[:user])
|
query
|
||||||
|
|> restrict_embedded_tag_any(opts)
|
||||||
|
|> restrict_embedded_tag_all(opts)
|
||||||
|
|> restrict_embedded_tag_reject_any(opts)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -1250,21 +1383,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp get_actor_url(_url), do: nil
|
defp get_actor_url(_url), do: nil
|
||||||
|
|
||||||
|
defp normalize_image(%{"url" => url}) do
|
||||||
|
%{
|
||||||
|
"type" => "Image",
|
||||||
|
"url" => [%{"href" => url}]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
|
||||||
|
defp normalize_image(_), do: nil
|
||||||
|
|
||||||
defp object_to_user_data(data) do
|
defp object_to_user_data(data) do
|
||||||
avatar =
|
|
||||||
data["icon"]["url"] &&
|
|
||||||
%{
|
|
||||||
"type" => "Image",
|
|
||||||
"url" => [%{"href" => data["icon"]["url"]}]
|
|
||||||
}
|
|
||||||
|
|
||||||
banner =
|
|
||||||
data["image"]["url"] &&
|
|
||||||
%{
|
|
||||||
"type" => "Image",
|
|
||||||
"url" => [%{"href" => data["image"]["url"]}]
|
|
||||||
}
|
|
||||||
|
|
||||||
fields =
|
fields =
|
||||||
data
|
data
|
||||||
|> Map.get("attachment", [])
|
|> Map.get("attachment", [])
|
||||||
|
@ -1308,13 +1437,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
ap_id: data["id"],
|
ap_id: data["id"],
|
||||||
uri: get_actor_url(data["url"]),
|
uri: get_actor_url(data["url"]),
|
||||||
ap_enabled: true,
|
ap_enabled: true,
|
||||||
banner: banner,
|
banner: normalize_image(data["image"]),
|
||||||
fields: fields,
|
fields: fields,
|
||||||
emoji: emojis,
|
emoji: emojis,
|
||||||
is_locked: is_locked,
|
is_locked: is_locked,
|
||||||
is_discoverable: is_discoverable,
|
is_discoverable: is_discoverable,
|
||||||
invisible: invisible,
|
invisible: invisible,
|
||||||
avatar: avatar,
|
avatar: normalize_image(data["icon"]),
|
||||||
name: data["name"],
|
name: data["name"],
|
||||||
follower_address: data["followers"],
|
follower_address: data["followers"],
|
||||||
following_address: data["following"],
|
following_address: data["following"],
|
||||||
|
|
|
@ -92,7 +92,9 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_policies do
|
def get_policies do
|
||||||
Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
|
Pleroma.Config.get([:mrf, :policies], [])
|
||||||
|
|> get_policies()
|
||||||
|
|> Enum.concat([Pleroma.Web.ActivityPub.MRF.HashtagPolicy])
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_policies(policy) when is_atom(policy), do: [policy]
|
defp get_policies(policy) when is_atom(policy), do: [policy]
|
||||||
|
|
116
lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
Normal file
116
lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Object
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #)
|
||||||
|
|
||||||
|
Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
defp check_reject(message, hashtags) do
|
||||||
|
if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do
|
||||||
|
{:reject, "[HashtagPolicy] Matches with rejected keyword"}
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_ftl_removal(%{"to" => to} = message, hashtags) do
|
||||||
|
if Pleroma.Constants.as_public() in to and
|
||||||
|
Enum.any?(Config.get([:mrf_hashtag, :federated_timeline_removal]), fn match ->
|
||||||
|
match in hashtags
|
||||||
|
end) do
|
||||||
|
to = List.delete(to, Pleroma.Constants.as_public())
|
||||||
|
cc = [Pleroma.Constants.as_public() | message["cc"] || []]
|
||||||
|
|
||||||
|
message =
|
||||||
|
message
|
||||||
|
|> Map.put("to", to)
|
||||||
|
|> Map.put("cc", cc)
|
||||||
|
|> Kernel.put_in(["object", "to"], to)
|
||||||
|
|> Kernel.put_in(["object", "cc"], cc)
|
||||||
|
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_ftl_removal(message, _hashtags), do: {:ok, message}
|
||||||
|
|
||||||
|
defp check_sensitive(message, hashtags) do
|
||||||
|
if Enum.any?(Config.get([:mrf_hashtag, :sensitive]), fn match -> match in hashtags end) do
|
||||||
|
{:ok, Kernel.put_in(message, ["object", "sensitive"], true)}
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"type" => "Create", "object" => object} = message) do
|
||||||
|
hashtags = Object.hashtags(%Object{data: object})
|
||||||
|
|
||||||
|
if hashtags != [] do
|
||||||
|
with {:ok, message} <- check_reject(message, hashtags),
|
||||||
|
{:ok, message} <- check_ftl_removal(message, hashtags),
|
||||||
|
{:ok, message} <- check_sensitive(message, hashtags) do
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
mrf_hashtag =
|
||||||
|
Config.get(:mrf_hashtag)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
{:ok, %{mrf_hashtag: mrf_hashtag}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_hashtag,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.HashtagPolicy",
|
||||||
|
label: "MRF Hashtag",
|
||||||
|
description: @moduledoc,
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :reject,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "A list of hashtags which result in message being rejected.",
|
||||||
|
suggestions: ["foo"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :federated_timeline_removal,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"A list of hashtags which result in message being removed from federated timelines (a.k.a unlisted).",
|
||||||
|
suggestions: ["foo"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :sensitive,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"A list of hashtags which result in message being set as sensitive (a.k.a NSFW/R-18)",
|
||||||
|
suggestions: ["nsfw", "r18"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -64,20 +64,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||||
%{host: actor_host} = _actor_info,
|
%{host: actor_host} = _actor_info,
|
||||||
%{
|
%{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"object" => child_object
|
"object" => %{} = _child_object
|
||||||
} = object
|
} = object
|
||||||
)
|
) do
|
||||||
when is_map(child_object) do
|
|
||||||
media_nsfw =
|
media_nsfw =
|
||||||
Config.get([:mrf_simple, :media_nsfw])
|
Config.get([:mrf_simple, :media_nsfw])
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
if MRF.subdomain_match?(media_nsfw, actor_host) do
|
if MRF.subdomain_match?(media_nsfw, actor_host) do
|
||||||
tags = (child_object["tag"] || []) ++ ["nsfw"]
|
Kernel.put_in(object, ["object", "sensitive"], true)
|
||||||
child_object = Map.put(child_object, "tag", tags)
|
|
||||||
child_object = Map.put(child_object, "sensitive", true)
|
|
||||||
Map.put(object, "object", child_object)
|
|
||||||
else
|
else
|
||||||
object
|
object
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,20 +28,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|
||||||
"mrf_tag:media-force-nsfw",
|
"mrf_tag:media-force-nsfw",
|
||||||
%{
|
%{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"object" => %{"attachment" => child_attachment} = object
|
"object" => %{"attachment" => child_attachment}
|
||||||
} = message
|
} = message
|
||||||
)
|
)
|
||||||
when length(child_attachment) > 0 do
|
when length(child_attachment) > 0 do
|
||||||
tags = (object["tag"] || []) ++ ["nsfw"]
|
{:ok, Kernel.put_in(message, ["object", "sensitive"], true)}
|
||||||
|
|
||||||
object =
|
|
||||||
object
|
|
||||||
|> Map.put("tag", tags)
|
|
||||||
|> Map.put("sensitive", true)
|
|
||||||
|
|
||||||
message = Map.put(message, "object", object)
|
|
||||||
|
|
||||||
{:ok, message}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp process_tag(
|
defp process_tag(
|
||||||
|
|
|
@ -32,18 +32,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
"""
|
"""
|
||||||
def fix_object(object, options \\ []) do
|
def fix_object(object, options \\ []) do
|
||||||
object
|
object
|
||||||
|> strip_internal_fields
|
|> strip_internal_fields()
|
||||||
|> fix_actor
|
|> fix_actor()
|
||||||
|> fix_url
|
|> fix_url()
|
||||||
|> fix_attachments
|
|> fix_attachments()
|
||||||
|> fix_context
|
|> fix_context()
|
||||||
|> fix_in_reply_to(options)
|
|> fix_in_reply_to(options)
|
||||||
|> fix_emoji
|
|> fix_emoji()
|
||||||
|> fix_tag
|
|> fix_tag()
|
||||||
|> set_sensitive
|
|> fix_content_map()
|
||||||
|> fix_content_map
|
|> fix_addressing()
|
||||||
|> fix_addressing
|
|> fix_summary()
|
||||||
|> fix_summary
|
|
||||||
|> fix_type(options)
|
|> fix_type(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -315,10 +314,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
tags =
|
tags =
|
||||||
tag
|
tag
|
||||||
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
|
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
|
||||||
|> Enum.map(fn %{"name" => name} ->
|
|> Enum.map(fn
|
||||||
name
|
%{"name" => "#" <> hashtag} -> String.downcase(hashtag)
|
||||||
|> String.slice(1..-1)
|
%{"name" => hashtag} -> String.downcase(hashtag)
|
||||||
|> String.downcase()
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Map.put(object, "tag", tag ++ tags)
|
Map.put(object, "tag", tag ++ tags)
|
||||||
|
@ -742,7 +740,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
# Prepares the object of an outgoing create activity.
|
# Prepares the object of an outgoing create activity.
|
||||||
def prepare_object(object) do
|
def prepare_object(object) do
|
||||||
object
|
object
|
||||||
|> set_sensitive
|
|
||||||
|> add_hashtags
|
|> add_hashtags
|
||||||
|> add_mention_tags
|
|> add_mention_tags
|
||||||
|> add_emoji_tags
|
|> add_emoji_tags
|
||||||
|
@ -933,15 +930,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
Map.put(object, "conversation", object["context"])
|
Map.put(object, "conversation", object["context"])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_sensitive(%{"sensitive" => _} = object) do
|
|
||||||
object
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_sensitive(object) do
|
|
||||||
tags = object["tag"] || []
|
|
||||||
Map.put(object, "sensitive", "nsfw" in tags)
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_type(%{"type" => "Answer"} = object) do
|
def set_type(%{"type" => "Answer"} = object) do
|
||||||
Map.put(object, "type", "Note")
|
Map.put(object, "type", "Note")
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,16 +13,17 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
alias Pleroma.Web.ActivityPub.Builder
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.Pipeline
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.AdminAPI
|
alias Pleroma.Web.AdminAPI
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
|
||||||
alias Pleroma.Web.AdminAPI.Search
|
alias Pleroma.Web.AdminAPI.Search
|
||||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||||
|
|
||||||
@users_page_size 50
|
@users_page_size 50
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["admin:read:accounts"]}
|
%{scopes: ["admin:read:accounts"]}
|
||||||
when action in [:list, :show]
|
when action in [:index, :show]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -44,13 +45,19 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
when action in [:follow, :unfollow]
|
when action in [:follow, :unfollow]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plug(:put_view, Pleroma.Web.AdminAPI.AccountView)
|
||||||
|
|
||||||
action_fallback(AdminAPI.FallbackController)
|
action_fallback(AdminAPI.FallbackController)
|
||||||
|
|
||||||
def delete(conn, %{"nickname" => nickname}) do
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation
|
||||||
delete(conn, %{"nicknames" => [nickname]})
|
|
||||||
|
def delete(conn, %{nickname: nickname}) do
|
||||||
|
conn
|
||||||
|
|> Map.put(:body_params, %{nicknames: [nickname]})
|
||||||
|
|> delete(%{})
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
def delete(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
|
||||||
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
||||||
|
|
||||||
Enum.each(users, fn user ->
|
Enum.each(users, fn user ->
|
||||||
|
@ -67,10 +74,16 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
json(conn, nicknames)
|
json(conn, nicknames)
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow(%{assigns: %{user: admin}} = conn, %{
|
def follow(
|
||||||
"follower" => follower_nick,
|
%{
|
||||||
"followed" => followed_nick
|
assigns: %{user: admin},
|
||||||
}) do
|
body_params: %{
|
||||||
|
follower: follower_nick,
|
||||||
|
followed: followed_nick
|
||||||
|
}
|
||||||
|
} = conn,
|
||||||
|
_
|
||||||
|
) do
|
||||||
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
|
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
|
||||||
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
|
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
|
||||||
User.follow(follower, followed)
|
User.follow(follower, followed)
|
||||||
|
@ -86,10 +99,16 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
json(conn, "ok")
|
json(conn, "ok")
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfollow(%{assigns: %{user: admin}} = conn, %{
|
def unfollow(
|
||||||
"follower" => follower_nick,
|
%{
|
||||||
"followed" => followed_nick
|
assigns: %{user: admin},
|
||||||
}) do
|
body_params: %{
|
||||||
|
follower: follower_nick,
|
||||||
|
followed: followed_nick
|
||||||
|
}
|
||||||
|
} = conn,
|
||||||
|
_
|
||||||
|
) do
|
||||||
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
|
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
|
||||||
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
|
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
|
||||||
User.unfollow(follower, followed)
|
User.unfollow(follower, followed)
|
||||||
|
@ -105,9 +124,10 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
json(conn, "ok")
|
json(conn, "ok")
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
|
def create(%{assigns: %{user: admin}, body_params: %{users: users}} = conn, _) do
|
||||||
changesets =
|
changesets =
|
||||||
Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
|
users
|
||||||
|
|> Enum.map(fn %{nickname: nickname, email: email, password: password} ->
|
||||||
user_data = %{
|
user_data = %{
|
||||||
nickname: nickname,
|
nickname: nickname,
|
||||||
name: nickname,
|
name: nickname,
|
||||||
|
@ -124,52 +144,49 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
case Pleroma.Repo.transaction(changesets) do
|
case Pleroma.Repo.transaction(changesets) do
|
||||||
{:ok, users} ->
|
{:ok, users_map} ->
|
||||||
res =
|
users =
|
||||||
users
|
users_map
|
||||||
|> Map.values()
|
|> Map.values()
|
||||||
|> Enum.map(fn user ->
|
|> Enum.map(fn user ->
|
||||||
{:ok, user} = User.post_register_action(user)
|
{:ok, user} = User.post_register_action(user)
|
||||||
|
|
||||||
user
|
user
|
||||||
end)
|
end)
|
||||||
|> Enum.map(&AccountView.render("created.json", %{user: &1}))
|
|
||||||
|
|
||||||
ModerationLog.insert_log(%{
|
ModerationLog.insert_log(%{
|
||||||
actor: admin,
|
actor: admin,
|
||||||
subjects: Map.values(users),
|
subjects: users,
|
||||||
action: "create"
|
action: "create"
|
||||||
})
|
})
|
||||||
|
|
||||||
json(conn, res)
|
render(conn, "created_many.json", users: users)
|
||||||
|
|
||||||
{:error, id, changeset, _} ->
|
{:error, id, changeset, _} ->
|
||||||
res =
|
changesets =
|
||||||
Enum.map(changesets.operations, fn
|
Enum.map(changesets.operations, fn
|
||||||
{current_id, {:changeset, _current_changeset, _}} when current_id == id ->
|
{^id, {:changeset, _current_changeset, _}} ->
|
||||||
AccountView.render("create-error.json", %{changeset: changeset})
|
changeset
|
||||||
|
|
||||||
{_, {:changeset, current_changeset, _}} ->
|
{_, {:changeset, current_changeset, _}} ->
|
||||||
AccountView.render("create-error.json", %{changeset: current_changeset})
|
current_changeset
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_status(:conflict)
|
|> put_status(:conflict)
|
||||||
|> json(res)
|
|> render("create_errors.json", changesets: changesets)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
def show(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
|
||||||
conn
|
render(conn, "show.json", %{user: user})
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("show.json", %{user: user})
|
|
||||||
else
|
else
|
||||||
_ -> {:error, :not_found}
|
_ -> {:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
def toggle_activation(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
|
||||||
user = User.get_cached_by_nickname(nickname)
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
{:ok, updated_user} = User.set_activation(user, !user.is_active)
|
{:ok, updated_user} = User.set_activation(user, !user.is_active)
|
||||||
|
@ -182,12 +199,10 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
action: action
|
action: action
|
||||||
})
|
})
|
||||||
|
|
||||||
conn
|
render(conn, "show.json", user: updated_user)
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("show.json", %{user: updated_user})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
def activate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
|
||||||
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
||||||
{:ok, updated_users} = User.set_activation(users, true)
|
{:ok, updated_users} = User.set_activation(users, true)
|
||||||
|
|
||||||
|
@ -197,12 +212,10 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
action: "activate"
|
action: "activate"
|
||||||
})
|
})
|
||||||
|
|
||||||
conn
|
render(conn, "index.json", users: Keyword.values(updated_users))
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("index.json", %{users: Keyword.values(updated_users)})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
def deactivate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
|
||||||
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
||||||
{:ok, updated_users} = User.set_activation(users, false)
|
{:ok, updated_users} = User.set_activation(users, false)
|
||||||
|
|
||||||
|
@ -212,12 +225,10 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
action: "deactivate"
|
action: "deactivate"
|
||||||
})
|
})
|
||||||
|
|
||||||
conn
|
render(conn, "index.json", users: Keyword.values(updated_users))
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("index.json", %{users: Keyword.values(updated_users)})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
|
||||||
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
|
||||||
{:ok, updated_users} = User.approve(users)
|
{:ok, updated_users} = User.approve(users)
|
||||||
|
|
||||||
|
@ -227,36 +238,27 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
action: "approve"
|
action: "approve"
|
||||||
})
|
})
|
||||||
|
|
||||||
conn
|
render(conn, "index.json", users: updated_users)
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("index.json", %{users: updated_users})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def list(conn, params) do
|
def index(conn, params) do
|
||||||
{page, page_size} = page_params(params)
|
{page, page_size} = page_params(params)
|
||||||
filters = maybe_parse_filters(params["filters"])
|
filters = maybe_parse_filters(params[:filters])
|
||||||
|
|
||||||
search_params =
|
search_params =
|
||||||
%{
|
%{
|
||||||
query: params["query"],
|
query: params[:query],
|
||||||
page: page,
|
page: page,
|
||||||
page_size: page_size,
|
page_size: page_size,
|
||||||
tags: params["tags"],
|
tags: params[:tags],
|
||||||
name: params["name"],
|
name: params[:name],
|
||||||
email: params["email"],
|
email: params[:email],
|
||||||
actor_types: params["actor_types"]
|
actor_types: params[:actor_types]
|
||||||
}
|
}
|
||||||
|> Map.merge(filters)
|
|> Map.merge(filters)
|
||||||
|
|
||||||
with {:ok, users, count} <- Search.user(search_params) do
|
with {:ok, users, count} <- Search.user(search_params) do
|
||||||
json(
|
render(conn, "index.json", users: users, count: count, page_size: page_size)
|
||||||
conn,
|
|
||||||
AccountView.render("index.json",
|
|
||||||
users: users,
|
|
||||||
count: count,
|
|
||||||
page_size: page_size
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -274,8 +276,8 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
||||||
|
|
||||||
defp page_params(params) do
|
defp page_params(params) do
|
||||||
{
|
{
|
||||||
fetch_integer_param(params, "page", 1),
|
fetch_integer_param(params, :page, 1),
|
||||||
fetch_integer_param(params, "page_size", @users_page_size)
|
fetch_integer_param(params, :page_size, @users_page_size)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -75,7 +75,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
|
||||||
"display_name" => display_name,
|
"display_name" => display_name,
|
||||||
"is_active" => user.is_active,
|
"is_active" => user.is_active,
|
||||||
"local" => user.local,
|
"local" => user.local,
|
||||||
"roles" => User.roles(user),
|
"roles" => roles(user),
|
||||||
"tags" => user.tags || [],
|
"tags" => user.tags || [],
|
||||||
"is_confirmed" => user.is_confirmed,
|
"is_confirmed" => user.is_confirmed,
|
||||||
"is_approved" => user.is_approved,
|
"is_approved" => user.is_approved,
|
||||||
|
@ -85,6 +85,10 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
|
||||||
}
|
}
|
||||||
end
|
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
|
def render("created.json", %{user: user}) do
|
||||||
%{
|
%{
|
||||||
type: "success",
|
type: "success",
|
||||||
|
@ -96,7 +100,11 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("create-error.json", %{changeset: %Ecto.Changeset{changes: changes, errors: errors}}) do
|
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",
|
type: "error",
|
||||||
code: 409,
|
code: 409,
|
||||||
|
@ -140,4 +148,11 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
|
||||||
|
|
||||||
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
|
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
|
||||||
defp image_url(_), do: nil
|
defp image_url(_), do: nil
|
||||||
|
|
||||||
|
defp roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
|
||||||
|
%{
|
||||||
|
admin: is_admin,
|
||||||
|
moderator: is_moderator
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -92,9 +92,10 @@ defmodule Pleroma.Web.ApiSpec do
|
||||||
"Invites",
|
"Invites",
|
||||||
"MediaProxy cache",
|
"MediaProxy cache",
|
||||||
"OAuth application managment",
|
"OAuth application managment",
|
||||||
"Report managment",
|
|
||||||
"Relays",
|
"Relays",
|
||||||
"Status administration"
|
"Report managment",
|
||||||
|
"Status administration",
|
||||||
|
"User administration"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},
|
%{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},
|
||||||
|
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
|
||||||
|
|
||||||
@behaviour Plug
|
@behaviour Plug
|
||||||
|
|
||||||
|
alias OpenApiSpex.Plug.PutApiSpec
|
||||||
alias Plug.Conn
|
alias Plug.Conn
|
||||||
|
|
||||||
@impl Plug
|
@impl Plug
|
||||||
|
@ -25,12 +26,10 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl Plug
|
@impl Plug
|
||||||
def call(%{private: %{open_api_spex: private_data}} = conn, %{
|
|
||||||
operation_id: operation_id,
|
def call(conn, %{operation_id: operation_id, render_error: render_error}) do
|
||||||
render_error: render_error
|
{spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
|
||||||
}) do
|
operation = operation_lookup[operation_id]
|
||||||
spec = private_data.spec
|
|
||||||
operation = private_data.operation_lookup[operation_id]
|
|
||||||
|
|
||||||
content_type =
|
content_type =
|
||||||
case Conn.get_req_header(conn, "content-type") do
|
case Conn.get_req_header(conn, "content-type") do
|
||||||
|
@ -43,8 +42,7 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
|
||||||
"application/json"
|
"application/json"
|
||||||
end
|
end
|
||||||
|
|
||||||
private_data = Map.put(private_data, :operation_id, operation_id)
|
conn = Conn.put_private(conn, :operation_id, operation_id)
|
||||||
conn = Conn.put_private(conn, :open_api_spex, private_data)
|
|
||||||
|
|
||||||
case cast_and_validate(spec, operation, conn, content_type, strict?()) do
|
case cast_and_validate(spec, operation, conn, content_type, strict?()) do
|
||||||
{:ok, conn} ->
|
{:ok, conn} ->
|
||||||
|
@ -64,25 +62,22 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
|
||||||
private: %{
|
private: %{
|
||||||
phoenix_controller: controller,
|
phoenix_controller: controller,
|
||||||
phoenix_action: action,
|
phoenix_action: action,
|
||||||
open_api_spex: private_data
|
open_api_spex: %{spec_module: spec_module}
|
||||||
}
|
}
|
||||||
} = conn,
|
} = conn,
|
||||||
opts
|
opts
|
||||||
) do
|
) do
|
||||||
|
{spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
|
||||||
|
|
||||||
operation =
|
operation =
|
||||||
case private_data.operation_lookup[{controller, action}] do
|
case operation_lookup[{controller, action}] do
|
||||||
nil ->
|
nil ->
|
||||||
operation_id = controller.open_api_operation(action).operationId
|
operation_id = controller.open_api_operation(action).operationId
|
||||||
operation = private_data.operation_lookup[operation_id]
|
operation = operation_lookup[operation_id]
|
||||||
|
|
||||||
operation_lookup =
|
operation_lookup = Map.put(operation_lookup, {controller, action}, operation)
|
||||||
private_data.operation_lookup
|
|
||||||
|> Map.put({controller, action}, operation)
|
|
||||||
|
|
||||||
OpenApiSpex.Plug.Cache.adapter().put(
|
OpenApiSpex.Plug.Cache.adapter().put(spec_module, {spec, operation_lookup})
|
||||||
private_data.spec_module,
|
|
||||||
{private_data.spec, operation_lookup}
|
|
||||||
)
|
|
||||||
|
|
||||||
operation
|
operation
|
||||||
|
|
||||||
|
|
389
lib/pleroma/web/api_spec/operations/admin/user_operation.ex
Normal file
389
lib/pleroma/web/api_spec/operations/admin/user_operation.ex
Normal file
|
@ -0,0 +1,389 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.Admin.UserOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ActorType
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "List users",
|
||||||
|
operationId: "AdminAPI.UserController.index",
|
||||||
|
security: [%{"oAuth" => ["admin:read:accounts"]}],
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:filters, :query, :string, "Comma separated list of filters"),
|
||||||
|
Operation.parameter(:query, :query, :string, "Search users query"),
|
||||||
|
Operation.parameter(:name, :query, :string, "Search by display name"),
|
||||||
|
Operation.parameter(:email, :query, :string, "Search by email"),
|
||||||
|
Operation.parameter(:page, :query, :integer, "Page Number"),
|
||||||
|
Operation.parameter(:page_size, :query, :integer, "Number of users to return per page"),
|
||||||
|
Operation.parameter(
|
||||||
|
:actor_types,
|
||||||
|
:query,
|
||||||
|
%Schema{type: :array, items: ActorType},
|
||||||
|
"Filter by actor type"
|
||||||
|
),
|
||||||
|
Operation.parameter(
|
||||||
|
:tags,
|
||||||
|
:query,
|
||||||
|
%Schema{type: :array, items: %Schema{type: :string}},
|
||||||
|
"Filter by tags"
|
||||||
|
)
|
||||||
|
| admin_api_params()
|
||||||
|
],
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response(
|
||||||
|
"Response",
|
||||||
|
"application/json",
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
users: %Schema{type: :array, items: user()},
|
||||||
|
count: %Schema{type: :integer},
|
||||||
|
page_size: %Schema{type: :integer}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Create a single or multiple users",
|
||||||
|
operationId: "AdminAPI.UserController.create",
|
||||||
|
security: [%{"oAuth" => ["admin:write:accounts"]}],
|
||||||
|
parameters: admin_api_params(),
|
||||||
|
requestBody:
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
description: "POST body for creating users",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
users: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
nickname: %Schema{type: :string},
|
||||||
|
email: %Schema{type: :string},
|
||||||
|
password: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Response", "application/json", %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
code: %Schema{type: :integer},
|
||||||
|
type: %Schema{type: :string},
|
||||||
|
data: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
email: %Schema{type: :string, format: :email},
|
||||||
|
nickname: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||||
|
409 =>
|
||||||
|
Operation.response("Conflict", "application/json", %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
code: %Schema{type: :integer},
|
||||||
|
error: %Schema{type: :string},
|
||||||
|
type: %Schema{type: :string},
|
||||||
|
data: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
email: %Schema{type: :string, format: :email},
|
||||||
|
nickname: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Show user",
|
||||||
|
operationId: "AdminAPI.UserController.show",
|
||||||
|
security: [%{"oAuth" => ["admin:read:accounts"]}],
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(
|
||||||
|
:nickname,
|
||||||
|
:path,
|
||||||
|
:string,
|
||||||
|
"User nickname or ID"
|
||||||
|
)
|
||||||
|
| admin_api_params()
|
||||||
|
],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Response", "application/json", user()),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||||
|
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Follow",
|
||||||
|
operationId: "AdminAPI.UserController.follow",
|
||||||
|
security: [%{"oAuth" => ["admin:write:follows"]}],
|
||||||
|
parameters: admin_api_params(),
|
||||||
|
requestBody:
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
follower: %Schema{type: :string, description: "Follower nickname"},
|
||||||
|
followed: %Schema{type: :string, description: "Followed nickname"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Response", "application/json", %Schema{type: :string}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def unfollow_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Unfollow",
|
||||||
|
operationId: "AdminAPI.UserController.unfollow",
|
||||||
|
security: [%{"oAuth" => ["admin:write:follows"]}],
|
||||||
|
parameters: admin_api_params(),
|
||||||
|
requestBody:
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
follower: %Schema{type: :string, description: "Follower nickname"},
|
||||||
|
followed: %Schema{type: :string, description: "Followed nickname"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Response", "application/json", %Schema{type: :string}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def approve_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Approve multiple users",
|
||||||
|
operationId: "AdminAPI.UserController.approve",
|
||||||
|
security: [%{"oAuth" => ["admin:write:accounts"]}],
|
||||||
|
parameters: admin_api_params(),
|
||||||
|
requestBody:
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
description: "POST body for deleting multiple users",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
nicknames: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Response", "application/json", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{user: %Schema{type: :array, items: user()}}
|
||||||
|
}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def toggle_activation_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Toggle user activation",
|
||||||
|
operationId: "AdminAPI.UserController.toggle_activation",
|
||||||
|
security: [%{"oAuth" => ["admin:write:accounts"]}],
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(:nickname, :path, :string, "User nickname")
|
||||||
|
| admin_api_params()
|
||||||
|
],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Response", "application/json", user()),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def activate_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Activate multiple users",
|
||||||
|
operationId: "AdminAPI.UserController.activate",
|
||||||
|
security: [%{"oAuth" => ["admin:write:accounts"]}],
|
||||||
|
parameters: admin_api_params(),
|
||||||
|
requestBody:
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
description: "POST body for deleting multiple users",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
nicknames: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Response", "application/json", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{user: %Schema{type: :array, items: user()}}
|
||||||
|
}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def deactivate_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Deactivates multiple users",
|
||||||
|
operationId: "AdminAPI.UserController.deactivate",
|
||||||
|
security: [%{"oAuth" => ["admin:write:accounts"]}],
|
||||||
|
parameters: admin_api_params(),
|
||||||
|
requestBody:
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
description: "POST body for deleting multiple users",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
nicknames: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Response", "application/json", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{user: %Schema{type: :array, items: user()}}
|
||||||
|
}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["User administration"],
|
||||||
|
summary: "Removes a single or multiple users",
|
||||||
|
operationId: "AdminAPI.UserController.delete",
|
||||||
|
security: [%{"oAuth" => ["admin:write:accounts"]}],
|
||||||
|
parameters: [
|
||||||
|
Operation.parameter(
|
||||||
|
:nickname,
|
||||||
|
:query,
|
||||||
|
:string,
|
||||||
|
"User nickname"
|
||||||
|
)
|
||||||
|
| admin_api_params()
|
||||||
|
],
|
||||||
|
requestBody:
|
||||||
|
request_body(
|
||||||
|
"Parameters",
|
||||||
|
%Schema{
|
||||||
|
description: "POST body for deleting multiple users",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
nicknames: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Response", "application/json", %Schema{
|
||||||
|
description: "Array of nicknames",
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string}
|
||||||
|
}),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp user do
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
id: %Schema{type: :string},
|
||||||
|
email: %Schema{type: :string, format: :email},
|
||||||
|
avatar: %Schema{type: :string, format: :uri},
|
||||||
|
nickname: %Schema{type: :string},
|
||||||
|
display_name: %Schema{type: :string},
|
||||||
|
is_active: %Schema{type: :boolean},
|
||||||
|
local: %Schema{type: :boolean},
|
||||||
|
roles: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
admin: %Schema{type: :boolean},
|
||||||
|
moderator: %Schema{type: :boolean}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tags: %Schema{type: :array, items: %Schema{type: :string}},
|
||||||
|
is_confirmed: %Schema{type: :boolean},
|
||||||
|
is_approved: %Schema{type: :boolean},
|
||||||
|
url: %Schema{type: :string, format: :uri},
|
||||||
|
registration_reason: %Schema{type: :string, nullable: true},
|
||||||
|
actor_type: %Schema{type: :string}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -59,7 +59,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
||||||
Operation.response(
|
Operation.response(
|
||||||
"Status. When `scheduled_at` is present, ScheduledStatus is returned instead",
|
"Status. When `scheduled_at` is present, ScheduledStatus is returned instead",
|
||||||
"application/json",
|
"application/json",
|
||||||
%Schema{oneOf: [Status, ScheduledStatus]}
|
%Schema{anyOf: [Status, ScheduledStatus]}
|
||||||
),
|
),
|
||||||
422 => Operation.response("Bad Request / MRF Rejection", "application/json", ApiError)
|
422 => Operation.response("Bad Request / MRF Rejection", "application/json", ApiError)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
|
defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
|
||||||
|
alias OpenApiSpex.Cast
|
||||||
alias OpenApiSpex.Schema
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
require OpenApiSpex
|
require OpenApiSpex
|
||||||
|
@ -27,10 +28,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
|
||||||
%Schema{type: :boolean},
|
%Schema{type: :boolean},
|
||||||
%Schema{type: :string},
|
%Schema{type: :string},
|
||||||
%Schema{type: :integer}
|
%Schema{type: :integer}
|
||||||
]
|
],
|
||||||
|
"x-validate": __MODULE__
|
||||||
})
|
})
|
||||||
|
|
||||||
def after_cast(value, _schmea) do
|
def cast(%Cast{value: value} = context) do
|
||||||
{:ok, Pleroma.Web.ControllerHelper.truthy_param?(value)}
|
context
|
||||||
|
|> Map.put(:value, Pleroma.Web.ControllerHelper.truthy_param?(value))
|
||||||
|
|> Cast.ok()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
|
@ -179,13 +180,39 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp sensitive(draft) do
|
defp sensitive(draft) do
|
||||||
sensitive = draft.params[:sensitive] || Enum.member?(draft.tags, {"#nsfw", "nsfw"})
|
sensitive = draft.params[:sensitive]
|
||||||
%__MODULE__{draft | sensitive: sensitive}
|
%__MODULE__{draft | sensitive: sensitive}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp object(draft) do
|
defp object(draft) do
|
||||||
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
|
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
|
||||||
|
|
||||||
|
# Sometimes people create posts with subject containing emoji,
|
||||||
|
# since subjects are usually copied this will result in a broken
|
||||||
|
# subject when someone replies from an instance that does not have
|
||||||
|
# the emoji or has it under different shortcode. This is an attempt
|
||||||
|
# to mitigate this by copying emoji from inReplyTo if they are present
|
||||||
|
# in the subject.
|
||||||
|
summary_emoji =
|
||||||
|
with %Activity{} <- draft.in_reply_to,
|
||||||
|
%Object{data: %{"tag" => [_ | _] = tag}} <- Object.normalize(draft.in_reply_to) do
|
||||||
|
Enum.reduce(tag, %{}, fn
|
||||||
|
%{"type" => "Emoji", "name" => name, "icon" => %{"url" => url}}, acc ->
|
||||||
|
if String.contains?(draft.summary, name) do
|
||||||
|
Map.put(acc, name, url)
|
||||||
|
else
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
|
||||||
|
_, acc ->
|
||||||
|
acc
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
_ -> %{}
|
||||||
|
end
|
||||||
|
|
||||||
|
emoji = Map.merge(emoji, summary_emoji)
|
||||||
|
|
||||||
object =
|
object =
|
||||||
Utils.make_note_data(draft)
|
Utils.make_note_data(draft)
|
||||||
|> Map.put("emoji", emoji)
|
|> Map.put("emoji", emoji)
|
||||||
|
|
|
@ -217,7 +217,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
draft.status
|
draft.status
|
||||||
|> format_input(content_type, options)
|
|> format_input(content_type, options)
|
||||||
|> maybe_add_attachments(draft.attachments, attachment_links)
|
|> maybe_add_attachments(draft.attachments, attachment_links)
|
||||||
|> maybe_add_nsfw_tag(draft.params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_content_type(content_type) do
|
defp get_content_type(content_type) do
|
||||||
|
@ -228,13 +227,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_add_nsfw_tag({text, mentions, tags}, %{"sensitive" => sensitive})
|
|
||||||
when sensitive in [true, "True", "true", "1"] do
|
|
||||||
{text, mentions, [{"#nsfw", "nsfw"} | tags]}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_add_nsfw_tag(data, _), do: data
|
|
||||||
|
|
||||||
def make_context(_, %Participation{} = participation) do
|
def make_context(_, %Participation{} = participation) do
|
||||||
Repo.preload(participation, :conversation).conversation.ap_id
|
Repo.preload(participation, :conversation).conversation.ap_id
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,6 +32,7 @@ defmodule Pleroma.Web.Feed.FeedView do
|
||||||
|
|
||||||
%{
|
%{
|
||||||
activity: activity,
|
activity: activity,
|
||||||
|
object: object,
|
||||||
data: Map.get(object, :data),
|
data: Map.get(object, :data),
|
||||||
actor: actor
|
actor: actor
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.InstanceController do
|
defmodule Pleroma.Web.MastodonAPI.InstanceController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
:skip_plug,
|
:skip_plug,
|
||||||
|
|
|
@ -133,34 +133,25 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp hashtag_fetching(params, user, local_only) do
|
defp hashtag_fetching(params, user, local_only) do
|
||||||
tags =
|
# Note: not sanitizing tag options at this stage (may be mix-cased, have duplicates etc.)
|
||||||
|
tags_any =
|
||||||
[params[:tag], params[:any]]
|
[params[:tag], params[:any]]
|
||||||
|> List.flatten()
|
|> List.flatten()
|
||||||
|> Enum.uniq()
|
|> Enum.filter(& &1)
|
||||||
|> Enum.reject(&is_nil/1)
|
|
||||||
|> Enum.map(&String.downcase/1)
|
|
||||||
|
|
||||||
tag_all =
|
tag_all = Map.get(params, :all, [])
|
||||||
params
|
tag_reject = Map.get(params, :none, [])
|
||||||
|> Map.get(:all, [])
|
|
||||||
|> Enum.map(&String.downcase/1)
|
|
||||||
|
|
||||||
tag_reject =
|
params
|
||||||
params
|
|> Map.put(:type, "Create")
|
||||||
|> Map.get(:none, [])
|
|> Map.put(:local_only, local_only)
|
||||||
|> Enum.map(&String.downcase/1)
|
|> Map.put(:blocking_user, user)
|
||||||
|
|> Map.put(:muting_user, user)
|
||||||
_activities =
|
|> Map.put(:user, user)
|
||||||
params
|
|> Map.put(:tag, tags_any)
|
||||||
|> Map.put(:type, "Create")
|
|> Map.put(:tag_all, tag_all)
|
||||||
|> Map.put(:local_only, local_only)
|
|> Map.put(:tag_reject, tag_reject)
|
||||||
|> Map.put(:blocking_user, user)
|
|> ActivityPub.fetch_public_activities()
|
||||||
|> Map.put(:muting_user, user)
|
|
||||||
|> Map.put(:user, user)
|
|
||||||
|> Map.put(:tag, tags)
|
|
||||||
|> Map.put(:tag_all, tag_all)
|
|
||||||
|> Map.put(:tag_reject, tag_reject)
|
|
||||||
|> ActivityPub.fetch_public_activities()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /api/v1/timelines/tag/:tag
|
# GET /api/v1/timelines/tag/:tag
|
||||||
|
|
|
@ -198,8 +198,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
like_count = object.data["like_count"] || 0
|
like_count = object.data["like_count"] || 0
|
||||||
announcement_count = object.data["announcement_count"] || 0
|
announcement_count = object.data["announcement_count"] || 0
|
||||||
|
|
||||||
tags = object.data["tag"] || []
|
hashtags = Object.hashtags(object)
|
||||||
sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
|
sensitive = object.data["sensitive"] || Enum.member?(hashtags, "nsfw")
|
||||||
|
|
||||||
|
tags = Object.tags(object)
|
||||||
|
|
||||||
tag_mentions =
|
tag_mentions =
|
||||||
tags
|
tags
|
||||||
|
@ -379,12 +381,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
|
|
||||||
page_url = page_url_data |> to_string
|
page_url = page_url_data |> to_string
|
||||||
|
|
||||||
image_url =
|
image_url_data =
|
||||||
if is_binary(rich_media["image"]) do
|
if is_binary(rich_media["image"]) do
|
||||||
URI.merge(page_url_data, URI.parse(rich_media["image"]))
|
URI.parse(rich_media["image"])
|
||||||
|> to_string
|
else
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
image_url = build_image_url(image_url_data, page_url_data)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
type: "link",
|
type: "link",
|
||||||
provider_name: page_url_data.host,
|
provider_name: page_url_data.host,
|
||||||
|
@ -540,4 +545,23 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
do: %{name: name, website: url}
|
do: %{name: name, website: url}
|
||||||
|
|
||||||
defp build_application(_), do: nil
|
defp build_application(_), do: nil
|
||||||
|
|
||||||
|
# Workaround for Elixir issue #10771
|
||||||
|
# Avoid applying URI.merge unless necessary
|
||||||
|
# TODO: revert to always attempting URI.merge(image_url_data, page_url_data)
|
||||||
|
# when Elixir 1.12 is the minimum supported version
|
||||||
|
@spec build_image_url(struct() | nil, struct()) :: String.t() | nil
|
||||||
|
defp build_image_url(
|
||||||
|
%URI{scheme: image_scheme, host: image_host} = image_url_data,
|
||||||
|
%URI{} = _page_url_data
|
||||||
|
)
|
||||||
|
when not is_nil(image_scheme) and not is_nil(image_host) do
|
||||||
|
image_url_data |> to_string
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
|
||||||
|
URI.merge(page_url_data, image_url_data) |> to_string
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_image_url(_, _), do: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -121,6 +121,11 @@ defmodule Pleroma.Web.MediaProxy do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def decode_url(encoded) do
|
||||||
|
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
|
||||||
|
decode_url(sig, base64)
|
||||||
|
end
|
||||||
|
|
||||||
defp signed_url(url) do
|
defp signed_url(url) do
|
||||||
:crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
|
:crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupController do
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action in [:index, :create])
|
plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action in [:index, :create])
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
|
||||||
%{scopes: ["read:chats"]} when action in [:messages, :index, :index2, :show]
|
%{scopes: ["read:chats"]} when action in [:messages, :index, :index2, :show]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
|
||||||
plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks)
|
plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks)
|
||||||
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes)
|
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes)
|
||||||
|
|
||||||
plug(OpenApiSpex.Plug.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation
|
defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation
|
||||||
|
|
||||||
def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do
|
def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do
|
||||||
|
|
|
@ -204,7 +204,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
|
get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
|
||||||
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
|
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
|
||||||
|
|
||||||
get("/users", UserController, :list)
|
get("/users", UserController, :index)
|
||||||
get("/users/:nickname", UserController, :show)
|
get("/users/:nickname", UserController, :show)
|
||||||
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
|
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
|
||||||
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
|
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
|
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= for tag <- @data["tag"] || [] do %>
|
<%= for tag <- Pleroma.Object.hashtags(@object) do %>
|
||||||
<category term="<%= tag %>"></category>
|
<category term="<%= tag %>"></category>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
<link rel="ostatus:conversation"><%= activity_context(@activity) %></link>
|
<link rel="ostatus:conversation"><%= activity_context(@activity) %></link>
|
||||||
|
|
||||||
<%= for tag <- @data["tag"] || [] do %>
|
<%= for tag <- Pleroma.Object.hashtags(@object) do %>
|
||||||
<category term="<%= tag %>"></category>
|
<category term="<%= tag %>"></category>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= for tag <- @data["tag"] || [] do %>
|
<%= for tag <- Pleroma.Object.hashtags(@object) do %>
|
||||||
<category term="<%= tag %>"></category>
|
<category term="<%= tag %>"></category>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
|
@ -94,52 +94,56 @@ defmodule Pleroma.Web.WebFinger do
|
||||||
|> XmlBuilder.to_doc()
|
|> XmlBuilder.to_doc()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp webfinger_from_xml(doc) do
|
defp webfinger_from_xml(body) do
|
||||||
subject = XML.string_from_xpath("//Subject", doc)
|
with {:ok, doc} <- XML.parse_document(body) do
|
||||||
|
subject = XML.string_from_xpath("//Subject", doc)
|
||||||
|
|
||||||
subscribe_address =
|
subscribe_address =
|
||||||
~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}
|
~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}
|
||||||
|> XML.string_from_xpath(doc)
|
|> XML.string_from_xpath(doc)
|
||||||
|
|
||||||
ap_id =
|
ap_id =
|
||||||
~s{//Link[@rel="self" and @type="application/activity+json"]/@href}
|
~s{//Link[@rel="self" and @type="application/activity+json"]/@href}
|
||||||
|> XML.string_from_xpath(doc)
|
|> XML.string_from_xpath(doc)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"subject" => subject,
|
"subject" => subject,
|
||||||
"subscribe_address" => subscribe_address,
|
"subscribe_address" => subscribe_address,
|
||||||
"ap_id" => ap_id
|
"ap_id" => ap_id
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp webfinger_from_json(doc) do
|
defp webfinger_from_json(body) do
|
||||||
data =
|
with {:ok, doc} <- Jason.decode(body) do
|
||||||
Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
|
data =
|
||||||
case {link["type"], link["rel"]} do
|
Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
|
||||||
{"application/activity+json", "self"} ->
|
case {link["type"], link["rel"]} do
|
||||||
Map.put(data, "ap_id", link["href"])
|
{"application/activity+json", "self"} ->
|
||||||
|
Map.put(data, "ap_id", link["href"])
|
||||||
|
|
||||||
{"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
|
{"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} ->
|
||||||
Map.put(data, "ap_id", link["href"])
|
Map.put(data, "ap_id", link["href"])
|
||||||
|
|
||||||
{nil, "http://ostatus.org/schema/1.0/subscribe"} ->
|
{nil, "http://ostatus.org/schema/1.0/subscribe"} ->
|
||||||
Map.put(data, "subscribe_address", link["template"])
|
Map.put(data, "subscribe_address", link["template"])
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Logger.debug("Unhandled type: #{inspect(link["type"])}")
|
Logger.debug("Unhandled type: #{inspect(link["type"])}")
|
||||||
data
|
data
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_template_from_xml(body) do
|
def get_template_from_xml(body) do
|
||||||
xpath = "//Link[@rel='lrdd']/@template"
|
xpath = "//Link[@rel='lrdd']/@template"
|
||||||
|
|
||||||
with doc when doc != :error <- XML.parse_document(body),
|
with {:ok, doc} <- XML.parse_document(body),
|
||||||
template when template != nil <- XML.string_from_xpath(xpath, doc) do
|
template when template != nil <- XML.string_from_xpath(xpath, doc) do
|
||||||
{:ok, template}
|
{:ok, template}
|
||||||
end
|
end
|
||||||
|
@ -192,15 +196,23 @@ defmodule Pleroma.Web.WebFinger do
|
||||||
address,
|
address,
|
||||||
[{"accept", "application/xrd+xml,application/jrd+json"}]
|
[{"accept", "application/xrd+xml,application/jrd+json"}]
|
||||||
),
|
),
|
||||||
{:ok, %{status: status, body: body}} when status in 200..299 <- response do
|
{:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <-
|
||||||
doc = XML.parse_document(body)
|
response do
|
||||||
|
case List.keyfind(headers, "content-type", 0) do
|
||||||
|
{_, content_type} ->
|
||||||
|
case Plug.Conn.Utils.media_type(content_type) do
|
||||||
|
{:ok, "application", subtype, _} when subtype in ~w(xrd+xml xml) ->
|
||||||
|
webfinger_from_xml(body)
|
||||||
|
|
||||||
if doc != :error do
|
{:ok, "application", subtype, _} when subtype in ~w(jrd+json json) ->
|
||||||
webfinger_from_xml(doc)
|
webfinger_from_json(body)
|
||||||
else
|
|
||||||
with {:ok, doc} <- Jason.decode(body) do
|
_ ->
|
||||||
webfinger_from_json(doc)
|
{:error, {:content_type, content_type}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, {:content_type, nil}}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
|
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Web.XML do
|
||||||
|> :binary.bin_to_list()
|
|> :binary.bin_to_list()
|
||||||
|> :xmerl_scan.string(quiet: true)
|
|> :xmerl_scan.string(quiet: true)
|
||||||
|
|
||||||
doc
|
{:ok, doc}
|
||||||
rescue
|
rescue
|
||||||
_e ->
|
_e ->
|
||||||
Logger.debug("Couldn't parse XML: #{inspect(text)}")
|
Logger.debug("Couldn't parse XML: #{inspect(text)}")
|
||||||
|
|
9
mix.exs
9
mix.exs
|
@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
|
||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :pleroma,
|
app: :pleroma,
|
||||||
version: version("2.2.50"),
|
version: version("2.3.50"),
|
||||||
elixir: "~> 1.9",
|
elixir: "~> 1.9",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||||
|
@ -121,6 +121,7 @@ defmodule Pleroma.Mixfile do
|
||||||
{:phoenix_pubsub, "~> 2.0"},
|
{:phoenix_pubsub, "~> 2.0"},
|
||||||
{:phoenix_ecto, "~> 4.0"},
|
{:phoenix_ecto, "~> 4.0"},
|
||||||
{:ecto_enum, "~> 1.4"},
|
{:ecto_enum, "~> 1.4"},
|
||||||
|
{:ecto_explain, "~> 0.1.2"},
|
||||||
{:ecto_sql, "~> 3.4.4"},
|
{:ecto_sql, "~> 3.4.4"},
|
||||||
{:postgrex, ">= 0.15.5"},
|
{:postgrex, ">= 0.15.5"},
|
||||||
{:oban, "~> 2.3.4"},
|
{:oban, "~> 2.3.4"},
|
||||||
|
@ -157,7 +158,7 @@ defmodule Pleroma.Mixfile do
|
||||||
{:floki, "~> 0.27"},
|
{:floki, "~> 0.27"},
|
||||||
{:timex, "~> 3.6"},
|
{:timex, "~> 3.6"},
|
||||||
{:ueberauth, "~> 0.4"},
|
{:ueberauth, "~> 0.4"},
|
||||||
{:linkify, "~> 0.4.1"},
|
{:linkify, "~> 0.5.0"},
|
||||||
{:http_signatures, "~> 0.1.0"},
|
{:http_signatures, "~> 0.1.0"},
|
||||||
{:telemetry, "~> 0.3"},
|
{:telemetry, "~> 0.3"},
|
||||||
{:poolboy, "~> 1.5"},
|
{:poolboy, "~> 1.5"},
|
||||||
|
@ -195,9 +196,7 @@ defmodule Pleroma.Mixfile do
|
||||||
{:majic,
|
{:majic,
|
||||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
|
git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
|
||||||
ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
|
ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
|
||||||
{:open_api_spex,
|
{:open_api_spex, "~> 3.10"},
|
||||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
|
|
||||||
ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"},
|
|
||||||
|
|
||||||
## dev & test
|
## dev & test
|
||||||
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
|
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
|
||||||
|
|
11
mix.lock
11
mix.lock
|
@ -31,6 +31,7 @@
|
||||||
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
|
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
|
||||||
"ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
|
"ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
|
||||||
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
||||||
|
"ecto_explain": {:hex, :ecto_explain, "0.1.2", "a9d504cbd4adc809911f796d5ef7ebb17a576a6d32286c3d464c015bd39d5541", [:mix], [], "hexpm", "1d0e7798ae30ecf4ce34e912e5354a0c1c832b7ebceba39298270b9a9f316330"},
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
|
"ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
|
||||||
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
|
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
|
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
|
||||||
|
@ -51,7 +52,7 @@
|
||||||
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
|
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
|
||||||
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
|
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
|
||||||
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
|
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
|
||||||
"gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
|
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
|
||||||
"gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
|
"gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]},
|
||||||
"hackney": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", "7d7119f0651515d6d7669c78393fd90950a3ec6e", [ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e"]},
|
"hackney": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", "7d7119f0651515d6d7669c78393fd90950a3ec6e", [ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e"]},
|
||||||
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
||||||
|
@ -65,7 +66,7 @@
|
||||||
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
|
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
|
||||||
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
|
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
|
||||||
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
|
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
|
||||||
"linkify": {:hex, :linkify, "0.4.1", "f881eb3429ae88010cf736e6fb3eed406c187bcdd544902ec937496636b7c7b3", [:mix], [], "hexpm", "ce98693f54ae9ace59f2f7a8aed3de2ef311381a8ce7794804bd75484c371dda"},
|
"linkify": {:hex, :linkify, "0.5.0", "e0ea8de73ff44742d6a889721221f4c4eccaad5284957ee9832ffeb347602d54", [:mix], [], "hexpm", "4ccd958350aee7c51c89e21f05b15d30596ebbba707e051d21766be1809df2d7"},
|
||||||
"majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "289cda1b6d0d70ccb2ba508a2b0bd24638db2880", [ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"]},
|
"majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "289cda1b6d0d70ccb2ba508a2b0bd24638db2880", [ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"]},
|
||||||
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
|
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
|
||||||
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
|
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
|
||||||
|
@ -82,7 +83,7 @@
|
||||||
"nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
|
"nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
|
||||||
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
|
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
|
||||||
"oban": {:hex, :oban, "2.3.4", "ec7509b9af2524d55f529cb7aee93d36131ae0bf0f37706f65d2fe707f4d9fd8", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c70ca0434758fd1805422ea4446af5e910ddc697c0c861549c8f0eb0cfbd2fdf"},
|
"oban": {:hex, :oban, "2.3.4", "ec7509b9af2524d55f529cb7aee93d36131ae0bf0f37706f65d2fe707f4d9fd8", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c70ca0434758fd1805422ea4446af5e910ddc697c0c861549c8f0eb0cfbd2fdf"},
|
||||||
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
|
"open_api_spex": {:hex, :open_api_spex, "3.10.0", "94e9521ad525b3fcf6dc77da7c45f87fdac24756d4de588cb0816b413e7c1844", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "2dbb2bde3d2b821f06936e8dfaf3284331186556291946d84eeba3750ac28765"},
|
||||||
"p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
|
"p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"},
|
||||||
"parse_trans": {:git, "https://github.com/uwiger/parse_trans.git", "76abb347c3c1d00fb0ccf9e4b43e22b3d2288484", [tag: "3.3.0"]},
|
"parse_trans": {:git, "https://github.com/uwiger/parse_trans.git", "76abb347c3c1d00fb0ccf9e4b43e22b3d2288484", [tag: "3.3.0"]},
|
||||||
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
|
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
|
||||||
|
@ -116,9 +117,9 @@
|
||||||
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
||||||
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
||||||
"tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"},
|
"tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"},
|
||||||
"timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
|
"timex": {:hex, :timex, "3.7.3", "df8a2ea814749d700d6878ab9eacac9fdb498ecee2f507cb0002ec172bc24d0f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8691c1d86ca3a7bc14a156e2199dc8927be95d1a8f0e3b69e4bb2d6262c53ac6"},
|
||||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||||
"tzdata": {:hex, :tzdata, "1.0.4", "a3baa4709ea8dba552dca165af6ae97c624a2d6ac14bd265165eaa8e8af94af6", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "b02637db3df1fd66dd2d3c4f194a81633d0e4b44308d36c1b2fdfd1e4e6f169b"},
|
"tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"},
|
||||||
"ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
|
"ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
|
||||||
"unicode_util_compat": {:git, "https://github.com/benoitc/unicode_util_compat.git", "38d7bc105f51159e8ea3279c40121db9db1e652f", [tag: "0.3.1"]},
|
"unicode_util_compat": {:git, "https://github.com/benoitc/unicode_util_compat.git", "38d7bc105f51159e8ea3279c40121db9db1e652f", [tag: "0.3.1"]},
|
||||||
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
|
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
|
||||||
|
|
|
@ -3,8 +3,8 @@ msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-06-19 14:33+0000\n"
|
"POT-Creation-Date: 2020-06-19 14:33+0000\n"
|
||||||
"PO-Revision-Date: 2020-07-09 14:40+0000\n"
|
"PO-Revision-Date: 2021-03-13 09:40+0000\n"
|
||||||
"Last-Translator: Ben Is <srsbzns@cock.li>\n"
|
"Last-Translator: Ben Is <spambenis@fastwebnet.it>\n"
|
||||||
"Language-Team: Italian <https://translate.pleroma.social/projects/pleroma/"
|
"Language-Team: Italian <https://translate.pleroma.social/projects/pleroma/"
|
||||||
"pleroma/it/>\n"
|
"pleroma/it/>\n"
|
||||||
"Language: it\n"
|
"Language: it\n"
|
||||||
|
@ -45,7 +45,7 @@ msgstr "ha una voce invalida"
|
||||||
|
|
||||||
## From Ecto.Changeset.validate_exclusion/3
|
## From Ecto.Changeset.validate_exclusion/3
|
||||||
msgid "is reserved"
|
msgid "is reserved"
|
||||||
msgstr "è vietato"
|
msgstr "è riservato"
|
||||||
|
|
||||||
## From Ecto.Changeset.validate_confirmation/3
|
## From Ecto.Changeset.validate_confirmation/3
|
||||||
msgid "does not match confirmation"
|
msgid "does not match confirmation"
|
||||||
|
@ -123,7 +123,7 @@ msgstr "Richiesta invalida"
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Can't delete object"
|
msgid "Can't delete object"
|
||||||
msgstr "Non puoi eliminare quest'oggetto"
|
msgstr "Oggetto non eliminabile"
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
|
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
|
@ -160,12 +160,12 @@ msgstr "Non puoi pubblicare un messaggio vuoto senza allegati"
|
||||||
#: lib/pleroma/web/common_api/utils.ex:504
|
#: lib/pleroma/web/common_api/utils.ex:504
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Comment must be up to %{max_size} characters"
|
msgid "Comment must be up to %{max_size} characters"
|
||||||
msgstr "I commenti posso al massimo consistere di %{max_size} caratteri"
|
msgstr "I commenti posso al massimo contenere %{max_size} caratteri"
|
||||||
|
|
||||||
#: lib/pleroma/config/config_db.ex:222
|
#: lib/pleroma/config/config_db.ex:222
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Config with params %{params} not found"
|
msgid "Config with params %{params} not found"
|
||||||
msgstr "Configurazione con parametri %{max_size} non trovata"
|
msgstr "Configurazione con parametri %{params} non trovata"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:95
|
#: lib/pleroma/web/common_api/common_api.ex:95
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
|
@ -200,7 +200,7 @@ msgstr "Non de-intestato"
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:126
|
#: lib/pleroma/web/common_api/common_api.ex:126
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Could not unrepeat"
|
msgid "Could not unrepeat"
|
||||||
msgstr "Non de-ripetuto"
|
msgstr "Non de-condiviso"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:428
|
#: lib/pleroma/web/common_api/common_api.ex:428
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:437
|
#: lib/pleroma/web/common_api/common_api.ex:437
|
||||||
|
@ -310,12 +310,12 @@ msgstr "Il messaggio ha superato la lunghezza massima"
|
||||||
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
|
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "This resource requires authentication."
|
msgid "This resource requires authentication."
|
||||||
msgstr "Accedi per leggere."
|
msgstr "Accedi per poter leggere."
|
||||||
|
|
||||||
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
|
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Throttled"
|
msgid "Throttled"
|
||||||
msgstr "Strozzato"
|
msgstr "Limitato"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:266
|
#: lib/pleroma/web/common_api/common_api.ex:266
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
|
@ -347,17 +347,17 @@ msgstr "Devi aggiungere un indirizzo email valido"
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "can't read inbox of %{nickname} as %{as_nickname}"
|
msgid "can't read inbox of %{nickname} as %{as_nickname}"
|
||||||
msgstr "non puoi leggere i messaggi privati di %{nickname} come %{as_nickname}"
|
msgstr "non puoi leggere i messaggi di %{nickname} come %{as_nickname}"
|
||||||
|
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "can't update outbox of %{nickname} as %{as_nickname}"
|
msgid "can't update outbox of %{nickname} as %{as_nickname}"
|
||||||
msgstr "non puoi aggiornare gli inviati di %{nickname} come %{as_nickname}"
|
msgstr "non puoi inviare da %{nickname} come %{as_nickname}"
|
||||||
|
|
||||||
#: lib/pleroma/web/common_api/common_api.ex:388
|
#: lib/pleroma/web/common_api/common_api.ex:388
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "conversation is already muted"
|
msgid "conversation is already muted"
|
||||||
msgstr "la conversazione è già zittita"
|
msgstr "la conversazione è già silenziata"
|
||||||
|
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
|
||||||
|
@ -419,7 +419,7 @@ msgstr "Errore interno"
|
||||||
#: lib/pleroma/web/oauth/fallback_controller.ex:29
|
#: lib/pleroma/web/oauth/fallback_controller.ex:29
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Invalid Username/Password"
|
msgid "Invalid Username/Password"
|
||||||
msgstr "Nome utente/parola d'ordine invalidi"
|
msgstr "Nome utente/password invalidi"
|
||||||
|
|
||||||
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
|
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
|
@ -455,7 +455,7 @@ msgstr "Gestore OAuth non supportato: %{provider}."
|
||||||
#: lib/pleroma/uploaders/uploader.ex:72
|
#: lib/pleroma/uploaders/uploader.ex:72
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Uploader callback timeout"
|
msgid "Uploader callback timeout"
|
||||||
msgstr "Callback caricatmento scaduta"
|
msgstr "Callback caricamento scaduta"
|
||||||
|
|
||||||
#: lib/pleroma/web/uploader_controller.ex:23
|
#: lib/pleroma/web/uploader_controller.ex:23
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
|
@ -496,7 +496,7 @@ msgstr "Parametro mancante: %{name}"
|
||||||
#: lib/pleroma/web/oauth/oauth_controller.ex:322
|
#: lib/pleroma/web/oauth/oauth_controller.ex:322
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Password reset is required"
|
msgid "Password reset is required"
|
||||||
msgstr "Necessario reimpostare parola d'ordine"
|
msgstr "Necessario reimpostare password"
|
||||||
|
|
||||||
#: lib/pleroma/tests/auth_test_controller.ex:9
|
#: lib/pleroma/tests/auth_test_controller.ex:9
|
||||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
|
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
|
||||||
|
@ -540,34 +540,32 @@ msgstr ""
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while adding file to pack."
|
msgid "Unexpected error occurred while adding file to pack."
|
||||||
msgstr "Errore inaspettato durante l'aggiunta del file al pacchetto."
|
msgstr "Errore inatteso durante l'aggiunta del file al pacchetto."
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while creating pack."
|
msgid "Unexpected error occurred while creating pack."
|
||||||
msgstr "Errore inaspettato durante la creazione del pacchetto."
|
msgstr "Errore inatteso durante la creazione del pacchetto."
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while removing file from pack."
|
msgid "Unexpected error occurred while removing file from pack."
|
||||||
msgstr "Errore inaspettato durante la rimozione del file dal pacchetto."
|
msgstr "Errore inatteso durante la rimozione del file dal pacchetto."
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while updating file in pack."
|
msgid "Unexpected error occurred while updating file in pack."
|
||||||
msgstr "Errore inaspettato durante l'aggiornamento del file nel pacchetto."
|
msgstr "Errore inatteso durante l'aggiornamento del file nel pacchetto."
|
||||||
|
|
||||||
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
|
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "Unexpected error occurred while updating pack metadata."
|
msgid "Unexpected error occurred while updating pack metadata."
|
||||||
msgstr "Errore inaspettato durante l'aggiornamento dei metadati del pacchetto."
|
msgstr "Errore inatteso durante l'aggiornamento dei metadati del pacchetto."
|
||||||
|
|
||||||
#: lib/pleroma/plugs/user_is_admin_plug.ex:21
|
#: lib/pleroma/plugs/user_is_admin_plug.ex:21
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
msgid "User is not an admin."
|
msgid "User is not an admin."
|
||||||
msgstr ""
|
msgstr "L'utente non è un amministratore."
|
||||||
"L'utente non è un amministratore."
|
|
||||||
"OAuth."
|
|
||||||
|
|
||||||
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
|
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
|
||||||
#, elixir-format
|
#, elixir-format
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do
|
||||||
begin
|
begin
|
||||||
result := jsonb_set(target, path, coalesce(new_value, 'null'::jsonb), create_missing);
|
result := jsonb_set(target, path, coalesce(new_value, 'null'::jsonb), create_missing);
|
||||||
if result is NULL then
|
if result is NULL then
|
||||||
raise 'jsonb_set tried to wipe the object, please report this incindent to Pleroma bug tracker. https://git.pleroma.social/pleroma/pleroma/issues/new';
|
raise 'jsonb_set tried to wipe the object, please report this incident to Pleroma bug tracker. https://git.pleroma.social/pleroma/pleroma/issues/new';
|
||||||
return target;
|
return target;
|
||||||
else
|
else
|
||||||
return result;
|
return result;
|
||||||
|
|
13
priv/repo/migrations/20201221202251_create_hashtags.exs
Normal file
13
priv/repo/migrations/20201221202251_create_hashtags.exs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateHashtags do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists table(:hashtags) do
|
||||||
|
add(:name, :citext, null: false)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(unique_index(:hashtags, [:name]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.RemoveDataFromHashtags do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:hashtags) do
|
||||||
|
remove_if_exists(:data, :map)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:hashtags) do
|
||||||
|
add_if_not_exists(:data, :map, default: %{})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,13 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateHashtagsObjects do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists table(:hashtags_objects, primary_key: false) do
|
||||||
|
add(:hashtag_id, references(:hashtags), null: false, primary_key: true)
|
||||||
|
add(:object_id, references(:objects), null: false, primary_key: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Note: PK index: "hashtags_objects_pkey" PRIMARY KEY, btree (hashtag_id, object_id)
|
||||||
|
create_if_not_exists(index(:hashtags_objects, [:object_id]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateDataMigrations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists table(:data_migrations) do
|
||||||
|
add(:name, :string, null: false)
|
||||||
|
add(:state, :integer, default: 1)
|
||||||
|
add(:feature_lock, :boolean, default: false)
|
||||||
|
add(:params, :map, default: %{})
|
||||||
|
add(:data, :map, default: %{})
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(unique_index(:data_migrations, [:name]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.DataMigrationCreatePopulateHashtagsTable do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
dt = NaiveDateTime.utc_now()
|
||||||
|
|
||||||
|
execute(
|
||||||
|
"INSERT INTO data_migrations(name, inserted_at, updated_at) " <>
|
||||||
|
"VALUES ('populate_hashtags_table', '#{dt}', '#{dt}') ON CONFLICT DO NOTHING;"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("DELETE FROM data_migrations WHERE name = 'populate_hashtags_table';")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateDataMigrationFailedIds do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists table(:data_migration_failed_ids, primary_key: false) do
|
||||||
|
add(:data_migration_id, references(:data_migrations), null: false, primary_key: true)
|
||||||
|
add(:record_id, :bigint, null: false, primary_key: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(
|
||||||
|
unique_index(:data_migration_failed_ids, [:data_migration_id, :record_id])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.RemoveHashtagsObjectsDuplicateIndex do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
@moduledoc "Removes `hashtags_objects_hashtag_id_object_id_index` index (duplicate of PK index)."
|
||||||
|
|
||||||
|
def up do
|
||||||
|
drop_if_exists(unique_index(:hashtags_objects, [:hashtag_id, :object_id]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def down, do: nil
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.ChangeHashtagsNameToText do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:hashtags) do
|
||||||
|
modify(:name, :text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:hashtags) do
|
||||||
|
modify(:name, :citext)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
80
test/fixtures/bridgy/actor.json
vendored
Normal file
80
test/fixtures/bridgy/actor.json
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"id": "https://fed.brid.gy/jk.nipponalba.scot",
|
||||||
|
"url": "https://fed.brid.gy/r/https://jk.nipponalba.scot",
|
||||||
|
"urls": [
|
||||||
|
{
|
||||||
|
"value": "https://jk.nipponalba.scot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "https://social.nipponalba.scot/jk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "https://px.nipponalba.scot/jk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"type": "Person",
|
||||||
|
"name": "J K 🇯🇵🏴",
|
||||||
|
"image": [
|
||||||
|
{
|
||||||
|
"url": "https://jk.nipponalba.scot/images/profile.jpg",
|
||||||
|
"type": "Image",
|
||||||
|
"name": "profile picture"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"type": "Tag",
|
||||||
|
"name": "Craft Beer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Tag",
|
||||||
|
"name": "Single Malt Whisky"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Tag",
|
||||||
|
"name": "Homebrewing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Tag",
|
||||||
|
"name": "Scottish Politics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Tag",
|
||||||
|
"name": "Scottish History"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Tag",
|
||||||
|
"name": "Japanese History"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Tag",
|
||||||
|
"name": "Tech"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Tag",
|
||||||
|
"name": "Veganism"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Tag",
|
||||||
|
"name": "Cooking"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icon": [
|
||||||
|
{
|
||||||
|
"url": "https://jk.nipponalba.scot/images/profile.jpg",
|
||||||
|
"type": "Image",
|
||||||
|
"name": "profile picture"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preferredUsername": "jk.nipponalba.scot",
|
||||||
|
"summary": "",
|
||||||
|
"publicKey": {
|
||||||
|
"id": "jk.nipponalba.scot",
|
||||||
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdarxwzxnNbJ2hneWOYHkYJowk\npyigQtxlUd0VjgSQHwxU9kWqfbrHBVADyTtcqi/4dAzQd3UnCI1TPNnn4LPZY9PW\noiWd3Zl1/EfLFxO7LU9GS7fcSLQkyj5JNhSlN3I8QPudZbybrgRDVZYooDe1D+52\n5KLGqC2ajrIVOiDRTQIDAQAB\n-----END PUBLIC KEY-----"
|
||||||
|
},
|
||||||
|
"inbox": "https://fed.brid.gy/jk.nipponalba.scot/inbox",
|
||||||
|
"outbox": "https://fed.brid.gy/jk.nipponalba.scot/outbox",
|
||||||
|
"following": "https://fed.brid.gy/jk.nipponalba.scot/following",
|
||||||
|
"followers": "https://fed.brid.gy/jk.nipponalba.scot/followers"
|
||||||
|
}
|
49
test/fixtures/tesla_mock/emoji-in-summary.json
vendored
Normal file
49
test/fixtures/tesla_mock/emoji-in-summary.json
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://patch.cx/schemas/litepub-0.1.jsonld",
|
||||||
|
{
|
||||||
|
"@language": "und"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actor": "https://patch.cx/users/rin",
|
||||||
|
"attachment": [],
|
||||||
|
"attributedTo": "https://patch.cx/users/rin",
|
||||||
|
"cc": [
|
||||||
|
"https://patch.cx/users/rin/followers"
|
||||||
|
],
|
||||||
|
"content": ":joker_disapprove: <br><br>just grabbing a test fixture, nevermind me",
|
||||||
|
"context": "https://patch.cx/contexts/2c3ce4b4-18b1-4b1a-8965-3932027b5326",
|
||||||
|
"conversation": "https://patch.cx/contexts/2c3ce4b4-18b1-4b1a-8965-3932027b5326",
|
||||||
|
"id": "https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
|
||||||
|
"published": "2021-03-22T16:54:46.461939Z",
|
||||||
|
"sensitive": null,
|
||||||
|
"source": ":joker_disapprove: \r\n\r\njust grabbing a test fixture, nevermind me",
|
||||||
|
"summary": ":joker_smile: ",
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://patch.cx/emoji/custom/joker_disapprove.png"
|
||||||
|
},
|
||||||
|
"id": "https://patch.cx/emoji/custom/joker_disapprove.png",
|
||||||
|
"name": ":joker_disapprove:",
|
||||||
|
"type": "Emoji",
|
||||||
|
"updated": "1970-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://patch.cx/emoji/custom/joker_smile.png"
|
||||||
|
},
|
||||||
|
"id": "https://patch.cx/emoji/custom/joker_smile.png",
|
||||||
|
"name": ":joker_smile:",
|
||||||
|
"type": "Emoji",
|
||||||
|
"updated": "1970-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"type": "Note"
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
|
||||||
<Link rel="lrdd" template="https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource={uri}" type="application/xrd+xml" />
|
|
||||||
</XRD>
|
|
|
@ -200,6 +200,44 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "migrate_from_db/1" do
|
||||||
|
setup do: clear_config(:configurable_from_database, true)
|
||||||
|
|
||||||
|
setup do
|
||||||
|
insert_config_record(:pleroma, :setting_first, key: "value", key2: ["Activity"])
|
||||||
|
insert_config_record(:pleroma, :setting_second, key: "value2", key2: [Repo])
|
||||||
|
insert_config_record(:quack, :level, :info)
|
||||||
|
|
||||||
|
path = "test/instance_static"
|
||||||
|
file_path = Path.join(path, "temp.exported_from_db.secret.exs")
|
||||||
|
|
||||||
|
on_exit(fn -> File.rm!(file_path) end)
|
||||||
|
|
||||||
|
[file_path: file_path]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with path parameter", %{file_path: file_path} do
|
||||||
|
MixTask.run(["migrate_from_db", "--env", "temp", "--path", Path.dirname(file_path)])
|
||||||
|
|
||||||
|
file = File.read!(file_path)
|
||||||
|
assert file =~ "config :pleroma, :setting_first,"
|
||||||
|
assert file =~ "config :pleroma, :setting_second,"
|
||||||
|
assert file =~ "config :quack, :level, :info"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "release", %{file_path: file_path} do
|
||||||
|
clear_config(:release, true)
|
||||||
|
clear_config(:config_path, file_path)
|
||||||
|
|
||||||
|
MixTask.run(["migrate_from_db", "--env", "temp"])
|
||||||
|
|
||||||
|
file = File.read!(file_path)
|
||||||
|
assert file =~ "config :pleroma, :setting_first,"
|
||||||
|
assert file =~ "config :pleroma, :setting_second,"
|
||||||
|
assert file =~ "config :quack, :level, :info"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "operations on database config" do
|
describe "operations on database config" do
|
||||||
setup do: clear_config(:configurable_from_database, true)
|
setup do: clear_config(:configurable_from_database, true)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ defmodule Pleroma.Activity.Ir.TopicsTest do
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
import Mock
|
||||||
|
|
||||||
describe "poll answer" do
|
describe "poll answer" do
|
||||||
test "produce no topics" do
|
test "produce no topics" do
|
||||||
activity = %Activity{object: %Object{data: %{"type" => "Answer"}}}
|
activity = %Activity{object: %Object{data: %{"type" => "Answer"}}}
|
||||||
|
@ -77,14 +79,13 @@ defmodule Pleroma.Activity.Ir.TopicsTest do
|
||||||
refute Enum.member?(topics, "public:local:media")
|
refute Enum.member?(topics, "public:local:media")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "converts tags to hash tags", %{activity: %{object: %{data: data} = object} = activity} do
|
test "converts tags to hash tags", %{activity: activity} do
|
||||||
tagged_data = Map.put(data, "tag", ["foo", "bar"])
|
with_mock(Object, [:passthrough], hashtags: fn _ -> ["foo", "bar"] end) do
|
||||||
activity = %{activity | object: %{object | data: tagged_data}}
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
topics = Topics.get_activity_topics(activity)
|
assert Enum.member?(topics, "hashtag:foo")
|
||||||
|
assert Enum.member?(topics, "hashtag:bar")
|
||||||
assert Enum.member?(topics, "hashtag:foo")
|
end
|
||||||
assert Enum.member?(topics, "hashtag:bar")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "only converts strings to hash tags", %{
|
test "only converts strings to hash tags", %{
|
||||||
|
|
17
test/pleroma/hashtag_test.exs
Normal file
17
test/pleroma/hashtag_test.exs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HashtagTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Hashtag
|
||||||
|
|
||||||
|
describe "changeset validations" do
|
||||||
|
test "ensure non-blank :name" do
|
||||||
|
changeset = Hashtag.changeset(%Hashtag{}, %{name: ""})
|
||||||
|
|
||||||
|
assert {:name, {"can't be blank", [validation: :required]}} in changeset.errors
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,10 +5,13 @@
|
||||||
defmodule Pleroma.ObjectTest do
|
defmodule Pleroma.ObjectTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
use Oban.Testing, repo: Pleroma.Repo
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
import ExUnit.CaptureLog
|
import ExUnit.CaptureLog
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import Tesla.Mock
|
import Tesla.Mock
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Hashtag
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Tests.ObanHelpers
|
alias Pleroma.Tests.ObanHelpers
|
||||||
|
@ -417,4 +420,28 @@ defmodule Pleroma.ObjectTest do
|
||||||
assert updated_object.data["like_count"] == 1
|
assert updated_object.data["like_count"] == 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ":hashtags association" do
|
||||||
|
test "Hashtag records are created with Object record and updated on its change" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, %{object: object}} =
|
||||||
|
CommonAPI.post(user, %{status: "some text #hashtag1 #hashtag2 ..."})
|
||||||
|
|
||||||
|
assert [%Hashtag{name: "hashtag1"}, %Hashtag{name: "hashtag2"}] =
|
||||||
|
Enum.sort_by(object.hashtags, & &1.name)
|
||||||
|
|
||||||
|
{:ok, object} = Object.update_data(object, %{"tag" => []})
|
||||||
|
|
||||||
|
assert [] = object.hashtags
|
||||||
|
|
||||||
|
object = Object.get_by_id(object.id) |> Repo.preload(:hashtags)
|
||||||
|
assert [] = object.hashtags
|
||||||
|
|
||||||
|
{:ok, object} = Object.update_data(object, %{"tag" => ["abc", "def"]})
|
||||||
|
|
||||||
|
assert [%Hashtag{name: "abc"}, %Hashtag{name: "def"}] =
|
||||||
|
Enum.sort_by(object.hashtags, & &1.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,24 +18,23 @@ defmodule Pleroma.ReverseProxyTest do
|
||||||
|
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
|
||||||
defp user_agent_mock(user_agent, invokes) do
|
defp request_mock(invokes) do
|
||||||
json = Jason.encode!(%{"user-agent": user_agent})
|
|
||||||
|
|
||||||
ClientMock
|
ClientMock
|
||||||
|> expect(:request, fn :get, url, _, _, _ ->
|
|> expect(:request, fn :get, url, headers, _body, _opts ->
|
||||||
Registry.register(ClientMock, url, 0)
|
Registry.register(ClientMock, url, 0)
|
||||||
|
body = headers |> Enum.into(%{}) |> Jason.encode!()
|
||||||
|
|
||||||
{:ok, 200,
|
{:ok, 200,
|
||||||
[
|
[
|
||||||
{"content-type", "application/json"},
|
{"content-type", "application/json"},
|
||||||
{"content-length", byte_size(json) |> to_string()}
|
{"content-length", byte_size(body) |> to_string()}
|
||||||
], %{url: url}}
|
], %{url: url, body: body}}
|
||||||
end)
|
end)
|
||||||
|> expect(:stream_body, invokes, fn %{url: url} = client ->
|
|> expect(:stream_body, invokes, fn %{url: url, body: body} = client ->
|
||||||
case Registry.lookup(ClientMock, url) do
|
case Registry.lookup(ClientMock, url) do
|
||||||
[{_, 0}] ->
|
[{_, 0}] ->
|
||||||
Registry.update_value(ClientMock, url, &(&1 + 1))
|
Registry.update_value(ClientMock, url, &(&1 + 1))
|
||||||
{:ok, json, client}
|
{:ok, body, client}
|
||||||
|
|
||||||
[{_, 1}] ->
|
[{_, 1}] ->
|
||||||
Registry.unregister(ClientMock, url)
|
Registry.unregister(ClientMock, url)
|
||||||
|
@ -46,7 +45,7 @@ defmodule Pleroma.ReverseProxyTest do
|
||||||
|
|
||||||
describe "reverse proxy" do
|
describe "reverse proxy" do
|
||||||
test "do not track successful request", %{conn: conn} do
|
test "do not track successful request", %{conn: conn} do
|
||||||
user_agent_mock("hackney/1.15.1", 2)
|
request_mock(2)
|
||||||
url = "/success"
|
url = "/success"
|
||||||
|
|
||||||
conn = ReverseProxy.call(conn, url)
|
conn = ReverseProxy.call(conn, url)
|
||||||
|
@ -56,18 +55,15 @@ defmodule Pleroma.ReverseProxyTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "user-agent" do
|
test "use Pleroma's user agent in the request; don't pass the client's", %{conn: conn} do
|
||||||
test "don't keep", %{conn: conn} do
|
request_mock(2)
|
||||||
user_agent_mock("hackney/1.15.1", 2)
|
|
||||||
conn = ReverseProxy.call(conn, "/user-agent")
|
|
||||||
assert json_response(conn, 200) == %{"user-agent" => "hackney/1.15.1"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "keep", %{conn: conn} do
|
conn =
|
||||||
user_agent_mock(Pleroma.Application.user_agent(), 2)
|
conn
|
||||||
conn = ReverseProxy.call(conn, "/user-agent-keep", keep_user_agent: true)
|
|> Plug.Conn.put_req_header("user-agent", "fake/1.0")
|
||||||
assert json_response(conn, 200) == %{"user-agent" => Pleroma.Application.user_agent()}
|
|> ReverseProxy.call("/user-agent")
|
||||||
end
|
|
||||||
|
assert json_response(conn, 200) == %{"user-agent" => Pleroma.Application.user_agent()}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "closed connection", %{conn: conn} do
|
test "closed connection", %{conn: conn} do
|
||||||
|
@ -114,7 +110,7 @@ defmodule Pleroma.ReverseProxyTest do
|
||||||
|
|
||||||
describe "max_body" do
|
describe "max_body" do
|
||||||
test "length returns error if content-length more than option", %{conn: conn} do
|
test "length returns error if content-length more than option", %{conn: conn} do
|
||||||
user_agent_mock("hackney/1.15.1", 0)
|
request_mock(0)
|
||||||
|
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
ReverseProxy.call(conn, "/huge-file", max_body_length: 4)
|
ReverseProxy.call(conn, "/huge-file", max_body_length: 4)
|
||||||
|
|
|
@ -208,37 +208,96 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
|
||||||
assert user.name == "Bernie2020 group"
|
assert user.name == "Bernie2020 group"
|
||||||
assert user.actor_type == "Group"
|
assert user.actor_type == "Group"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "works for bridgy actors" do
|
||||||
|
user_id = "https://fed.brid.gy/jk.nipponalba.scot"
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{method: :get, url: ^user_id} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/bridgy/actor.json"),
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
|
||||||
|
|
||||||
|
assert user.actor_type == "Person"
|
||||||
|
|
||||||
|
assert user.avatar == %{
|
||||||
|
"type" => "Image",
|
||||||
|
"url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert user.banner == %{
|
||||||
|
"type" => "Image",
|
||||||
|
"url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it fetches the appropriate tag-restricted posts" do
|
test "it fetches the appropriate tag-restricted posts" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
{:ok, status_one} = CommonAPI.post(user, %{status: ". #test"})
|
{:ok, status_one} = CommonAPI.post(user, %{status: ". #TEST"})
|
||||||
{:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
|
{:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
|
||||||
{:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
|
{:ok, status_three} = CommonAPI.post(user, %{status: ". #test #Reject"})
|
||||||
|
|
||||||
fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
|
{:ok, status_four} = CommonAPI.post(user, %{status: ". #Any1 #any2"})
|
||||||
|
{:ok, status_five} = CommonAPI.post(user, %{status: ". #Any2 #any1"})
|
||||||
|
|
||||||
fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]})
|
for hashtag_timeline_strategy <- [:enabled, :disabled] do
|
||||||
|
clear_config([:features, :improved_hashtag_timeline], hashtag_timeline_strategy)
|
||||||
|
|
||||||
fetch_three =
|
fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
|
||||||
ActivityPub.fetch_activities([], %{
|
|
||||||
type: "Create",
|
|
||||||
tag: ["test", "essais"],
|
|
||||||
tag_reject: ["reject"]
|
|
||||||
})
|
|
||||||
|
|
||||||
fetch_four =
|
fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["TEST", "essais"]})
|
||||||
ActivityPub.fetch_activities([], %{
|
|
||||||
type: "Create",
|
|
||||||
tag: ["test"],
|
|
||||||
tag_all: ["test", "reject"]
|
|
||||||
})
|
|
||||||
|
|
||||||
assert fetch_one == [status_one, status_three]
|
fetch_three =
|
||||||
assert fetch_two == [status_one, status_two, status_three]
|
ActivityPub.fetch_activities([], %{
|
||||||
assert fetch_three == [status_one, status_two]
|
type: "Create",
|
||||||
assert fetch_four == [status_three]
|
tag: ["test", "Essais"],
|
||||||
|
tag_reject: ["reject"]
|
||||||
|
})
|
||||||
|
|
||||||
|
fetch_four =
|
||||||
|
ActivityPub.fetch_activities([], %{
|
||||||
|
type: "Create",
|
||||||
|
tag: ["test"],
|
||||||
|
tag_all: ["test", "REJECT"]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Testing that deduplication (if needed) is done on DB (not Ecto) level; :limit is important
|
||||||
|
fetch_five =
|
||||||
|
ActivityPub.fetch_activities([], %{
|
||||||
|
type: "Create",
|
||||||
|
tag: ["ANY1", "any2"],
|
||||||
|
limit: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
fetch_six =
|
||||||
|
ActivityPub.fetch_activities([], %{
|
||||||
|
type: "Create",
|
||||||
|
tag: ["any1", "Any2"],
|
||||||
|
tag_all: [],
|
||||||
|
tag_reject: []
|
||||||
|
})
|
||||||
|
|
||||||
|
# Regression test: passing empty lists as filter options shouldn't affect the results
|
||||||
|
assert fetch_five == fetch_six
|
||||||
|
|
||||||
|
[fetch_one, fetch_two, fetch_three, fetch_four, fetch_five] =
|
||||||
|
Enum.map([fetch_one, fetch_two, fetch_three, fetch_four, fetch_five], fn statuses ->
|
||||||
|
Enum.map(statuses, fn s -> Repo.preload(s, object: :hashtags) end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert fetch_one == [status_one, status_three]
|
||||||
|
assert fetch_two == [status_one, status_two, status_three]
|
||||||
|
assert fetch_three == [status_one, status_two]
|
||||||
|
assert fetch_four == [status_three]
|
||||||
|
assert fetch_five == [status_four, status_five]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "insertion" do
|
describe "insertion" do
|
||||||
|
|
31
test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs
Normal file
31
test/pleroma/web/activity_pub/mrf/hashtag_policy_test.exs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicyTest do
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "it sets the sensitive property with relevant hashtags" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
|
||||||
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
assert modified["object"]["sensitive"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it doesn't sets the sensitive property with irrelevant hashtags" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe hey"})
|
||||||
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
refute modified["object"]["sensitive"]
|
||||||
|
end
|
||||||
|
end
|
|
@ -75,10 +75,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
|
||||||
local_message = build_local_message()
|
local_message = build_local_message()
|
||||||
|
|
||||||
assert SimplePolicy.filter(media_message) ==
|
assert SimplePolicy.filter(media_message) ==
|
||||||
{:ok,
|
{:ok, put_in(media_message, ["object", "sensitive"], true)}
|
||||||
media_message
|
|
||||||
|> put_in(["object", "tag"], ["foo", "nsfw"])
|
|
||||||
|> put_in(["object", "sensitive"], true)}
|
|
||||||
|
|
||||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||||
end
|
end
|
||||||
|
@ -89,10 +86,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
|
||||||
local_message = build_local_message()
|
local_message = build_local_message()
|
||||||
|
|
||||||
assert SimplePolicy.filter(media_message) ==
|
assert SimplePolicy.filter(media_message) ==
|
||||||
{:ok,
|
{:ok, put_in(media_message, ["object", "sensitive"], true)}
|
||||||
media_message
|
|
||||||
|> put_in(["object", "tag"], ["foo", "nsfw"])
|
|
||||||
|> put_in(["object", "sensitive"], true)}
|
|
||||||
|
|
||||||
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
assert SimplePolicy.filter(local_message) == {:ok, local_message}
|
||||||
end
|
end
|
||||||
|
|
|
@ -114,7 +114,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicyTest do
|
||||||
except_message = %{
|
except_message = %{
|
||||||
"actor" => actor.ap_id,
|
"actor" => actor.ap_id,
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"object" => %{"tag" => ["test", "nsfw"], "attachment" => ["file1"], "sensitive" => true}
|
"object" => %{"tag" => ["test"], "attachment" => ["file1"], "sensitive" => true}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert TagPolicy.filter(message) == {:ok, except_message}
|
assert TagPolicy.filter(message) == {:ok, except_message}
|
||||||
|
|
|
@ -68,7 +68,12 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
|
||||||
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy])
|
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy])
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
mrf_policies: ["NoOpPolicy"],
|
mrf_policies: ["NoOpPolicy", "HashtagPolicy"],
|
||||||
|
mrf_hashtag: %{
|
||||||
|
federated_timeline_removal: [],
|
||||||
|
reject: [],
|
||||||
|
sensitive: ["nsfw"]
|
||||||
|
},
|
||||||
exclusions: false
|
exclusions: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,8 +84,13 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
|
||||||
clear_config([:mrf, :policies], [MRFModuleMock])
|
clear_config([:mrf, :policies], [MRFModuleMock])
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
mrf_policies: ["MRFModuleMock"],
|
mrf_policies: ["MRFModuleMock", "HashtagPolicy"],
|
||||||
mrf_module_mock: "some config data",
|
mrf_module_mock: "some config data",
|
||||||
|
mrf_hashtag: %{
|
||||||
|
federated_timeline_removal: [],
|
||||||
|
reject: [],
|
||||||
|
sensitive: ["nsfw"]
|
||||||
|
},
|
||||||
exclusions: false
|
exclusions: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
object = Object.normalize(data["object"], fetch: false)
|
object = Object.normalize(data["object"], fetch: false)
|
||||||
|
|
||||||
assert "test" in object.data["tag"]
|
assert "test" in Object.tags(object)
|
||||||
|
assert Object.hashtags(object) == ["test"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it cleans up incoming notices which are not really DMs" do
|
test "it cleans up incoming notices which are not really DMs" do
|
||||||
|
@ -220,7 +221,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
object = Object.normalize(data["object"], fetch: false)
|
object = Object.normalize(data["object"], fetch: false)
|
||||||
|
|
||||||
assert Enum.at(object.data["tag"], 2) == "moo"
|
assert Enum.at(Object.tags(object), 2) == "moo"
|
||||||
|
assert Object.hashtags(object) == ["moo"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming notices with contentMap" do
|
test "it works for incoming notices with contentMap" do
|
||||||
|
|
|
@ -153,15 +153,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it adds the sensitive property" do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"})
|
|
||||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
|
||||||
|
|
||||||
assert modified["object"]["sensitive"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it adds the json-ld context and the conversation property" do
|
test "it adds the json-ld context and the conversation property" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123")
|
conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123")
|
||||||
|
|
||||||
assert json_response(conn, 200)
|
assert json_response_and_validate_schema(conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
|
test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
|
||||||
|
@ -67,7 +67,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|> assign(:token, good_token)
|
|> assign(:token, good_token)
|
||||||
|> get(url)
|
|> get(url)
|
||||||
|
|
||||||
assert json_response(conn, 200)
|
assert json_response_and_validate_schema(conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
for good_token <- [good_token1, good_token2, good_token3] do
|
for good_token <- [good_token1, good_token2, good_token3] do
|
||||||
|
@ -87,7 +87,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|> assign(:token, bad_token)
|
|> assign(:token, bad_token)
|
||||||
|> get(url)
|
|> get(url)
|
||||||
|
|
||||||
assert json_response(conn, :forbidden)
|
assert json_response_and_validate_schema(conn, :forbidden)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
assert ModerationLog.get_log_entry_message(log_entry) ==
|
assert ModerationLog.get_log_entry_message(log_entry) ==
|
||||||
"@#{admin.nickname} deleted users: @#{user.nickname}"
|
"@#{admin.nickname} deleted users: @#{user.nickname}"
|
||||||
|
|
||||||
assert json_response(conn, 200) == [user.nickname]
|
assert json_response_and_validate_schema(conn, 200) == [user.nickname]
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = Repo.get(User, user.id)
|
||||||
refute user.is_active
|
refute user.is_active
|
||||||
|
@ -152,28 +152,30 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
user_one = insert(:user)
|
user_one = insert(:user)
|
||||||
user_two = insert(:user)
|
user_two = insert(:user)
|
||||||
|
|
||||||
conn =
|
response =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|> delete("/api/pleroma/admin/users", %{
|
|> delete("/api/pleroma/admin/users", %{
|
||||||
nicknames: [user_one.nickname, user_two.nickname]
|
nicknames: [user_one.nickname, user_two.nickname]
|
||||||
})
|
})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
log_entry = Repo.one(ModerationLog)
|
log_entry = Repo.one(ModerationLog)
|
||||||
|
|
||||||
assert ModerationLog.get_log_entry_message(log_entry) ==
|
assert ModerationLog.get_log_entry_message(log_entry) ==
|
||||||
"@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
|
"@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
|
||||||
|
|
||||||
response = json_response(conn, 200)
|
|
||||||
assert response -- [user_one.nickname, user_two.nickname] == []
|
assert response -- [user_one.nickname, user_two.nickname] == []
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "/api/pleroma/admin/users" do
|
describe "/api/pleroma/admin/users" do
|
||||||
test "Create", %{conn: conn} do
|
test "Create", %{conn: conn} do
|
||||||
conn =
|
response =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/pleroma/admin/users", %{
|
|> post("/api/pleroma/admin/users", %{
|
||||||
"users" => [
|
"users" => [
|
||||||
%{
|
%{
|
||||||
|
@ -188,8 +190,9 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|> Enum.map(&Map.get(&1, "type"))
|
||||||
|
|
||||||
response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type"))
|
|
||||||
assert response == ["success", "success"]
|
assert response == ["success", "success"]
|
||||||
|
|
||||||
log_entry = Repo.one(ModerationLog)
|
log_entry = Repo.one(ModerationLog)
|
||||||
|
@ -203,6 +206,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/pleroma/admin/users", %{
|
|> post("/api/pleroma/admin/users", %{
|
||||||
"users" => [
|
"users" => [
|
||||||
%{
|
%{
|
||||||
|
@ -213,7 +217,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
assert json_response(conn, 409) == [
|
assert json_response_and_validate_schema(conn, 409) == [
|
||||||
%{
|
%{
|
||||||
"code" => 409,
|
"code" => 409,
|
||||||
"data" => %{
|
"data" => %{
|
||||||
|
@ -232,6 +236,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/pleroma/admin/users", %{
|
|> post("/api/pleroma/admin/users", %{
|
||||||
"users" => [
|
"users" => [
|
||||||
%{
|
%{
|
||||||
|
@ -242,7 +247,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
assert json_response(conn, 409) == [
|
assert json_response_and_validate_schema(conn, 409) == [
|
||||||
%{
|
%{
|
||||||
"code" => 409,
|
"code" => 409,
|
||||||
"data" => %{
|
"data" => %{
|
||||||
|
@ -261,6 +266,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/pleroma/admin/users", %{
|
|> post("/api/pleroma/admin/users", %{
|
||||||
"users" => [
|
"users" => [
|
||||||
%{
|
%{
|
||||||
|
@ -276,7 +282,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
assert json_response(conn, 409) == [
|
assert json_response_and_validate_schema(conn, 409) == [
|
||||||
%{
|
%{
|
||||||
"code" => 409,
|
"code" => 409,
|
||||||
"data" => %{
|
"data" => %{
|
||||||
|
@ -307,7 +313,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
|
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
|
||||||
|
|
||||||
assert user_response(user) == json_response(conn, 200)
|
assert user_response(user) == json_response_and_validate_schema(conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "when the user doesn't exist", %{conn: conn} do
|
test "when the user doesn't exist", %{conn: conn} do
|
||||||
|
@ -315,7 +321,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
|
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
|
||||||
|
|
||||||
assert %{"error" => "Not found"} == json_response(conn, 404)
|
assert %{"error" => "Not found"} == json_response_and_validate_schema(conn, 404)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -326,6 +332,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/pleroma/admin/users/follow", %{
|
|> post("/api/pleroma/admin/users/follow", %{
|
||||||
"follower" => follower.nickname,
|
"follower" => follower.nickname,
|
||||||
"followed" => user.nickname
|
"followed" => user.nickname
|
||||||
|
@ -352,6 +359,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/pleroma/admin/users/unfollow", %{
|
|> post("/api/pleroma/admin/users/unfollow", %{
|
||||||
"follower" => follower.nickname,
|
"follower" => follower.nickname,
|
||||||
"followed" => user.nickname
|
"followed" => user.nickname
|
||||||
|
@ -395,7 +403,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
]
|
]
|
||||||
|> Enum.sort_by(& &1["nickname"])
|
|> Enum.sort_by(& &1["nickname"])
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 3,
|
"count" => 3,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => users
|
"users" => users
|
||||||
|
@ -410,7 +418,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
assert %{"count" => 26, "page_size" => 10, "users" => users1} =
|
assert %{"count" => 26, "page_size" => 10, "users" => users1} =
|
||||||
conn
|
conn
|
||||||
|> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
|
|> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
|
||||||
|> json_response(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert Enum.count(users1) == 10
|
assert Enum.count(users1) == 10
|
||||||
assert service1 not in users1
|
assert service1 not in users1
|
||||||
|
@ -418,7 +426,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
assert %{"count" => 26, "page_size" => 10, "users" => users2} =
|
assert %{"count" => 26, "page_size" => 10, "users" => users2} =
|
||||||
conn
|
conn
|
||||||
|> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
|
|> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
|
||||||
|> json_response(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert Enum.count(users2) == 10
|
assert Enum.count(users2) == 10
|
||||||
assert service1 not in users2
|
assert service1 not in users2
|
||||||
|
@ -426,7 +434,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
assert %{"count" => 26, "page_size" => 10, "users" => users3} =
|
assert %{"count" => 26, "page_size" => 10, "users" => users3} =
|
||||||
conn
|
conn
|
||||||
|> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
|
|> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
|
||||||
|> json_response(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert Enum.count(users3) == 6
|
assert Enum.count(users3) == 6
|
||||||
assert service1 not in users3
|
assert service1 not in users3
|
||||||
|
@ -437,7 +445,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users?page=2")
|
conn = get(conn, "/api/pleroma/admin/users?page=2")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 2,
|
"count" => 2,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => []
|
"users" => []
|
||||||
|
@ -449,7 +457,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users?query=bo")
|
conn = get(conn, "/api/pleroma/admin/users?query=bo")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => [user_response(user, %{"local" => true})]
|
"users" => [user_response(user, %{"local" => true})]
|
||||||
|
@ -462,7 +470,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
|
conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => [user_response(user)]
|
"users" => [user_response(user)]
|
||||||
|
@ -475,7 +483,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
|
conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => [user_response(user)]
|
"users" => [user_response(user)]
|
||||||
|
@ -488,7 +496,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users?name=display")
|
conn = get(conn, "/api/pleroma/admin/users?name=display")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => [user_response(user)]
|
"users" => [user_response(user)]
|
||||||
|
@ -501,7 +509,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
|
conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => [user_response(user)]
|
"users" => [user_response(user)]
|
||||||
|
@ -514,7 +522,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
|
conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
|
||||||
|
|
||||||
assert json_response(conn1, 200) == %{
|
assert json_response_and_validate_schema(conn1, 200) == %{
|
||||||
"count" => 2,
|
"count" => 2,
|
||||||
"page_size" => 1,
|
"page_size" => 1,
|
||||||
"users" => [user_response(user)]
|
"users" => [user_response(user)]
|
||||||
|
@ -522,7 +530,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
|
conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
|
||||||
|
|
||||||
assert json_response(conn2, 200) == %{
|
assert json_response_and_validate_schema(conn2, 200) == %{
|
||||||
"count" => 2,
|
"count" => 2,
|
||||||
"page_size" => 1,
|
"page_size" => 1,
|
||||||
"users" => [user_response(user2)]
|
"users" => [user_response(user2)]
|
||||||
|
@ -542,7 +550,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|> assign(:token, token)
|
|> assign(:token, token)
|
||||||
|> get("/api/pleroma/admin/users?query=bo&filters=local")
|
|> get("/api/pleroma/admin/users?query=bo&filters=local")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => [user_response(user)]
|
"users" => [user_response(user)]
|
||||||
|
@ -570,7 +578,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
]
|
]
|
||||||
|> Enum.sort_by(& &1["nickname"])
|
|> Enum.sort_by(& &1["nickname"])
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 3,
|
"count" => 3,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => users
|
"users" => users
|
||||||
|
@ -587,7 +595,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/api/pleroma/admin/users?filters=unconfirmed")
|
|> get("/api/pleroma/admin/users?filters=unconfirmed")
|
||||||
|> json_response(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
users =
|
users =
|
||||||
Enum.map([old_user, sad_user], fn user ->
|
Enum.map([old_user, sad_user], fn user ->
|
||||||
|
@ -620,7 +628,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => users
|
"users" => users
|
||||||
|
@ -647,7 +655,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
]
|
]
|
||||||
|> Enum.sort_by(& &1["nickname"])
|
|> Enum.sort_by(& &1["nickname"])
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 2,
|
"count" => 2,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => users
|
"users" => users
|
||||||
|
@ -661,7 +669,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
|
conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => [
|
"users" => [
|
||||||
|
@ -682,8 +690,8 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
response =
|
response =
|
||||||
conn
|
conn
|
||||||
|> get(user_path(conn, :list), %{actor_types: ["Person"]})
|
|> get(user_path(conn, :index), %{actor_types: ["Person"]})
|
||||||
|> json_response(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
users =
|
users =
|
||||||
[
|
[
|
||||||
|
@ -705,8 +713,8 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
response =
|
response =
|
||||||
conn
|
conn
|
||||||
|> get(user_path(conn, :list), %{actor_types: ["Person", "Service"]})
|
|> get(user_path(conn, :index), %{actor_types: ["Person", "Service"]})
|
||||||
|> json_response(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
users =
|
users =
|
||||||
[
|
[
|
||||||
|
@ -728,8 +736,8 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
response =
|
response =
|
||||||
conn
|
conn
|
||||||
|> get(user_path(conn, :list), %{actor_types: ["Service"]})
|
|> get(user_path(conn, :index), %{actor_types: ["Service"]})
|
||||||
|> json_response(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
users = [user_response(user_service, %{"actor_type" => "Service"})]
|
users = [user_response(user_service, %{"actor_type" => "Service"})]
|
||||||
|
|
||||||
|
@ -751,7 +759,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
]
|
]
|
||||||
|> Enum.sort_by(& &1["nickname"])
|
|> Enum.sort_by(& &1["nickname"])
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 2,
|
"count" => 2,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => users
|
"users" => users
|
||||||
|
@ -776,7 +784,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
%{"id" => ^admin_id},
|
%{"id" => ^admin_id},
|
||||||
%{"id" => ^user_id}
|
%{"id" => ^user_id}
|
||||||
]
|
]
|
||||||
} = json_response(conn, 200)
|
} = json_response_and_validate_schema(conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works with multiple filters" do
|
test "it works with multiple filters" do
|
||||||
|
@ -793,7 +801,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|> assign(:token, token)
|
|> assign(:token, token)
|
||||||
|> get("/api/pleroma/admin/users?filters=deactivated,external")
|
|> get("/api/pleroma/admin/users?filters=deactivated,external")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => [user_response(user)]
|
"users" => [user_response(user)]
|
||||||
|
@ -805,7 +813,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/users")
|
conn = get(conn, "/api/pleroma/admin/users")
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{
|
assert json_response_and_validate_schema(conn, 200) == %{
|
||||||
"count" => 1,
|
"count" => 1,
|
||||||
"page_size" => 50,
|
"page_size" => 50,
|
||||||
"users" => [
|
"users" => [
|
||||||
|
@ -820,13 +828,14 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
user_two = insert(:user, is_active: false)
|
user_two = insert(:user, is_active: false)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
patch(
|
conn
|
||||||
conn,
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> patch(
|
||||||
"/api/pleroma/admin/users/activate",
|
"/api/pleroma/admin/users/activate",
|
||||||
%{nicknames: [user_one.nickname, user_two.nickname]}
|
%{nicknames: [user_one.nickname, user_two.nickname]}
|
||||||
)
|
)
|
||||||
|
|
||||||
response = json_response(conn, 200)
|
response = json_response_and_validate_schema(conn, 200)
|
||||||
assert Enum.map(response["users"], & &1["is_active"]) == [true, true]
|
assert Enum.map(response["users"], & &1["is_active"]) == [true, true]
|
||||||
|
|
||||||
log_entry = Repo.one(ModerationLog)
|
log_entry = Repo.one(ModerationLog)
|
||||||
|
@ -840,13 +849,14 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
user_two = insert(:user, is_active: true)
|
user_two = insert(:user, is_active: true)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
patch(
|
conn
|
||||||
conn,
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> patch(
|
||||||
"/api/pleroma/admin/users/deactivate",
|
"/api/pleroma/admin/users/deactivate",
|
||||||
%{nicknames: [user_one.nickname, user_two.nickname]}
|
%{nicknames: [user_one.nickname, user_two.nickname]}
|
||||||
)
|
)
|
||||||
|
|
||||||
response = json_response(conn, 200)
|
response = json_response_and_validate_schema(conn, 200)
|
||||||
assert Enum.map(response["users"], & &1["is_active"]) == [false, false]
|
assert Enum.map(response["users"], & &1["is_active"]) == [false, false]
|
||||||
|
|
||||||
log_entry = Repo.one(ModerationLog)
|
log_entry = Repo.one(ModerationLog)
|
||||||
|
@ -860,13 +870,14 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
user_two = insert(:user, is_approved: false)
|
user_two = insert(:user, is_approved: false)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
patch(
|
conn
|
||||||
conn,
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> patch(
|
||||||
"/api/pleroma/admin/users/approve",
|
"/api/pleroma/admin/users/approve",
|
||||||
%{nicknames: [user_one.nickname, user_two.nickname]}
|
%{nicknames: [user_one.nickname, user_two.nickname]}
|
||||||
)
|
)
|
||||||
|
|
||||||
response = json_response(conn, 200)
|
response = json_response_and_validate_schema(conn, 200)
|
||||||
assert Enum.map(response["users"], & &1["is_approved"]) == [true, true]
|
assert Enum.map(response["users"], & &1["is_approved"]) == [true, true]
|
||||||
|
|
||||||
log_entry = Repo.one(ModerationLog)
|
log_entry = Repo.one(ModerationLog)
|
||||||
|
@ -878,9 +889,12 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
||||||
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
|
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
|
||||||
|
|
||||||
assert json_response(conn, 200) ==
|
assert json_response_and_validate_schema(conn, 200) ==
|
||||||
user_response(
|
user_response(
|
||||||
user,
|
user,
|
||||||
%{"is_active" => !user.is_active}
|
%{"is_active" => !user.is_active}
|
||||||
|
|
|
@ -25,6 +25,11 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
setup do: clear_config([:instance, :safe_dm_mentions])
|
setup do: clear_config([:instance, :safe_dm_mentions])
|
||||||
setup do: clear_config([:instance, :limit])
|
setup do: clear_config([:instance, :limit])
|
||||||
setup do: clear_config([:instance, :max_pinned_statuses])
|
setup do: clear_config([:instance, :max_pinned_statuses])
|
||||||
|
@ -493,7 +498,7 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
|
|
||||||
object = Object.normalize(activity, fetch: false)
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
|
||||||
assert object.data["tag"] == ["2hu"]
|
assert Object.tags(object) == ["2hu"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it adds emoji in the object" do
|
test "it adds emoji in the object" do
|
||||||
|
@ -517,6 +522,27 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
assert url == "#{Pleroma.Web.base_url()}/emoji/blank.png"
|
assert url == "#{Pleroma.Web.base_url()}/emoji/blank.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it copies emoji from the subject of the parent post" do
|
||||||
|
%Object{} =
|
||||||
|
object =
|
||||||
|
Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
|
||||||
|
fetch: true
|
||||||
|
)
|
||||||
|
|
||||||
|
activity = Activity.get_create_by_object_ap_id(object.data["id"])
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, reply_activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
in_reply_to_id: activity.id,
|
||||||
|
status: ":joker_disapprove:",
|
||||||
|
spoiler_text: ":joker_smile:"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert Object.normalize(reply_activity).data["emoji"][":joker_smile:"]
|
||||||
|
refute Object.normalize(reply_activity).data["emoji"][":joker_disapprove:"]
|
||||||
|
end
|
||||||
|
|
||||||
test "deactivated users can't post" do
|
test "deactivated users can't post" do
|
||||||
user = insert(:user, is_active: false)
|
user = insert(:user, is_active: false)
|
||||||
assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
|
assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
|
||||||
|
|
|
@ -262,8 +262,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
||||||
mentions: [],
|
mentions: [],
|
||||||
tags: [
|
tags: [
|
||||||
%{
|
%{
|
||||||
name: "#{object_data["tag"]}",
|
name: "#{hd(object_data["tag"])}",
|
||||||
url: "http://localhost:4001/tag/#{object_data["tag"]}"
|
url: "http://localhost:4001/tag/#{hd(object_data["tag"])}"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
application: nil,
|
application: nil,
|
||||||
|
|
|
@ -11,8 +11,7 @@ defmodule Pleroma.Web.MediaProxyTest do
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
defp decode_result(encoded) do
|
defp decode_result(encoded) do
|
||||||
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
|
{:ok, decoded} = MediaProxy.decode_url(encoded)
|
||||||
{:ok, decoded} = MediaProxy.decode_url(sig, base64)
|
|
||||||
decoded
|
decoded
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,26 @@ defmodule Pleroma.Web.WebFingerTest do
|
||||||
assert {:error, _} = WebFinger.finger("pleroma.social")
|
assert {:error, _} = WebFinger.finger("pleroma.social")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "returns error when there is no content-type header" do
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{url: "http://social.heldscal.la/.well-known/host-meta"} ->
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/social.heldscal.la_host_meta")
|
||||||
|
}}
|
||||||
|
|
||||||
|
%{
|
||||||
|
url:
|
||||||
|
"https://social.heldscal.la/.well-known/webfinger?resource=acct:invalid_content@social.heldscal.la"
|
||||||
|
} ->
|
||||||
|
{:ok, %Tesla.Env{status: 200, body: ""}}
|
||||||
|
end)
|
||||||
|
|
||||||
|
user = "invalid_content@social.heldscal.la"
|
||||||
|
assert {:error, {:content_type, nil}} = WebFinger.finger(user)
|
||||||
|
end
|
||||||
|
|
||||||
test "returns error when fails parse xml or json" do
|
test "returns error when fails parse xml or json" do
|
||||||
user = "invalid_content@social.heldscal.la"
|
user = "invalid_content@social.heldscal.la"
|
||||||
assert {:error, %Jason.DecodeError{}} = WebFinger.finger(user)
|
assert {:error, %Jason.DecodeError{}} = WebFinger.finger(user)
|
||||||
|
@ -113,5 +133,52 @@ defmodule Pleroma.Web.WebFingerTest do
|
||||||
ap_id = "https://" <> to_string(:idna.encode("zetsubou.みんな")) <> "/users/lain"
|
ap_id = "https://" <> to_string(:idna.encode("zetsubou.みんな")) <> "/users/lain"
|
||||||
{:ok, _data} = WebFinger.finger(ap_id)
|
{:ok, _data} = WebFinger.finger(ap_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "respects json content-type" do
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{
|
||||||
|
url:
|
||||||
|
"https://mastodon.social/.well-known/webfinger?resource=acct:emelie@mastodon.social"
|
||||||
|
} ->
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/webfinger_emelie.json"),
|
||||||
|
headers: [{"content-type", "application/jrd+json"}]
|
||||||
|
}}
|
||||||
|
|
||||||
|
%{url: "http://mastodon.social/.well-known/host-meta"} ->
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/mastodon.social_host_meta")
|
||||||
|
}}
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, _data} = WebFinger.finger("emelie@mastodon.social")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "respects xml content-type" do
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{
|
||||||
|
url: "https://pawoo.net/.well-known/webfinger?resource=acct:pekorino@pawoo.net"
|
||||||
|
} ->
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.xml"),
|
||||||
|
headers: [{"content-type", "application/xrd+xml"}]
|
||||||
|
}}
|
||||||
|
|
||||||
|
%{url: "http://pawoo.net/.well-known/host-meta"} ->
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/pawoo.net_host_meta")
|
||||||
|
}}
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, _data} = WebFinger.finger("pekorino@pawoo.net")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -67,13 +67,11 @@ defmodule Pleroma.Web.ConnCase do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp json_response_and_validate_schema(
|
defp json_response_and_validate_schema(
|
||||||
%{
|
%{private: %{operation_id: op_id}} = conn,
|
||||||
private: %{
|
|
||||||
open_api_spex: %{operation_id: op_id, operation_lookup: lookup, spec: spec}
|
|
||||||
}
|
|
||||||
} = conn,
|
|
||||||
status
|
status
|
||||||
) do
|
) do
|
||||||
|
{spec, lookup} = OpenApiSpex.Plug.PutApiSpec.get_spec_and_operation_lookup(conn)
|
||||||
|
|
||||||
content_type =
|
content_type =
|
||||||
conn
|
conn
|
||||||
|> Plug.Conn.get_resp_header("content-type")
|
|> Plug.Conn.get_resp_header("content-type")
|
||||||
|
|
|
@ -122,7 +122,7 @@ defmodule HttpRequestMock do
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
body: File.read!("test/fixtures/tesla_mock/mike@osada.macgirvin.com.json"),
|
body: File.read!("test/fixtures/tesla_mock/mike@osada.macgirvin.com.json"),
|
||||||
headers: activitypub_object_headers()
|
headers: [{"content-type", "application/jrd+json"}]
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -187,7 +187,8 @@ defmodule HttpRequestMock do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml")
|
body: File.read!("test/fixtures/tesla_mock/lain_squeet.me_webfinger.xml"),
|
||||||
|
headers: [{"content-type", "application/xrd+xml"}]
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -526,22 +527,6 @@ defmodule HttpRequestMock do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("http://zetsubou.xn--q9jyb4c/.well-known/host-meta", _, _, _) do
|
|
||||||
{:ok,
|
|
||||||
%Tesla.Env{
|
|
||||||
status: 200,
|
|
||||||
body: File.read!("test/fixtures/tesla_mock/xn--q9jyb4c_host_meta")
|
|
||||||
}}
|
|
||||||
end
|
|
||||||
|
|
||||||
def get("https://zetsubou.xn--q9jyb4c/.well-known/host-meta", _, _, _) do
|
|
||||||
{:ok,
|
|
||||||
%Tesla.Env{
|
|
||||||
status: 200,
|
|
||||||
body: File.read!("test/fixtures/tesla_mock/xn--q9jyb4c_host_meta")
|
|
||||||
}}
|
|
||||||
end
|
|
||||||
|
|
||||||
def get("http://pleroma.soykaf.com/.well-known/host-meta", _, _, _) do
|
def get("http://pleroma.soykaf.com/.well-known/host-meta", _, _, _) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
|
@ -786,7 +771,8 @@ defmodule HttpRequestMock do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
body: File.read!("test/fixtures/tesla_mock/shp@social.heldscal.la.xml")
|
body: File.read!("test/fixtures/tesla_mock/shp@social.heldscal.la.xml"),
|
||||||
|
headers: [{"content-type", "application/xrd+xml"}]
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -796,7 +782,7 @@ defmodule HttpRequestMock do
|
||||||
_,
|
_,
|
||||||
[{"accept", "application/xrd+xml,application/jrd+json"}]
|
[{"accept", "application/xrd+xml,application/jrd+json"}]
|
||||||
) do
|
) do
|
||||||
{:ok, %Tesla.Env{status: 200, body: ""}}
|
{:ok, %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/jrd+json"}]}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get("http://framatube.org/.well-known/host-meta", _, _, _) do
|
def get("http://framatube.org/.well-known/host-meta", _, _, _) do
|
||||||
|
@ -816,7 +802,7 @@ defmodule HttpRequestMock do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: [{"content-type", "application/json"}],
|
headers: [{"content-type", "application/jrd+json"}],
|
||||||
body: File.read!("test/fixtures/tesla_mock/framasoft@framatube.org.json")
|
body: File.read!("test/fixtures/tesla_mock/framasoft@framatube.org.json")
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
@ -876,7 +862,7 @@ defmodule HttpRequestMock do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: [{"content-type", "application/json"}],
|
headers: [{"content-type", "application/jrd+json"}],
|
||||||
body: File.read!("test/fixtures/tesla_mock/kaniini@gerzilla.de.json")
|
body: File.read!("test/fixtures/tesla_mock/kaniini@gerzilla.de.json")
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
@ -1074,7 +1060,8 @@ defmodule HttpRequestMock do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
body: File.read!("test/fixtures/lain.xml")
|
body: File.read!("test/fixtures/lain.xml"),
|
||||||
|
headers: [{"content-type", "application/xrd+xml"}]
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1087,7 +1074,16 @@ defmodule HttpRequestMock do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
body: File.read!("test/fixtures/lain.xml")
|
body: File.read!("test/fixtures/lain.xml"),
|
||||||
|
headers: [{"content-type", "application/xrd+xml"}]
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("http://zetsubou.xn--q9jyb4c/.well-known/host-meta", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/host-meta-zetsubou.xn--q9jyb4c.xml")
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1153,7 +1149,8 @@ defmodule HttpRequestMock do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
body: File.read!("test/fixtures/tesla_mock/kpherox@mstdn.jp.xml")
|
body: File.read!("test/fixtures/tesla_mock/kpherox@mstdn.jp.xml"),
|
||||||
|
headers: [{"content-type", "application/xrd+xml"}]
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1281,6 +1278,15 @@ defmodule HttpRequestMock do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/emoji-in-summary.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(url, query, body, headers) do
|
def get(url, query, body, headers) do
|
||||||
{:error,
|
{:error,
|
||||||
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
||||||
|
|
Loading…
Reference in a new issue