diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9ef3ddd0d..0fec89368 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,6 +34,14 @@ build: - mix deps.get - mix compile --force +spec-build: + stage: test + artifacts: + paths: + - spec.json + script: + - mix pleroma.openapi_spec spec.json + benchmark: stage: benchmark when: manual @@ -155,6 +163,20 @@ review_app: - (ssh -t dokku@pleroma.online -- certs:add "$CI_ENVIRONMENT_SLUG" /home/dokku/server.crt /home/dokku/server.key) || true - git push -f dokku@pleroma.online:$CI_ENVIRONMENT_SLUG $CI_COMMIT_SHA:refs/heads/master +spec-deploy: + stage: deploy + artifacts: + paths: + - spec.json + only: + - develop@pleroma/pleroma + image: alpine:latest + before_script: + - apk add curl + script: + - curl -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline + + stop_review_app: image: alpine:3.9 stage: deploy diff --git a/CHANGELOG.md b/CHANGELOG.md index f9a32f430..23567a97c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,18 +10,29 @@ 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_activated` to `mix pleroma.user activate/deactivate` -- **Breaking**: AdminAPI changed User field `confirmation_pending` to `is_confirmed` -- **Breaking**: AdminAPI changed User field `approval_pending` to `is_approved` -- **Breaking**: AdminAPI changed User field `deactivated` to `is_active` - 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. - Improved registration workflow for email confirmation and account approval modes. - Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries. - Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators. -- Admin API: Reports now ordered by newest - Deprecated `Pleroma.Uploaders.S3, :public_endpoint`. Now `Pleroma.Upload, :base_url` is the standard configuration key for all uploaders. - Improved Apache webserver support: updated sample configuration, MediaProxy cache invalidation verified with the included sample script - Improve OAuth 2.0 provider support. A missing `fqn` field was added to the response, but does not expose the user's email address. +- Provide redirect of external posts from `/notice/:id` to their original URL +- Admins no longer receive notifications for reports if they are the actor making the report. +- Improved Mailer configuration setting descriptions for AdminFE. + +
+ API Changes + +- **Breaking:** AdminAPI changed User field `confirmation_pending` to `is_confirmed` +- **Breaking:** AdminAPI changed User field `approval_pending` to `is_approved` +- **Breaking**: AdminAPI changed User field `deactivated` to `is_active` +- **Breaking:** AdminAPI `GET /api/pleroma/admin/users/:nickname_or_id/statuses` changed response format and added the number of total users posts. +- **Breaking:** AdminAPI `GET /api/pleroma/admin/instances/:instance/statuses` changed response format and added the number of total users posts. +- Admin API: Reports now ordered by newest + +
- Extracted object hashtags into separate table in order to improve hashtag timeline performance (via background migration in `Pleroma.Migrators.HashtagsTableMigrator`). ### Added @@ -41,6 +52,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Ability to set ActivityPub aliases for follower migration. - Configurable background job limits for RichMedia (link previews) and MediaProxyWarmingPolicy - 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 +- New users will receive a simple email confirming their registration if no other emails will be dispatched. (e.g., Welcome, Confirmation, or Approval Required)
API Changes @@ -73,6 +86,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Fixed last_status.account being not filled with account data. - Mastodon API: Fix not being able to add or remove multiple users at once in lists. - Mastodon API: Fixed own_votes being not returned with poll data. + - Mastodon API: Fixed creation of scheduled posts with polls. + - Mastodon API: Support for expires_in/expires_at in the Filters.
## Unreleased (Patch) diff --git a/benchmarks/load_testing/fetcher.ex b/benchmarks/load_testing/fetcher.ex index dfbd916be..607b7d4cb 100644 --- a/benchmarks/load_testing/fetcher.ex +++ b/benchmarks/load_testing/fetcher.ex @@ -33,10 +33,11 @@ defp fetch_user(user) do end defp create_filter(user) do - Pleroma.Filter.create(%Pleroma.Filter{ + Pleroma.Filter.create(%{ user_id: user.id, phrase: "must be filtered", - hide: true + hide: true, + context: ["home"] }) end diff --git a/config/config.exs b/config/config.exs index 91888c512..8a7c466d3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -543,6 +543,7 @@ queues: [ activity_expiration: 10, token_expiration: 5, + filter_expiration: 1, backup: 1, federator_incoming: 50, federator_outgoing: 50, diff --git a/config/description.exs b/config/description.exs index b2f301e2d..2e96024f5 100644 --- a/config/description.exs +++ b/config/description.exs @@ -99,7 +99,8 @@ key: :base_url, label: "Base URL", type: :string, - description: "Base URL for the uploads, needed if you use CDN", + description: + "Base URL for the uploads. Required if you use a CDN or host attachments under a different domain.", suggestions: [ "https://cdn-host.com" ] @@ -214,253 +215,216 @@ type: :group, description: "Mailer-related settings", children: [ + %{ + key: :enabled, + label: "Mailer Enabled", + type: :boolean + }, %{ key: :adapter, type: :module, description: - "One of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters)," <> - " or Swoosh.Adapters.Local for in-memory mailbox", + "One of the mail adapters listed in [Swoosh documentation](https://hexdocs.pm/swoosh/Swoosh.html#module-adapters)", suggestions: [ + Swoosh.Adapters.AmazonSES, + Swoosh.Adapters.Dyn, + Swoosh.Adapters.Gmail, + Swoosh.Adapters.Mailgun, + Swoosh.Adapters.Mailjet, + Swoosh.Adapters.Mandrill, + Swoosh.Adapters.Postmark, Swoosh.Adapters.SMTP, Swoosh.Adapters.Sendgrid, Swoosh.Adapters.Sendmail, - Swoosh.Adapters.Mandrill, - Swoosh.Adapters.Mailgun, - Swoosh.Adapters.Mailjet, - Swoosh.Adapters.Postmark, - Swoosh.Adapters.SparkPost, - Swoosh.Adapters.AmazonSES, - Swoosh.Adapters.Dyn, Swoosh.Adapters.SocketLabs, - Swoosh.Adapters.Gmail, - Swoosh.Adapters.Local + Swoosh.Adapters.SparkPost ] }, - %{ - key: :enabled, - type: :boolean, - description: "Allow/disallow send emails" - }, %{ group: {:subgroup, Swoosh.Adapters.SMTP}, key: :relay, type: :string, - description: "`Swoosh.Adapters.SMTP` adapter specific setting", - suggestions: ["smtp.gmail.com"] - }, - %{ - group: {:subgroup, Swoosh.Adapters.SMTP}, - key: :username, - type: :string, - description: "`Swoosh.Adapters.SMTP` adapter specific setting", - suggestions: ["pleroma"] - }, - %{ - group: {:subgroup, Swoosh.Adapters.SMTP}, - key: :password, - type: :string, - description: "`Swoosh.Adapters.SMTP` adapter specific setting", - suggestions: ["password"] - }, - %{ - group: {:subgroup, Swoosh.Adapters.SMTP}, - key: :ssl, - label: "SSL", - type: :boolean, - description: "`Swoosh.Adapters.SMTP` adapter specific setting" - }, - %{ - group: {:subgroup, Swoosh.Adapters.SMTP}, - key: :tls, - label: "TLS", - type: :atom, - description: "`Swoosh.Adapters.SMTP` adapter specific setting", - suggestions: [:always, :never, :if_available] - }, - %{ - group: {:subgroup, Swoosh.Adapters.SMTP}, - key: :auth, - type: :atom, - description: "`Swoosh.Adapters.SMTP` adapter specific setting", - suggestions: [:always, :never, :if_available] + description: "Hostname or IP address", + suggestions: ["smtp.example.com"] }, %{ group: {:subgroup, Swoosh.Adapters.SMTP}, key: :port, type: :integer, - description: "`Swoosh.Adapters.SMTP` adapter specific setting", - suggestions: [1025] + description: "SMTP port", + suggestions: ["1025"] + }, + %{ + group: {:subgroup, Swoosh.Adapters.SMTP}, + key: :username, + type: :string, + description: "SMTP AUTH username", + suggestions: ["user@example.com"] + }, + %{ + group: {:subgroup, Swoosh.Adapters.SMTP}, + key: :password, + type: :string, + description: "SMTP AUTH password", + suggestions: ["password"] + }, + %{ + group: {:subgroup, Swoosh.Adapters.SMTP}, + key: :ssl, + label: "Use SSL", + type: :boolean, + description: "Use Implicit SSL/TLS. e.g. port 465" + }, + %{ + group: {:subgroup, Swoosh.Adapters.SMTP}, + key: :tls, + label: "STARTTLS Mode", + type: {:dropdown, :atom}, + description: "Explicit TLS (STARTTLS) enforcement mode", + suggestions: [:if_available, :always, :never] + }, + %{ + group: {:subgroup, Swoosh.Adapters.SMTP}, + key: :auth, + label: "AUTH Mode", + type: {:dropdown, :atom}, + description: "SMTP AUTH enforcement mode", + suggestions: [:if_available, :always, :never] }, %{ group: {:subgroup, Swoosh.Adapters.SMTP}, key: :retries, type: :integer, - description: "`Swoosh.Adapters.SMTP` adapter specific setting", - suggestions: [5] - }, - %{ - group: {:subgroup, Swoosh.Adapters.SMTP}, - key: :no_mx_lookups, - label: "No MX lookups", - type: :boolean, - description: "`Swoosh.Adapters.SMTP` adapter specific setting" + description: "SMTP temporary (4xx) error retries", + suggestions: [1] }, %{ group: {:subgroup, Swoosh.Adapters.Sendgrid}, key: :api_key, - label: "API key", + label: "SendGrid API Key", type: :string, - description: "`Swoosh.Adapters.Sendgrid` adapter specific setting", - suggestions: ["my-api-key"] + suggestions: ["YOUR_API_KEY"] }, %{ group: {:subgroup, Swoosh.Adapters.Sendmail}, key: :cmd_path, type: :string, - description: "`Swoosh.Adapters.Sendmail` adapter specific setting", suggestions: ["/usr/bin/sendmail"] }, %{ group: {:subgroup, Swoosh.Adapters.Sendmail}, key: :cmd_args, type: :string, - description: "`Swoosh.Adapters.Sendmail` adapter specific setting", suggestions: ["-N delay,failure,success"] }, %{ group: {:subgroup, Swoosh.Adapters.Sendmail}, key: :qmail, - type: :boolean, - description: "`Swoosh.Adapters.Sendmail` adapter specific setting" + label: "Qmail compat mode", + type: :boolean }, %{ group: {:subgroup, Swoosh.Adapters.Mandrill}, key: :api_key, - label: "API key", + label: "Mandrill API Key", type: :string, - description: "`Swoosh.Adapters.Mandrill` adapter specific setting", - suggestions: ["my-api-key"] + suggestions: ["YOUR_API_KEY"] }, %{ group: {:subgroup, Swoosh.Adapters.Mailgun}, key: :api_key, - label: "API key", + label: "Mailgun API Key", type: :string, - description: "`Swoosh.Adapters.Mailgun` adapter specific setting", - suggestions: ["my-api-key"] + suggestions: ["YOUR_API_KEY"] }, %{ group: {:subgroup, Swoosh.Adapters.Mailgun}, key: :domain, type: :string, - description: "`Swoosh.Adapters.Mailgun` adapter specific setting", - suggestions: ["pleroma.com"] + suggestions: ["YOUR_DOMAIN_NAME"] }, %{ group: {:subgroup, Swoosh.Adapters.Mailjet}, key: :api_key, - label: "API key", + label: "MailJet Public API Key", type: :string, - description: "`Swoosh.Adapters.Mailjet` adapter specific setting", - suggestions: ["my-api-key"] + suggestions: ["MJ_APIKEY_PUBLIC"] }, %{ group: {:subgroup, Swoosh.Adapters.Mailjet}, key: :secret, + label: "MailJet Private API Key", type: :string, - description: "`Swoosh.Adapters.Mailjet` adapter specific setting", - suggestions: ["my-secret-key"] + suggestions: ["MJ_APIKEY_PRIVATE"] }, %{ group: {:subgroup, Swoosh.Adapters.Postmark}, key: :api_key, - label: "API key", + label: "Postmark API Key", type: :string, - description: "`Swoosh.Adapters.Postmark` adapter specific setting", - suggestions: ["my-api-key"] + suggestions: ["X-Postmark-Server-Token"] }, %{ group: {:subgroup, Swoosh.Adapters.SparkPost}, key: :api_key, - label: "API key", + label: "SparkPost API key", type: :string, - description: "`Swoosh.Adapters.SparkPost` adapter specific setting", - suggestions: ["my-api-key"] + suggestions: ["YOUR_API_KEY"] }, %{ group: {:subgroup, Swoosh.Adapters.SparkPost}, key: :endpoint, type: :string, - description: "`Swoosh.Adapters.SparkPost` adapter specific setting", suggestions: ["https://api.sparkpost.com/api/v1"] }, - %{ - group: {:subgroup, Swoosh.Adapters.AmazonSES}, - key: :region, - type: :string, - description: "`Swoosh.Adapters.AmazonSES` adapter specific setting", - suggestions: ["us-east-1", "us-east-2"] - }, %{ group: {:subgroup, Swoosh.Adapters.AmazonSES}, key: :access_key, + label: "AWS Access Key", type: :string, - description: "`Swoosh.Adapters.AmazonSES` adapter specific setting", - suggestions: ["aws-access-key"] + suggestions: ["AWS_ACCESS_KEY"] }, %{ group: {:subgroup, Swoosh.Adapters.AmazonSES}, key: :secret, + label: "AWS Secret Key", type: :string, - description: "`Swoosh.Adapters.AmazonSES` adapter specific setting", - suggestions: ["aws-secret-key"] + suggestions: ["AWS_SECRET_KEY"] + }, + %{ + group: {:subgroup, Swoosh.Adapters.AmazonSES}, + key: :region, + label: "AWS Region", + type: :string, + suggestions: ["us-east-1", "us-east-2"] }, %{ group: {:subgroup, Swoosh.Adapters.Dyn}, key: :api_key, - label: "API key", + label: "Dyn API Key", type: :string, - description: "`Swoosh.Adapters.Dyn` adapter specific setting", - suggestions: ["my-api-key"] - }, - %{ - group: {:subgroup, Swoosh.Adapters.SocketLabs}, - key: :server_id, - type: :string, - description: "`Swoosh.Adapters.SocketLabs` adapter specific setting" + suggestions: ["apikey"] }, %{ group: {:subgroup, Swoosh.Adapters.SocketLabs}, key: :api_key, - label: "API key", + label: "SocketLabs API Key", type: :string, - description: "`Swoosh.Adapters.SocketLabs` adapter specific setting" + suggestions: ["INJECTION_API_KEY"] + }, + %{ + group: {:subgroup, Swoosh.Adapters.SocketLabs}, + key: :server_id, + label: "Server ID", + type: :string, + suggestions: ["SERVER_ID"] }, %{ group: {:subgroup, Swoosh.Adapters.Gmail}, key: :access_token, + label: "GMail API Access Token", type: :string, - description: "`Swoosh.Adapters.Gmail` adapter specific setting" - } - ] - }, - %{ - group: :swoosh, - type: :group, - description: "`Swoosh.Adapters.Local` adapter specific settings", - children: [ - %{ - group: {:subgroup, Swoosh.Adapters.Local}, - key: :serve_mailbox, - type: :boolean, - description: "Run the preview server together as part of your app" - }, - %{ - group: {:subgroup, Swoosh.Adapters.Local}, - key: :preview_port, - type: :integer, - description: "The preview server port", - suggestions: [4001] + suggestions: ["GMAIL_API_ACCESS_TOKEN"] } ] }, @@ -1559,7 +1523,8 @@ %{ key: :max_body_length, type: :integer, - description: "Maximum file size allowed through the Pleroma MediaProxy cache." + description: + "Maximum file size (in bytes) allowed through the Pleroma MediaProxy cache." }, %{ key: :max_read_duration, @@ -1609,7 +1574,7 @@ key: :min_content_length, type: :integer, description: - "Min content length to perform preview, in bytes. If greater than 0, media smaller in size will be served as is, without thumbnailing." + "Min content length (in bytes) to perform preview. Media smaller in size will be served without thumbnailing." } ] }, @@ -1657,6 +1622,7 @@ }, %{ key: :url_format, + label: "URL Format", type: :string, description: "Optional URL format preprocessing. Only required for Apache's htcacheclean.", @@ -2902,7 +2868,7 @@ type: :integer, description: "Activity pub routes (except question activities). Default: `nil` (no expiration).", - suggestions: [30_000, nil] + suggestions: [nil] }, %{ key: :activity_pub_question, @@ -3340,9 +3306,9 @@ }, %{ key: :ip_whitelist, + label: "IP Whitelist", type: [{:list, :string}, {:list, :charlist}, {:list, :tuple}], - description: - "[Pleroma extension] If non-empty, restricts access to app metrics endpoint to specified IP addresses." + description: "Restrict access of app metrics endpoint to the specified IP addresses." }, %{ key: :auth, diff --git a/config/emoji.txt b/config/emoji.txt index a2c5add2e..52b714ee5 100644 --- a/config/emoji.txt +++ b/config/emoji.txt @@ -1,4 +1,3 @@ firefox, /emoji/Firefox.gif, Gif,Fun blank, /emoji/blank.png, Fun dinosaur, /emoji/dino walking.gif, Gif -external_emoji, https://example.com/emoji.png diff --git a/docs/administration/CLI_tasks/database.md b/docs/administration/CLI_tasks/database.md index 6dca83167..c53c49921 100644 --- a/docs/administration/CLI_tasks/database.md +++ b/docs/administration/CLI_tasks/database.md @@ -141,3 +141,21 @@ but should only be run if necessary. **It is safe to cancel this.** ```sh mix pleroma.database ensure_expiration ``` + +## Change Text Search Configuration + +Change `default_text_search_config` for database and (if necessary) text_search_config used in index, then rebuild index (it may take time). + +=== "OTP" + + ```sh + ./bin/pleroma_ctl database set_text_search_config english + ``` + +=== "From Source" + + ```sh + mix pleroma.database set_text_search_config english + ``` + +See [PostgreSQL documentation](https://www.postgresql.org/docs/current/textsearch-configuration.html) and `docs/configuration/howto_search_cjk.md` for more detail. diff --git a/docs/configuration/howto_search_cjk.md b/docs/configuration/howto_search_cjk.md new file mode 100644 index 000000000..d3ce28077 --- /dev/null +++ b/docs/configuration/howto_search_cjk.md @@ -0,0 +1,42 @@ +# How to enable text search for Chinese, Japanese and Korean + +Pleroma's full text search feature is powered by PostgreSQL's native [text search](https://www.postgresql.org/docs/current/textsearch.html), it works well out of box for most of languages, but needs extra configurations for some asian languages like Chinese, Japanese and Korean (CJK). + + +## Setup and test the new search config + +In most cases, you would need an extension installed to support parsing CJK text. Here are a few extension you may choose from, or you are more than welcome to share additional ones you found working for you with the rest of Pleroma community. + + * [a generic n-gram parser](https://github.com/huangjimmy/pg_cjk_parser) supports Simplifed/Traditional Chinese, Japanese, and Korean + * [a Korean parser](https://github.com/i0seph/textsearch_ko) based on mecab + * [a Japanese parser](https://www.amris.co.jp/tsja/index.html) based on mecab + * [zhparser](https://github.com/amutu/zhparser/) is a PostgreSQL extension base on the Simple Chinese Word Segmentation(SCWS) + * [another Chinese parser](https://github.com/jaiminpan/pg_jieba) based on Jieba Chinese Word Segmentation + +Once you have the new search config , make sure you test it with the `pleroma` user in PostgreSQL (change `YOUR.CONFIG` to your real configuration name) +``` +SELECT ts_debug('YOUR.CONFIG', '安装和配置Nginx, ElixirとErlangをインストールします'); +``` +Check output of the query, and see if it matches your expectation. + + +## Update text search config and index in database + +=== "OTP" + + ```sh + ./bin/pleroma_ctl database set_text_search_config YOUR.CONFIG + ``` + +=== "From Source" + + ```sh + mix pleroma.database set_text_search_config YOUR.CONFIG + ``` + +Note: index update may take a while. + +## Restart database connection +Since some changes above will only apply with a new database connection, you will have to restart either Pleroma or PostgreSQL process, or use `pg_terminate_backend` SQL command without restarting either. + +Now the search results of statuses should be much more friendly for your language of choice, the results for searching users and tags were not changed, as the default parsing/matching should work for most cases. diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md index 5253dc668..04a181401 100644 --- a/docs/development/API/admin_api.md +++ b/docs/development/API/admin_api.md @@ -287,7 +287,18 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret - *optional* `with_reblogs`: `true`/`false` – allows to see reblogs (default is false) - Response: - On failure: `Not found` - - On success: JSON array of user's latest statuses + - On success: JSON, where: + - `total`: total count of the statuses for the user + - `activities`: list of the statuses for the user + +```json +{ + "total" : 1, + "activities": [ + // activities list + ] +} +``` ## `GET /api/pleroma/admin/instances/:instance/statuses` @@ -300,7 +311,18 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret - *optional* `with_reblogs`: `true`/`false` – allows to see reblogs (default is false) - Response: - On failure: `Not found` - - On success: JSON array of instance's latest statuses + - On success: JSON, where: + - `total`: total count of the statuses for the instance + - `activities`: list of the statuses for the instance + +```json +{ + "total" : 1, + "activities": [ + // activities list + ] +} +``` ## `GET /api/pleroma/admin/statuses` diff --git a/installation/pleroma.vcl b/installation/pleroma.vcl index 13dad784c..4752510ea 100644 --- a/installation/pleroma.vcl +++ b/installation/pleroma.vcl @@ -59,6 +59,13 @@ sub vcl_backend_response { set beresp.http.CR = beresp.http.content-range; } + # Bypass cache for large files + # 50000000 ~ 50MB + if (std.integer(beresp.http.content-length, 0) > 50000000) { + set beresp.uncacheable = true; + return(deliver); + } + # Don't cache objects that require authentication if (beresp.http.Authorization && !beresp.http.Cache-Control ~ "public") { set beresp.uncacheable = true; diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index 7c4f54141..2136ddb02 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -171,6 +171,53 @@ def run(["ensure_expiration"]) do |> Stream.run() end + def run(["set_text_search_config", tsconfig]) do + start_pleroma() + %{rows: [[tsc]]} = Ecto.Adapters.SQL.query!(Pleroma.Repo, "SHOW default_text_search_config;") + shell_info("Current default_text_search_config: #{tsc}") + + %{rows: [[db]]} = Ecto.Adapters.SQL.query!(Pleroma.Repo, "SELECT current_database();") + shell_info("Update default_text_search_config: #{tsconfig}") + + %{messages: msg} = + Ecto.Adapters.SQL.query!( + Pleroma.Repo, + "ALTER DATABASE #{db} SET default_text_search_config = '#{tsconfig}';" + ) + + # non-exist config will not raise excpetion but only give >0 messages + if length(msg) > 0 do + shell_info("Error: #{inspect(msg, pretty: true)}") + else + rum_enabled = Pleroma.Config.get([:database, :rum_enabled]) + shell_info("Recreate index, RUM: #{rum_enabled}") + + # Note SQL below needs to be kept up-to-date with latest GIN or RUM index definition in future + if rum_enabled do + Ecto.Adapters.SQL.query!( + Pleroma.Repo, + "CREATE OR REPLACE FUNCTION objects_fts_update() RETURNS trigger AS $$ BEGIN + new.fts_content := to_tsvector(new.data->>'content'); + RETURN new; + END + $$ LANGUAGE plpgsql" + ) + + shell_info("Refresh RUM index") + Ecto.Adapters.SQL.query!(Pleroma.Repo, "UPDATE objects SET updated_at = NOW();") + else + Ecto.Adapters.SQL.query!(Pleroma.Repo, "DROP INDEX IF EXISTS objects_fts;") + + Ecto.Adapters.SQL.query!( + Pleroma.Repo, + "CREATE INDEX objects_fts ON objects USING gin(to_tsvector('#{tsconfig}', data->>'content')); " + ) + end + + shell_info('Done.') + 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 diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index e05c207e5..4ce8c9b05 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -38,7 +38,7 @@ def run(["resend_confirmation_emails"]) do invisible: false }) |> Pleroma.Repo.chunk_stream(500) - |> Stream.each(&Pleroma.User.try_send_confirmation_email(&1)) + |> Stream.each(&Pleroma.User.maybe_send_confirmation_email(&1)) |> Stream.run() end end diff --git a/lib/mix/tasks/pleroma/openapi_spec.ex b/lib/mix/tasks/pleroma/openapi_spec.ex new file mode 100644 index 000000000..8f719c58b --- /dev/null +++ b/lib/mix/tasks/pleroma/openapi_spec.ex @@ -0,0 +1,8 @@ +defmodule Mix.Tasks.Pleroma.OpenapiSpec do + def run([path]) do + # Load Pleroma application to get version info + Application.load(:pleroma) + spec = Pleroma.Web.ApiSpec.spec(server_specific: false) |> Jason.encode!() + File.write(path, spec) + end +end diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index 52e7c048d..ed898ba4f 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -64,7 +64,7 @@ defp query_with(q, :gin, search_query, :plain) do from([a, o] in q, where: fragment( - "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", + "to_tsvector(?->>'content') @@ plainto_tsquery(?)", o.data, ^search_query ) @@ -75,7 +75,7 @@ defp query_with(q, :gin, search_query, :websearch) do from([a, o] in q, where: fragment( - "to_tsvector('english', ?->>'content') @@ websearch_to_tsquery('english', ?)", + "to_tsvector(?->>'content') @@ websearch_to_tsquery(?)", o.data, ^search_query ) @@ -86,7 +86,7 @@ defp query_with(q, :rum, search_query, :plain) do from([a, o] in q, where: fragment( - "? @@ plainto_tsquery('english', ?)", + "? @@ plainto_tsquery(?)", o.fts_content, ^search_query ), @@ -98,7 +98,7 @@ defp query_with(q, :rum, search_query, :websearch) do from([a, o] in q, where: fragment( - "? @@ websearch_to_tsquery('english', ?)", + "? @@ websearch_to_tsquery(?)", o.fts_content, ^search_query ), diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index d39451a9d..9ca048a5f 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Application do @name Mix.Project.config()[:name] @version Mix.Project.config()[:version] @repository Mix.Project.config()[:source_url] - @env Mix.env() + @mix_env Mix.env() def name, do: @name def version, do: @version @@ -92,15 +92,15 @@ def start(_type, _args) do Pleroma.Web.Plugs.RateLimiter.Supervisor ] ++ cachex_children() ++ - http_children(adapter, @env) ++ + http_children(adapter, @mix_env) ++ [ Pleroma.Stats, Pleroma.JobQueueMonitor, {Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]}, {Oban, Config.get(Oban)} ] ++ - task_children(@env) ++ - dont_run_in_test(@env) ++ + task_children(@mix_env) ++ + dont_run_in_test(@mix_env) ++ chat_child(chat_enabled?()) ++ [ Pleroma.Web.Endpoint, @@ -146,7 +146,7 @@ def load_custom_modules do raise "Invalid custom modules" {:ok, modules, _warnings} -> - if @env != :test do + if @mix_env != :test do Enum.each(modules, fn mod -> Logger.info("Custom module loaded: #{inspect(mod)}") end) diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index dbd89f1c7..52f3d419d 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -81,9 +81,9 @@ def account_confirmation_email(user) do ) html_body = """ -

Welcome to #{instance_name()}!

+

Thank you for registering on #{instance_name()}

Email confirmation is required to activate the account.

-

Click the following link to proceed: activate your account.

+

Please click the following link to activate your account.

""" new() @@ -106,6 +106,20 @@ def approval_pending_email(user) do |> html_body(html_body) end + def successful_registration_email(user) do + html_body = """ +

Hello @#{user.nickname},

+

Your account at #{instance_name()} has been registered successfully.

+

No further action is required to activate your account.

+ """ + + new() + |> to(recipient(user)) + |> from(sender()) + |> subject("Account registered on #{instance_name()}") + |> html_body(html_body) + end + @doc """ Email used in digest email notifications Includes Mentions and New Followers data diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index 028cf5ea8..67acd7069 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -15,6 +15,8 @@ defmodule Pleroma.Emoji.Loader do require Logger + @mix_env Mix.env() + @type pattern :: Regex.t() | module() | String.t() @type patterns :: pattern() | [pattern()] @type group_patterns :: keyword(patterns()) @@ -77,10 +79,19 @@ def load do # it should run even if there are no emoji packs shortcode_globs = Config.get([:emoji, :shortcode_globs], []) + # for testing emoji.txt entries we do not want exposed in normal operation + test_emoji = + if @mix_env == :test do + load_from_file("test/config/emoji.txt", emoji_groups) + else + [] + end + emojis_txt = (load_from_file("config/emoji.txt", emoji_groups) ++ load_from_file("config/custom_emoji.txt", emoji_groups) ++ - load_from_globs(shortcode_globs, emoji_groups)) + load_from_globs(shortcode_globs, emoji_groups) ++ + test_emoji) |> Enum.reject(fn value -> value == nil end) Enum.map(emojis ++ emojis_txt, &prepare_emoji/1) diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex index fc531f7fc..82b9caf9b 100644 --- a/lib/pleroma/filter.ex +++ b/lib/pleroma/filter.ex @@ -11,6 +11,9 @@ defmodule Pleroma.Filter do alias Pleroma.Repo alias Pleroma.User + @type t() :: %__MODULE__{} + @type format() :: :postgres | :re + schema "filters" do belongs_to(:user, User, type: FlakeId.Ecto.CompatType) field(:filter_id, :integer) @@ -18,15 +21,16 @@ defmodule Pleroma.Filter do field(:whole_word, :boolean, default: true) field(:phrase, :string) field(:context, {:array, :string}) - field(:expires_at, :utc_datetime) + field(:expires_at, :naive_datetime) timestamps() end + @spec get(integer() | String.t(), User.t()) :: t() | nil def get(id, %{id: user_id} = _user) do query = from( - f in Pleroma.Filter, + f in __MODULE__, where: f.filter_id == ^id, where: f.user_id == ^user_id ) @@ -34,14 +38,17 @@ def get(id, %{id: user_id} = _user) do Repo.one(query) end + @spec get_active(Ecto.Query.t() | module()) :: Ecto.Query.t() def get_active(query) do from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now()) end + @spec get_irreversible(Ecto.Query.t()) :: Ecto.Query.t() def get_irreversible(query) do from(f in query, where: f.hide) end + @spec get_filters(Ecto.Query.t() | module(), User.t()) :: [t()] def get_filters(query \\ __MODULE__, %User{id: user_id}) do query = from( @@ -53,7 +60,32 @@ def get_filters(query \\ __MODULE__, %User{id: user_id}) do Repo.all(query) end - def create(%Pleroma.Filter{user_id: user_id, filter_id: nil} = filter) do + @spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def create(attrs \\ %{}) do + Repo.transaction(fn -> create_with_expiration(attrs) end) + end + + defp create_with_expiration(attrs) do + with {:ok, filter} <- do_create(attrs), + {:ok, _} <- maybe_add_expiration_job(filter) do + filter + else + {:error, error} -> Repo.rollback(error) + end + end + + defp do_create(attrs) do + %__MODULE__{} + |> cast(attrs, [:phrase, :context, :hide, :expires_at, :whole_word, :user_id, :filter_id]) + |> maybe_add_filter_id() + |> validate_required([:phrase, :context, :user_id, :filter_id]) + |> maybe_add_expires_at(attrs) + |> Repo.insert() + end + + defp maybe_add_filter_id(%{changes: %{filter_id: _}} = changeset), do: changeset + + defp maybe_add_filter_id(%{changes: %{user_id: user_id}} = changeset) do # If filter_id wasn't given, use the max filter_id for this user plus 1. # XXX This could result in a race condition if a user tries to add two # different filters for their account from two different clients at the @@ -61,7 +93,7 @@ def create(%Pleroma.Filter{user_id: user_id, filter_id: nil} = filter) do max_id_query = from( - f in Pleroma.Filter, + f in __MODULE__, where: f.user_id == ^user_id, select: max(f.filter_id) ) @@ -76,34 +108,92 @@ def create(%Pleroma.Filter{user_id: user_id, filter_id: nil} = filter) do max_id + 1 end - filter - |> Map.put(:filter_id, filter_id) - |> Repo.insert() + change(changeset, filter_id: filter_id) end - def create(%Pleroma.Filter{} = filter) do - Repo.insert(filter) + # don't override expires_at, if passed expires_at and expires_in + defp maybe_add_expires_at(%{changes: %{expires_at: %NaiveDateTime{} = _}} = changeset, _) do + changeset end - def delete(%Pleroma.Filter{id: filter_key} = filter) when is_number(filter_key) do - Repo.delete(filter) + defp maybe_add_expires_at(changeset, %{expires_in: expires_in}) + when is_integer(expires_in) and expires_in > 0 do + expires_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(expires_in) + |> NaiveDateTime.truncate(:second) + + change(changeset, expires_at: expires_at) end - def delete(%Pleroma.Filter{id: filter_key} = filter) when is_nil(filter_key) do - %Pleroma.Filter{id: id} = get(filter.filter_id, %{id: filter.user_id}) - - filter - |> Map.put(:id, id) - |> Repo.delete() + defp maybe_add_expires_at(changeset, %{expires_in: nil}) do + change(changeset, expires_at: nil) end - def update(%Pleroma.Filter{} = filter, params) do + defp maybe_add_expires_at(changeset, _), do: changeset + + defp maybe_add_expiration_job(%{expires_at: %NaiveDateTime{} = expires_at} = filter) do + Pleroma.Workers.PurgeExpiredFilter.enqueue(%{ + filter_id: filter.id, + expires_at: DateTime.from_naive!(expires_at, "Etc/UTC") + }) + end + + defp maybe_add_expiration_job(_), do: {:ok, nil} + + @spec delete(t()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def delete(%__MODULE__{} = filter) do + Repo.transaction(fn -> delete_with_expiration(filter) end) + end + + defp delete_with_expiration(filter) do + with {:ok, _} <- maybe_delete_old_expiration_job(filter, nil), + {:ok, filter} <- Repo.delete(filter) do + filter + else + {:error, error} -> Repo.rollback(error) + end + end + + @spec update(t(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def update(%__MODULE__{} = filter, params) do + Repo.transaction(fn -> update_with_expiration(filter, params) end) + end + + defp update_with_expiration(filter, params) do + with {:ok, updated} <- do_update(filter, params), + {:ok, _} <- maybe_delete_old_expiration_job(filter, updated), + {:ok, _} <- + maybe_add_expiration_job(updated) do + updated + else + {:error, error} -> Repo.rollback(error) + end + end + + defp do_update(filter, params) do filter |> cast(params, [:phrase, :context, :hide, :expires_at, :whole_word]) |> validate_required([:phrase, :context]) + |> maybe_add_expires_at(params) |> Repo.update() end + defp maybe_delete_old_expiration_job(%{expires_at: nil}, _), do: {:ok, nil} + + defp maybe_delete_old_expiration_job(%{expires_at: expires_at}, %{expires_at: expires_at}) do + {:ok, nil} + end + + defp maybe_delete_old_expiration_job(%{id: id}, _) do + with %Oban.Job{} = job <- Pleroma.Workers.PurgeExpiredFilter.get_expiration(id) do + Repo.delete(job) + else + nil -> {:ok, nil} + end + end + + @spec compose_regex(User.t() | [t()], format()) :: String.t() | Regex.t() | nil def compose_regex(user_or_filters, format \\ :postgres) def compose_regex(%User{} = user, format) do diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 55b513212..1970fbf65 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -507,8 +507,8 @@ def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => obje [object_id] end - def get_potential_receiver_ap_ids(%{data: %{"type" => "Flag"}}) do - User.all_superusers() |> Enum.map(fn user -> user.ap_id end) + def get_potential_receiver_ap_ids(%{data: %{"type" => "Flag", "actor" => actor}}) do + (User.all_superusers() |> Enum.map(fn user -> user.ap_id end)) -- [actor] end def get_potential_receiver_ap_ids(activity) do diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex index af99d001c..0be878ca2 100644 --- a/lib/pleroma/uploaders/uploader.ex +++ b/lib/pleroma/uploaders/uploader.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Uploaders.Uploader do import Pleroma.Web.Gettext + @mix_env Mix.env() + @moduledoc """ Defines the contract to put and get an uploaded file to any backend. """ @@ -74,7 +76,7 @@ defp handle_callback(uploader, upload) do end defp callback_timeout do - case Mix.env() do + case @mix_env do :test -> 1_000 _ -> 30_000 end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b69709db4..51f5bc8ea 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -798,7 +798,7 @@ def register(%Ecto.Changeset{} = changeset) do end def post_register_action(%User{is_confirmed: false} = user) do - with {:ok, _} <- try_send_confirmation_email(user) do + with {:ok, _} <- maybe_send_confirmation_email(user) do {:ok, user} end end @@ -814,9 +814,10 @@ def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do with {:ok, user} <- autofollow_users(user), {:ok, _} <- autofollowing_users(user), {:ok, user} <- set_cache(user), - {:ok, _} <- send_welcome_email(user), - {:ok, _} <- send_welcome_message(user), - {:ok, _} <- send_welcome_chat_message(user) do + {:ok, _} <- maybe_send_registration_email(user), + {:ok, _} <- maybe_send_welcome_email(user), + {:ok, _} <- maybe_send_welcome_message(user), + {:ok, _} <- maybe_send_welcome_chat_message(user) do {:ok, user} end end @@ -841,7 +842,7 @@ defp send_admin_approval_emails(user) do {:ok, :enqueued} end - def send_welcome_message(user) do + defp maybe_send_welcome_message(user) do if User.WelcomeMessage.enabled?() do User.WelcomeMessage.post_message(user) {:ok, :enqueued} @@ -850,7 +851,7 @@ def send_welcome_message(user) do end end - def send_welcome_chat_message(user) do + defp maybe_send_welcome_chat_message(user) do if User.WelcomeChatMessage.enabled?() do User.WelcomeChatMessage.post_message(user) {:ok, :enqueued} @@ -859,7 +860,7 @@ def send_welcome_chat_message(user) do end end - def send_welcome_email(%User{email: email} = user) when is_binary(email) do + defp maybe_send_welcome_email(%User{email: email} = user) when is_binary(email) do if User.WelcomeEmail.enabled?() do User.WelcomeEmail.send_email(user) {:ok, :enqueued} @@ -868,10 +869,10 @@ def send_welcome_email(%User{email: email} = user) when is_binary(email) do end end - def send_welcome_email(_), do: {:ok, :noop} + defp maybe_send_welcome_email(_), do: {:ok, :noop} - @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop} - def try_send_confirmation_email(%User{is_confirmed: false, email: email} = user) + @spec maybe_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop} + def maybe_send_confirmation_email(%User{is_confirmed: false, email: email} = user) when is_binary(email) do if Config.get([:instance, :account_activation_required]) do send_confirmation_email(user) @@ -881,7 +882,7 @@ def try_send_confirmation_email(%User{is_confirmed: false, email: email} = user) end end - def try_send_confirmation_email(_), do: {:ok, :noop} + def maybe_send_confirmation_email(_), do: {:ok, :noop} @spec send_confirmation_email(Uset.t()) :: User.t() def send_confirmation_email(%User{} = user) do @@ -892,6 +893,24 @@ def send_confirmation_email(%User{} = user) do user end + @spec maybe_send_registration_email(User.t()) :: {:ok, :enqueued | :noop} + defp maybe_send_registration_email(%User{email: email} = user) when is_binary(email) do + with false <- User.WelcomeEmail.enabled?(), + false <- Config.get([:instance, :account_activation_required], false), + false <- Config.get([:instance, :account_approval_required], false) do + user + |> Pleroma.Emails.UserEmail.successful_registration_email() + |> Pleroma.Emails.Mailer.deliver_async() + + {:ok, :enqueued} + else + _ -> + {:ok, :noop} + end + end + + defp maybe_send_registration_email(_), do: {:ok, :noop} + def needs_update?(%User{local: true}), do: false def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 7ac18e5c5..9623e635a 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -377,6 +377,7 @@ defp do_flag( :ok <- maybe_federate(stripped_activity) do User.all_superusers() + |> Enum.filter(fn user -> user.ap_id != actor end) |> Enum.filter(fn user -> not is_nil(user.email) end) |> Enum.each(fn superuser -> superuser @@ -591,7 +592,21 @@ def fetch_user_abstract_activities(user, reading_user, params \\ %{}) do |> Enum.reverse() end - def fetch_user_activities(user, reading_user, params \\ %{}) do + def fetch_user_activities(user, reading_user, params \\ %{}) + + def fetch_user_activities(user, reading_user, %{total: true} = params) do + result = fetch_activities_for_user(user, reading_user, params) + + Keyword.put(result, :items, Enum.reverse(result[:items])) + end + + def fetch_user_activities(user, reading_user, params) do + user + |> fetch_activities_for_user(reading_user, params) + |> Enum.reverse() + end + + defp fetch_activities_for_user(user, reading_user, params) do params = params |> Map.put(:type, ["Create", "Announce"]) @@ -616,10 +631,20 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do } |> user_activities_recipients() |> fetch_activities(params, pagination_type) - |> Enum.reverse() + end + + def fetch_statuses(reading_user, %{total: true} = params) do + result = fetch_activities_for_reading_user(reading_user, params) + Keyword.put(result, :items, Enum.reverse(result[:items])) end def fetch_statuses(reading_user, params) do + reading_user + |> fetch_activities_for_reading_user(params) + |> Enum.reverse() + end + + defp fetch_activities_for_reading_user(reading_user, params) do params = Map.put(params, :type, ["Create", "Announce"]) %{ @@ -628,7 +653,6 @@ def fetch_statuses(reading_user, params) do } |> user_activities_recipients() |> fetch_activities(params, :offset) - |> Enum.reverse() end defp user_activities_recipients(%{godmode: true}), do: [] diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index eb9e119f7..9d3dcc7f9 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -79,11 +79,11 @@ def user(conn, %{"nickname" => nickname}) do end end - def object(conn, _) do + def object(%{assigns: assigns} = conn, _) do with ap_id <- Endpoint.url() <> conn.request_path, %Object{} = object <- Object.get_cached_by_ap_id(ap_id), - {_, true} <- {:public?, Visibility.is_public?(object)}, - {_, false} <- {:local?, Visibility.is_local_public?(object)} do + user <- Map.get(assigns, :user, nil), + {_, true} <- {:visible?, Visibility.visible_for_user?(object, user)} do conn |> assign(:tracking_fun_data, object.id) |> set_cache_ttl_for(object) @@ -91,11 +91,8 @@ def object(conn, _) do |> put_view(ObjectView) |> render("object.json", object: object) else - {:public?, false} -> - {:error, :not_found} - - {:local?, true} -> - {:error, :not_found} + {:visible?, false} -> {:error, :not_found} + nil -> {:error, :not_found} end end @@ -109,11 +106,12 @@ def track_object_fetch(conn, object_id) do conn end - def activity(conn, _params) do + def activity(%{assigns: assigns} = conn, _) do with ap_id <- Endpoint.url() <> conn.request_path, %Activity{} = activity <- Activity.normalize(ap_id), - {_, true} <- {:public?, Visibility.is_public?(activity)}, - {_, false} <- {:local?, Visibility.is_local_public?(activity)} do + {_, true} <- {:local?, activity.local}, + user <- Map.get(assigns, :user, nil), + {_, true} <- {:visible?, Visibility.visible_for_user?(activity, user)} do conn |> maybe_set_tracking_data(activity) |> set_cache_ttl_for(activity) @@ -121,8 +119,8 @@ def activity(conn, _params) do |> put_view(ObjectView) |> render("object.json", object: activity) else - {:public?, false} -> {:error, :not_found} - {:local?, true} -> {:error, :not_found} + {:visible?, false} -> {:error, :not_found} + {:local?, false} -> {:error, :not_found} nil -> {:error, :not_found} end end diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex new file mode 100644 index 000000000..32bb1b645 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do + @moduledoc "Filter local activities which have no content" + @behaviour Pleroma.Web.ActivityPub.MRF + + alias Pleroma.Web + + @impl true + def filter(%{"actor" => actor} = object) do + with true <- is_local?(actor), + true <- is_note?(object), + false <- has_attachment?(object), + true <- only_mentions?(object) do + {:reject, "[NoEmptyPolicy]"} + else + _ -> + {:ok, object} + end + end + + def filter(object), do: {:ok, object} + + defp is_local?(actor) do + if actor |> String.starts_with?("#{Web.base_url()}") do + true + else + false + end + end + + defp has_attachment?(%{ + "type" => "Create", + "object" => %{"type" => "Note", "attachment" => attachments} + }) + when length(attachments) > 0, + do: true + + defp has_attachment?(_), do: false + + defp only_mentions?(%{"type" => "Create", "object" => %{"type" => "Note", "source" => source}}) do + non_mentions = + source |> String.split() |> Enum.filter(&(not String.starts_with?(&1, "@"))) |> length + + if non_mentions > 0 do + false + else + true + end + end + + defp only_mentions?(_), do: false + + defp is_note?(%{"type" => "Create", "object" => %{"type" => "Note"}}), do: true + defp is_note?(_), do: false + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 6ef59e93f..00234c0b0 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -56,11 +56,10 @@ def is_direct?(activity) do def is_list?(%{data: %{"listMessage" => _}}), do: true def is_list?(_), do: false - @spec visible_for_user?(Activity.t() | nil, User.t() | nil) :: boolean() + @spec visible_for_user?(Object.t() | Activity.t() | nil, User.t() | nil) :: boolean() def visible_for_user?(%Activity{actor: ap_id}, %User{ap_id: ap_id}), do: true - + def visible_for_user?(%Object{data: %{"actor" => ap_id}}, %User{ap_id: ap_id}), do: true def visible_for_user?(nil, _), do: false - def visible_for_user?(%Activity{data: %{"listMessage" => _}}, nil), do: false def visible_for_user?( @@ -73,16 +72,18 @@ def visible_for_user?( |> Pleroma.List.member?(user) end - def visible_for_user?(%Activity{} = activity, nil) do - if restrict_unauthenticated_access?(activity), + def visible_for_user?(%{__struct__: module} = message, nil) + when module in [Activity, Object] do + if restrict_unauthenticated_access?(message), do: false, - else: is_public?(activity) + else: is_public?(message) and not is_local_public?(message) end - def visible_for_user?(%Activity{} = activity, user) do + def visible_for_user?(%{__struct__: module} = message, user) + when module in [Activity, Object] do x = [user.ap_id | User.following(user)] - y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || []) - is_public?(activity) || Enum.any?(x, &(&1 in y)) + y = [message.data["actor"]] ++ message.data["to"] ++ (message.data["cc"] || []) + is_public?(message) || Enum.any?(x, &(&1 in y)) end def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 709c863ec..d581df4a2 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -85,17 +85,18 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true {page, page_size} = page_params(params) - activities = + result = ActivityPub.fetch_statuses(nil, %{ instance: instance, limit: page_size, offset: (page - 1) * page_size, - exclude_reblogs: not with_reblogs + exclude_reblogs: not with_reblogs, + total: true }) conn |> put_view(AdminAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity}) + |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity}) end def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do @@ -105,18 +106,19 @@ def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickna with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do {page, page_size} = page_params(params) - activities = + result = ActivityPub.fetch_user_activities(user, nil, %{ limit: page_size, offset: (page - 1) * page_size, godmode: godmode, exclude_reblogs: not with_reblogs, - pagination_type: :offset + pagination_type: :offset, + total: true }) conn |> put_view(AdminAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity}) + |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity}) else _ -> {:error, :not_found} end @@ -404,7 +406,7 @@ defp configurable_from_database do if Config.get(:configurable_from_database) do :ok else - {:error, "To use this endpoint you need to enable configuration from database."} + {:error, "You must enable configurable_from_database in your config file."} end end diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex index 7872fe2d8..4ebf2a305 100644 --- a/lib/pleroma/web/admin_api/controllers/config_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex @@ -122,7 +122,7 @@ defp configurable_from_database do if Config.get(:configurable_from_database) do :ok else - {:error, "To use this endpoint you need to enable configuration from database."} + {:error, "You must enable configurable_from_database in your config file."} end end diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex index 361fa5b0d..48d639b41 100644 --- a/lib/pleroma/web/admin_api/views/status_view.ex +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -13,6 +13,10 @@ defmodule Pleroma.Web.AdminAPI.StatusView do defdelegate merge_account_views(user), to: AdminAPI.AccountView + def render("index.json", %{total: total} = opts) do + %{total: total, activities: safe_render_many(opts.activities, __MODULE__, "show.json", opts)} + end + def render("index.json", opts) do safe_render_many(opts.activities, __MODULE__, "show.json", opts) end diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 064558597..b16068f7b 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -11,10 +11,10 @@ defmodule Pleroma.Web.ApiSpec do @behaviour OpenApi @impl OpenApi - def spec do + def spec(opts \\ []) do %OpenApi{ servers: - if Phoenix.Endpoint.server?(:pleroma, Endpoint) do + if opts[:server_specific] do [ # Populate the Server info from a phoenix endpoint OpenApiSpex.Server.from_endpoint(Endpoint) @@ -23,9 +23,26 @@ def spec do [] end, info: %OpenApiSpex.Info{ - title: "Pleroma", - description: Application.spec(:pleroma, :description) |> to_string(), - version: Application.spec(:pleroma, :vsn) |> to_string() + title: "Pleroma API", + description: """ + This is documentation for client Pleroma API. Most of the endpoints and entities come + from Mastodon API and have custom extensions on top. + + While this document aims to be a complete guide to the client API Pleroma exposes, + the details are still being worked out. Some endpoints may have incomplete or poorly worded documentation. + You might want to check the following resources if something is not clear: + - [Legacy Pleroma-specific endpoint documentation](https://docs-develop.pleroma.social/backend/development/API/pleroma_api/) + - [Mastodon API documentation](https://docs.joinmastodon.org/client/intro/) + - [Differences in Mastodon API responses from vanilla Mastodon](https://docs-develop.pleroma.social/backend/development/API/differences_in_mastoapi_responses/) + + Please report such occurences on our [issue tracker](https://git.pleroma.social/pleroma/pleroma/-/issues). Feel free to submit API questions or proposals there too! + """, + # Strip environment from the version + version: Application.spec(:pleroma, :vsn) |> to_string() |> String.replace(~r/\+.*$/, ""), + extensions: %{ + # Logo path should be picked so that the path exists both on Pleroma instances and on api.pleroma.social + "x-logo": %{"url" => "/static/logo.svg", "altText" => "Pleroma logo"} + } }, # populate the paths from a phoenix router paths: OpenApiSpex.Paths.from_router(Router), @@ -45,15 +62,73 @@ def spec do authorizationUrl: "/oauth/authorize", tokenUrl: "/oauth/token", scopes: %{ - "read" => "read", - "write" => "write", - "follow" => "follow", - "push" => "push" + # TODO: Document granular scopes + "read" => "Read everything", + "write" => "Write everything", + "follow" => "Manage relationships", + "push" => "Web Push API subscriptions" } } } } } + }, + extensions: %{ + # Redoc-specific extension, every time a new tag is added it should be reflected here, + # otherwise it won't be shown. + "x-tagGroups": [ + %{ + "name" => "Accounts", + "tags" => ["Account actions", "Retrieve account information", "Scrobbles"] + }, + %{ + "name" => "Administration", + "tags" => [ + "Chat administration", + "Emoji packs", + "Frontend managment", + "Instance configuration", + "Instance documents", + "Invites", + "MediaProxy cache", + "OAuth application managment", + "Report managment", + "Relays", + "Status administration" + ] + }, + %{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]}, + %{ + "name" => "Current account", + "tags" => [ + "Account credentials", + "Backups", + "Blocks and mutes", + "Data import", + "Domain blocks", + "Follow requests", + "Mascot", + "Markers", + "Notifications" + ] + }, + %{"name" => "Instance", "tags" => ["Custom emojis"]}, + %{"name" => "Messaging", "tags" => ["Chats", "Conversations"]}, + %{ + "name" => "Statuses", + "tags" => [ + "Emoji reactions", + "Lists", + "Polls", + "Timelines", + "Retrieve status information", + "Scheduled statuses", + "Search", + "Status actions" + ] + }, + %{"name" => "Miscellaneous", "tags" => ["Reports", "Suggestions"]} + ] } } # discover request/response schemas from path specs diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 6babe0b28..6f67339e6 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -63,7 +63,7 @@ def with_relationships_param do :with_relationships, :query, BooleanLike, - "Embed relationships into accounts." + "Embed relationships into accounts. **If this parameter is not set account's `pleroma.relationship` is going to be `null`.**" ) end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 3d451cd5a..54e5ebc76 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -26,7 +26,7 @@ def open_api_operation(action) do @spec create_operation() :: Operation.t() def create_operation do %Operation{ - tags: ["accounts"], + tags: ["Account credentials"], summary: "Register an account", description: "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.", @@ -43,7 +43,7 @@ def create_operation do def verify_credentials_operation do %Operation{ - tags: ["accounts"], + tags: ["Account credentials"], description: "Test to make sure that the user token works.", summary: "Verify account credentials", operationId: "AccountController.verify_credentials", @@ -56,7 +56,7 @@ def verify_credentials_operation do def update_credentials_operation do %Operation{ - tags: ["accounts"], + tags: ["Account credentials"], summary: "Update account credentials", description: "Update the user's display and preferences.", operationId: "AccountController.update_credentials", @@ -71,8 +71,8 @@ def update_credentials_operation do def relationships_operation do %Operation{ - tags: ["accounts"], - summary: "Check relationships to other accounts", + tags: ["Retrieve account information"], + summary: "Relationship with current account", operationId: "AccountController.relationships", description: "Find out whether a given account is followed, blocked, muted, etc.", security: [%{"oAuth" => ["read:follows"]}], @@ -95,7 +95,7 @@ def relationships_operation do def show_operation do %Operation{ - tags: ["accounts"], + tags: ["Retrieve account information"], summary: "Account", operationId: "AccountController.show", description: "View information about a profile.", @@ -113,8 +113,8 @@ def show_operation do def statuses_operation do %Operation{ - tags: ["accounts"], summary: "Statuses", + tags: ["Retrieve account information"], operationId: "AccountController.statuses", description: "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)", @@ -160,7 +160,7 @@ def statuses_operation do def followers_operation do %Operation{ - tags: ["accounts"], + tags: ["Retrieve account information"], summary: "Followers", operationId: "AccountController.followers", security: [%{"oAuth" => ["read:accounts"]}], @@ -179,7 +179,7 @@ def followers_operation do def following_operation do %Operation{ - tags: ["accounts"], + tags: ["Retrieve account information"], summary: "Following", operationId: "AccountController.following", security: [%{"oAuth" => ["read:accounts"]}], @@ -196,7 +196,7 @@ def following_operation do def lists_operation do %Operation{ - tags: ["accounts"], + tags: ["Retrieve account information"], summary: "Lists containing this account", operationId: "AccountController.lists", security: [%{"oAuth" => ["read:lists"]}], @@ -208,7 +208,7 @@ def lists_operation do def follow_operation do %Operation{ - tags: ["accounts"], + tags: ["Account actions"], summary: "Follow", operationId: "AccountController.follow", security: [%{"oAuth" => ["follow", "write:follows"]}], @@ -241,7 +241,7 @@ def follow_operation do def unfollow_operation do %Operation{ - tags: ["accounts"], + tags: ["Account actions"], summary: "Unfollow", operationId: "AccountController.unfollow", security: [%{"oAuth" => ["follow", "write:follows"]}], @@ -257,7 +257,7 @@ def unfollow_operation do def mute_operation do %Operation{ - tags: ["accounts"], + tags: ["Account actions"], summary: "Mute", operationId: "AccountController.mute", security: [%{"oAuth" => ["follow", "write:mutes"]}], @@ -287,7 +287,7 @@ def mute_operation do def unmute_operation do %Operation{ - tags: ["accounts"], + tags: ["Account actions"], summary: "Unmute", operationId: "AccountController.unmute", security: [%{"oAuth" => ["follow", "write:mutes"]}], @@ -301,7 +301,7 @@ def unmute_operation do def block_operation do %Operation{ - tags: ["accounts"], + tags: ["Account actions"], summary: "Block", operationId: "AccountController.block", security: [%{"oAuth" => ["follow", "write:blocks"]}], @@ -316,7 +316,7 @@ def block_operation do def unblock_operation do %Operation{ - tags: ["accounts"], + tags: ["Account actions"], summary: "Unblock", operationId: "AccountController.unblock", security: [%{"oAuth" => ["follow", "write:blocks"]}], @@ -330,7 +330,7 @@ def unblock_operation do def follow_by_uri_operation do %Operation{ - tags: ["accounts"], + tags: ["Account actions"], summary: "Follow by URI", operationId: "AccountController.follows", security: [%{"oAuth" => ["follow", "write:follows"]}], @@ -345,8 +345,8 @@ def follow_by_uri_operation do def mutes_operation do %Operation{ - tags: ["accounts"], - summary: "Muted accounts", + tags: ["Blocks and mutes"], + summary: "Retrieve list of mutes", operationId: "AccountController.mutes", description: "Accounts the user has muted.", security: [%{"oAuth" => ["follow", "read:mutes"]}], @@ -359,8 +359,8 @@ def mutes_operation do def blocks_operation do %Operation{ - tags: ["accounts"], - summary: "Blocked users", + tags: ["Blocks and mutes"], + summary: "Retrieve list of blocks", operationId: "AccountController.blocks", description: "View your blocks. See also accounts/:id/{block,unblock}", security: [%{"oAuth" => ["read:blocks"]}], @@ -373,7 +373,7 @@ def blocks_operation do def endorsements_operation do %Operation{ - tags: ["accounts"], + tags: ["Retrieve account information"], summary: "Endorsements", operationId: "AccountController.endorsements", description: "Not implemented", @@ -386,7 +386,7 @@ def endorsements_operation do def identity_proofs_operation do %Operation{ - tags: ["accounts"], + tags: ["Retrieve account information"], summary: "Identity proofs", operationId: "AccountController.identity_proofs", # Validators complains about unused path params otherwise diff --git a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex index 8062da987..cbe4b8972 100644 --- a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex @@ -16,7 +16,7 @@ def open_api_operation(action) do def delete_message_operation do %Operation{ - tags: ["admin", "chat"], + tags: ["Chat administration"], summary: "Delete an individual chat message", operationId: "AdminAPI.ChatController.delete_message", parameters: [ @@ -41,8 +41,8 @@ def delete_message_operation do def messages_operation do %Operation{ - tags: ["admin", "chat"], - summary: "Get the most recent messages of the chat", + tags: ["Chat administration"], + summary: "Get chat's messages", operationId: "AdminAPI.ChatController.messages", parameters: [Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++ @@ -65,7 +65,7 @@ def messages_operation do def show_operation do %Operation{ - tags: ["chat"], + tags: ["Chat administration"], summary: "Create a chat", operationId: "AdminAPI.ChatController.show", parameters: [ diff --git a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex index 323539ca5..b8ccc1d00 100644 --- a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex @@ -16,8 +16,8 @@ def open_api_operation(action) do def show_operation do %Operation{ - tags: ["Admin", "Config"], - summary: "Get list of merged default settings with saved in database", + tags: ["Instance configuration"], + summary: "Retrieve instance configuration", operationId: "AdminAPI.ConfigController.show", parameters: [ Operation.parameter( @@ -38,8 +38,8 @@ def show_operation do def update_operation do %Operation{ - tags: ["Admin", "Config"], - summary: "Update config settings", + tags: ["Instance configuration"], + summary: "Update instance configuration", operationId: "AdminAPI.ConfigController.update", security: [%{"oAuth" => ["write"]}], parameters: admin_api_params(), @@ -71,8 +71,8 @@ def update_operation do def descriptions_operation do %Operation{ - tags: ["Admin", "Config"], - summary: "Get JSON with config descriptions.", + tags: ["Instance configuration"], + summary: "Retrieve config description", operationId: "AdminAPI.ConfigController.descriptions", security: [%{"oAuth" => ["read"]}], parameters: admin_api_params(), diff --git a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex index 05e2fe2be..b149becf9 100644 --- a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex @@ -16,8 +16,8 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Admin", "Reports"], - summary: "Get a list of available frontends", + tags: ["Frontend managment"], + summary: "Retrieve a list of available frontends", operationId: "AdminAPI.FrontendController.index", security: [%{"oAuth" => ["read"]}], responses: %{ @@ -29,7 +29,7 @@ def index_operation do def install_operation do %Operation{ - tags: ["Admin", "Reports"], + tags: ["Frontend managment"], summary: "Install a frontend", operationId: "AdminAPI.FrontendController.install", security: [%{"oAuth" => ["read"]}], diff --git a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex index 0e1fdec08..3e89abfb5 100644 --- a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex @@ -15,8 +15,8 @@ def open_api_operation(action) do def show_operation do %Operation{ - tags: ["Admin", "InstanceDocument"], - summary: "Get the instance document", + tags: ["Instance documents"], + summary: "Retrieve an instance document", operationId: "AdminAPI.InstanceDocumentController.show", security: [%{"oAuth" => ["read"]}], parameters: [ @@ -36,8 +36,8 @@ def show_operation do def update_operation do %Operation{ - tags: ["Admin", "InstanceDocument"], - summary: "Update the instance document", + tags: ["Instance documents"], + summary: "Update an instance document", operationId: "AdminAPI.InstanceDocumentController.update", security: [%{"oAuth" => ["write"]}], requestBody: Helpers.request_body("Parameters", update_request()), @@ -74,8 +74,8 @@ defp update_request do def delete_operation do %Operation{ - tags: ["Admin", "InstanceDocument"], - summary: "Get the instance document", + tags: ["Instance documents"], + summary: "Delete an instance document", operationId: "AdminAPI.InstanceDocumentController.delete", security: [%{"oAuth" => ["write"]}], parameters: [ diff --git a/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex b/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex index 0ce7bcc45..60d69c767 100644 --- a/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/invite_operation.ex @@ -16,7 +16,7 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Admin", "Invites"], + tags: ["Invites"], summary: "Get a list of generated invites", operationId: "AdminAPI.InviteController.index", security: [%{"oAuth" => ["read:invites"]}], @@ -48,7 +48,7 @@ def index_operation do def create_operation do %Operation{ - tags: ["Admin", "Invites"], + tags: ["Invites"], summary: "Create an account registration invite token", operationId: "AdminAPI.InviteController.create", security: [%{"oAuth" => ["write:invites"]}], @@ -69,7 +69,7 @@ def create_operation do def revoke_operation do %Operation{ - tags: ["Admin", "Invites"], + tags: ["Invites"], summary: "Revoke invite by token", operationId: "AdminAPI.InviteController.revoke", security: [%{"oAuth" => ["write:invites"]}], @@ -96,7 +96,7 @@ def revoke_operation do def email_operation do %Operation{ - tags: ["Admin", "Invites"], + tags: ["Invites"], summary: "Sends registration invite via email", operationId: "AdminAPI.InviteController.email", security: [%{"oAuth" => ["write:invites"]}], diff --git a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex index e16356a47..675504ee0 100644 --- a/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex @@ -16,8 +16,8 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Admin", "MediaProxyCache"], - summary: "Fetch a paginated list of all banned MediaProxy URLs in Cachex", + tags: ["MediaProxy cache"], + summary: "Retrieve a list of banned MediaProxy URLs", operationId: "AdminAPI.MediaProxyCacheController.index", security: [%{"oAuth" => ["read:media_proxy_caches"]}], parameters: [ @@ -44,7 +44,7 @@ def index_operation do responses: %{ 200 => Operation.response( - "Array of banned MediaProxy URLs in Cachex", + "Array of MediaProxy URLs", "application/json", %Schema{ type: :object, @@ -68,8 +68,8 @@ def index_operation do def delete_operation do %Operation{ - tags: ["Admin", "MediaProxyCache"], - summary: "Remove a banned MediaProxy URL from Cachex", + tags: ["MediaProxy cache"], + summary: "Remove a banned MediaProxy URL", operationId: "AdminAPI.MediaProxyCacheController.delete", security: [%{"oAuth" => ["write:media_proxy_caches"]}], parameters: admin_api_params(), @@ -94,8 +94,8 @@ def delete_operation do def purge_operation do %Operation{ - tags: ["Admin", "MediaProxyCache"], - summary: "Purge and optionally ban a MediaProxy URL", + tags: ["MediaProxy cache"], + summary: "Purge a URL from MediaProxy cache and optionally ban it", operationId: "AdminAPI.MediaProxyCacheController.purge", security: [%{"oAuth" => ["write:media_proxy_caches"]}], parameters: admin_api_params(), diff --git a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex index f1b32343d..2f3bee4f0 100644 --- a/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex @@ -16,8 +16,8 @@ def open_api_operation(action) do def index_operation do %Operation{ - summary: "List OAuth apps", - tags: ["Admin", "oAuth Apps"], + summary: "Retrieve a list of OAuth applications", + tags: ["OAuth application managment"], operationId: "AdminAPI.OAuthAppController.index", security: [%{"oAuth" => ["write"]}], parameters: [ @@ -69,8 +69,8 @@ def index_operation do def create_operation do %Operation{ - tags: ["Admin", "oAuth Apps"], - summary: "Create OAuth App", + tags: ["OAuth application managment"], + summary: "Create an OAuth application", operationId: "AdminAPI.OAuthAppController.create", requestBody: request_body("Parameters", create_request()), parameters: admin_api_params(), @@ -84,8 +84,8 @@ def create_operation do def update_operation do %Operation{ - tags: ["Admin", "oAuth Apps"], - summary: "Update OAuth App", + tags: ["OAuth application managment"], + summary: "Update OAuth application", operationId: "AdminAPI.OAuthAppController.update", parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["write"]}], @@ -102,8 +102,8 @@ def update_operation do def delete_operation do %Operation{ - tags: ["Admin", "oAuth Apps"], - summary: "Delete OAuth App", + tags: ["OAuth application managment"], + summary: "Delete OAuth application", operationId: "AdminAPI.OAuthAppController.delete", parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["write"]}], diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex index 7a17072e1..c47f18f0c 100644 --- a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex @@ -15,8 +15,8 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Admin", "Relays"], - summary: "List Relays", + tags: ["Relays"], + summary: "Retrieve a list of relays", operationId: "AdminAPI.RelayController.index", security: [%{"oAuth" => ["read"]}], parameters: admin_api_params(), @@ -37,8 +37,8 @@ def index_operation do def follow_operation do %Operation{ - tags: ["Admin", "Relays"], - summary: "Follow a Relay", + tags: ["Relays"], + summary: "Follow a relay", operationId: "AdminAPI.RelayController.follow", security: [%{"oAuth" => ["write:follows"]}], parameters: admin_api_params(), @@ -51,8 +51,8 @@ def follow_operation do def unfollow_operation do %Operation{ - tags: ["Admin", "Relays"], - summary: "Unfollow a Relay", + tags: ["Relays"], + summary: "Unfollow a relay", operationId: "AdminAPI.RelayController.unfollow", security: [%{"oAuth" => ["write:follows"]}], parameters: admin_api_params(), diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index e7acfa271..cfa892d29 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -19,8 +19,8 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Admin", "Reports"], - summary: "Get a list of reports", + tags: ["Report managment"], + summary: "Retrieve a list of reports", operationId: "AdminAPI.ReportController.index", security: [%{"oAuth" => ["read:reports"]}], parameters: [ @@ -69,8 +69,8 @@ def index_operation do def show_operation do %Operation{ - tags: ["Admin", "Reports"], - summary: "Get an individual report", + tags: ["Report managment"], + summary: "Retrieve a report", operationId: "AdminAPI.ReportController.show", parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["read:reports"]}], @@ -83,8 +83,8 @@ def show_operation do def update_operation do %Operation{ - tags: ["Admin", "Reports"], - summary: "Change the state of one or multiple reports", + tags: ["Report managment"], + summary: "Change state of specified reports", operationId: "AdminAPI.ReportController.update", security: [%{"oAuth" => ["write:reports"]}], parameters: admin_api_params(), @@ -99,8 +99,8 @@ def update_operation do def notes_create_operation do %Operation{ - tags: ["Admin", "Reports"], - summary: "Create report note", + tags: ["Report managment"], + summary: "Add a note to the report", operationId: "AdminAPI.ReportController.notes_create", parameters: [id_param() | admin_api_params()], requestBody: @@ -120,8 +120,8 @@ def notes_create_operation do def notes_delete_operation do %Operation{ - tags: ["Admin", "Reports"], - summary: "Delete report note", + tags: ["Report managment"], + summary: "Delete note attached to the report", operationId: "AdminAPI.ReportController.notes_delete", parameters: [ Operation.parameter(:report_id, :path, :string, "Report ID"), diff --git a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex index 34a0bce07..bbfbd8f93 100644 --- a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex @@ -21,8 +21,9 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Admin", "Statuses"], + tags: ["Status administration"], operationId: "AdminAPI.StatusController.index", + summary: "Get all statuses", security: [%{"oAuth" => ["read:statuses"]}], parameters: [ Operation.parameter( @@ -69,8 +70,8 @@ def index_operation do def show_operation do %Operation{ - tags: ["Admin", "Statuses"], - summary: "Show Status", + tags: ["Status adminitration)"], + summary: "Get status", operationId: "AdminAPI.StatusController.show", parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["read:statuses"]}], @@ -83,8 +84,8 @@ def show_operation do def update_operation do %Operation{ - tags: ["Admin", "Statuses"], - summary: "Change the scope of an individual reported status", + tags: ["Status adminitration)"], + summary: "Change the scope of a status", operationId: "AdminAPI.StatusController.update", parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["write:statuses"]}], @@ -98,8 +99,8 @@ def update_operation do def delete_operation do %Operation{ - tags: ["Admin", "Statuses"], - summary: "Delete an individual reported status", + tags: ["Status adminitration)"], + summary: "Delete status", operationId: "AdminAPI.StatusController.delete", parameters: [id_param() | admin_api_params()], security: [%{"oAuth" => ["write:statuses"]}], diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex index 7587e488e..dfb1c7170 100644 --- a/lib/pleroma/web/api_spec/operations/app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/app_operation.ex @@ -16,7 +16,7 @@ def open_api_operation(action) do @spec create_operation() :: Operation.t() def create_operation do %Operation{ - tags: ["apps"], + tags: ["Applications"], summary: "Create an application", description: "Create a new application to obtain OAuth2 credentials", operationId: "AppController.create", @@ -45,8 +45,8 @@ def create_operation do def verify_credentials_operation do %Operation{ - tags: ["apps"], - summary: "Verify your app works", + tags: ["Applications"], + summary: "Verify the application works", description: "Confirm that the app's OAuth2 credentials work.", operationId: "AppController.verify_credentials", security: [%{"oAuth" => ["read"]}], diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex index e5ee6e695..b49700172 100644 --- a/lib/pleroma/web/api_spec/operations/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex @@ -20,7 +20,7 @@ def open_api_operation(action) do def mark_as_read_operation do %Operation{ - tags: ["chat"], + tags: ["Chats"], summary: "Mark all messages in the chat as read", operationId: "ChatController.mark_as_read", parameters: [Operation.parameter(:id, :path, :string, "The ID of the Chat")], @@ -43,8 +43,8 @@ def mark_as_read_operation do def mark_message_as_read_operation do %Operation{ - tags: ["chat"], - summary: "Mark one message in the chat as read", + tags: ["Chats"], + summary: "Mark a message as read", operationId: "ChatController.mark_message_as_read", parameters: [ Operation.parameter(:id, :path, :string, "The ID of the Chat"), @@ -68,8 +68,8 @@ def mark_message_as_read_operation do def show_operation do %Operation{ - tags: ["chat"], - summary: "Create a chat", + tags: ["Chats"], + summary: "Retrieve a chat", operationId: "ChatController.show", parameters: [ Operation.parameter( @@ -99,7 +99,7 @@ def show_operation do def create_operation do %Operation{ - tags: ["chat"], + tags: ["Chats"], summary: "Create a chat", operationId: "ChatController.create", parameters: [ @@ -130,8 +130,8 @@ def create_operation do def index_operation do %Operation{ - tags: ["chat"], - summary: "Get a list of chats that you participated in", + tags: ["Chats"], + summary: "Retrieve list of chats", operationId: "ChatController.index", parameters: [ Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users") @@ -150,8 +150,8 @@ def index_operation do def messages_operation do %Operation{ - tags: ["chat"], - summary: "Get the most recent messages of the chat", + tags: ["Chats"], + summary: "Retrieve chat's messages", operationId: "ChatController.messages", parameters: [Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++ @@ -175,7 +175,7 @@ def messages_operation do def post_chat_message_operation do %Operation{ - tags: ["chat"], + tags: ["Chats"], summary: "Post a message to the chat", operationId: "ChatController.post_chat_message", parameters: [ @@ -202,8 +202,8 @@ def post_chat_message_operation do def delete_message_operation do %Operation{ - tags: ["chat"], - summary: "delete_message", + tags: ["Chats"], + summary: "Delete message", operationId: "ChatController.delete_message", parameters: [ Operation.parameter(:id, :path, :string, "The ID of the Chat"), diff --git a/lib/pleroma/web/api_spec/operations/conversation_operation.ex b/lib/pleroma/web/api_spec/operations/conversation_operation.ex index 15fc3d66d..367f4125a 100644 --- a/lib/pleroma/web/api_spec/operations/conversation_operation.ex +++ b/lib/pleroma/web/api_spec/operations/conversation_operation.ex @@ -18,7 +18,7 @@ def open_api_operation(action) do def index_operation do %Operation{ tags: ["Conversations"], - summary: "Show conversation", + summary: "List of conversations", security: [%{"oAuth" => ["read:statuses"]}], operationId: "ConversationController.index", parameters: [ @@ -44,7 +44,7 @@ def index_operation do def mark_as_read_operation do %Operation{ tags: ["Conversations"], - summary: "Mark as read", + summary: "Mark conversation as read", operationId: "ConversationController.mark_as_read", parameters: [ Operation.parameter(:id, :path, :string, "Conversation ID", diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex index 541c1ff1b..98da1a6de 100644 --- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex +++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex @@ -14,8 +14,8 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["custom_emojis"], - summary: "List custom custom emojis", + tags: ["Custom emojis"], + summary: "Retrieve a list of custom emojis", description: "Returns custom emojis that are available on the server.", operationId: "CustomEmojiController.index", responses: %{ diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex index 2be54e359..f124e7fe5 100644 --- a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex +++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex @@ -14,9 +14,8 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["domain_blocks"], - summary: "Fetch domain blocks", - description: "View domains the user has blocked.", + tags: ["Domain blocks"], + summary: "Retrieve a list of blocked domains", security: [%{"oAuth" => ["follow", "read:blocks"]}], operationId: "DomainBlockController.index", responses: %{ @@ -34,7 +33,7 @@ def index_operation do # Supporting domain query parameter is deprecated in Mastodon API def create_operation do %Operation{ - tags: ["domain_blocks"], + tags: ["Domain blocks"], summary: "Block a domain", description: """ Block a domain to: @@ -55,7 +54,7 @@ def create_operation do # Supporting domain query parameter is deprecated in Mastodon API def delete_operation do %Operation{ - tags: ["domain_blocks"], + tags: ["Domain blocks"], summary: "Unblock a domain", description: "Remove a domain block, if it exists in the user's array of blocked domains.", operationId: "DomainBlockController.delete", diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex index e1aa7d4ca..a7b306a30 100644 --- a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex +++ b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex @@ -17,7 +17,7 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Emoji Reactions"], + tags: ["Emoji reactions"], summary: "Get an object of emoji to account mappings with accounts that reacted to the post", parameters: [ @@ -42,7 +42,7 @@ def index_operation do def create_operation do %Operation{ - tags: ["Emoji Reactions"], + tags: ["Emoji reactions"], summary: "React to a post with a unicode emoji", parameters: [ Operation.parameter(:id, :path, FlakeID, "Status ID", required: true), @@ -61,7 +61,7 @@ def create_operation do def delete_operation do %Operation{ - tags: ["Emoji Reactions"], + tags: ["Emoji reactions"], summary: "Remove a reaction to a post with a unicode emoji", parameters: [ Operation.parameter(:id, :path, FlakeID, "Status ID", required: true), @@ -78,7 +78,7 @@ def delete_operation do end defp array_of_reactions_response do - Operation.response("Array of Emoji Reactions", "application/json", %Schema{ + Operation.response("Array of Emoji reactions", "application/json", %Schema{ type: :array, items: emoji_reaction(), example: [emoji_reaction().example] diff --git a/lib/pleroma/web/api_spec/operations/filter_operation.ex b/lib/pleroma/web/api_spec/operations/filter_operation.ex index c5b0c035b..5102921bc 100644 --- a/lib/pleroma/web/api_spec/operations/filter_operation.ex +++ b/lib/pleroma/web/api_spec/operations/filter_operation.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.FilterOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.BooleanLike def open_api_operation(action) do @@ -15,57 +16,64 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["apps"], - summary: "View all filters", + tags: ["Filters"], + summary: "All filters", operationId: "FilterController.index", security: [%{"oAuth" => ["read:filters"]}], responses: %{ - 200 => Operation.response("Filters", "application/json", array_of_filters()) + 200 => Operation.response("Filters", "application/json", array_of_filters()), + 403 => Operation.response("Error", "application/json", ApiError) } } end def create_operation do %Operation{ - tags: ["apps"], + tags: ["Filters"], summary: "Create a filter", operationId: "FilterController.create", requestBody: Helpers.request_body("Parameters", create_request(), required: true), security: [%{"oAuth" => ["write:filters"]}], - responses: %{200 => Operation.response("Filter", "application/json", filter())} + responses: %{ + 200 => Operation.response("Filter", "application/json", filter()), + 403 => Operation.response("Error", "application/json", ApiError) + } } end def show_operation do %Operation{ - tags: ["apps"], - summary: "View all filters", + tags: ["Filters"], + summary: "Filter", parameters: [id_param()], operationId: "FilterController.show", security: [%{"oAuth" => ["read:filters"]}], responses: %{ - 200 => Operation.response("Filter", "application/json", filter()) + 200 => Operation.response("Filter", "application/json", filter()), + 403 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) } } end def update_operation do %Operation{ - tags: ["apps"], + tags: ["Filters"], summary: "Update a filter", parameters: [id_param()], operationId: "FilterController.update", requestBody: Helpers.request_body("Parameters", update_request(), required: true), security: [%{"oAuth" => ["write:filters"]}], responses: %{ - 200 => Operation.response("Filter", "application/json", filter()) + 200 => Operation.response("Filter", "application/json", filter()), + 403 => Operation.response("Error", "application/json", ApiError) } } end def delete_operation do %Operation{ - tags: ["apps"], + tags: ["Filters"], summary: "Remove a filter", parameters: [id_param()], operationId: "FilterController.delete", @@ -75,7 +83,8 @@ def delete_operation do Operation.response("Filter", "application/json", %Schema{ type: :object, description: "Empty object" - }) + }), + 403 => Operation.response("Error", "application/json", ApiError) } } end @@ -210,15 +219,13 @@ defp update_request do nullable: true, description: "Consider word boundaries?", default: true + }, + expires_in: %Schema{ + nullable: true, + type: :integer, + description: + "Number of seconds from now the filter should expire. Otherwise, null for a filter that doesn't expire." } - # TODO: probably should implement filter expiration - # expires_in: %Schema{ - # type: :string, - # format: :"date-time", - # description: - # "ISO 8601 Datetime for when the filter expires. Otherwise, - # null for a filter that doesn't expire." - # } }, required: [:phrase, :context], example: %{ diff --git a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex index fc849bcb2..784019699 100644 --- a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex +++ b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex @@ -15,8 +15,8 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Follow Requests"], - summary: "Pending Follows", + tags: ["Follow requests"], + summary: "Retrieve follow requests", security: [%{"oAuth" => ["read:follows", "follow"]}], operationId: "FollowRequestController.index", responses: %{ @@ -32,8 +32,8 @@ def index_operation do def authorize_operation do %Operation{ - tags: ["Follow Requests"], - summary: "Accept Follow", + tags: ["Follow requests"], + summary: "Accept follow request", operationId: "FollowRequestController.authorize", parameters: [id_param()], security: [%{"oAuth" => ["follow", "write:follows"]}], @@ -45,8 +45,8 @@ def authorize_operation do def reject_operation do %Operation{ - tags: ["Follow Requests"], - summary: "Reject Follow", + tags: ["Follow requests"], + summary: "Reject follow request", operationId: "FollowRequestController.reject", parameters: [id_param()], security: [%{"oAuth" => ["follow", "write:follows"]}], diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 8ca82b95c..9384acc32 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -14,7 +14,7 @@ def open_api_operation(action) do def show_operation do %Operation{ tags: ["Instance"], - summary: "Fetch instance", + summary: "Retrieve instance information", description: "Information about the server", operationId: "InstanceController.show", responses: %{ @@ -26,7 +26,7 @@ def show_operation do def peers_operation do %Operation{ tags: ["Instance"], - summary: "List of known hosts", + summary: "Retrieve list of known instances", operationId: "InstanceController.peers", responses: %{ 200 => Operation.response("Array of domains", "application/json", array_of_domains()) diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex index 62a67cc20..8a6e92b99 100644 --- a/lib/pleroma/web/api_spec/operations/list_operation.ex +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -20,7 +20,7 @@ def open_api_operation(action) do def index_operation do %Operation{ tags: ["Lists"], - summary: "Show user's lists", + summary: "Retrieve a list of lists", description: "Fetch all lists that the user owns", security: [%{"oAuth" => ["read:lists"]}], operationId: "ListController.index", @@ -33,7 +33,7 @@ def index_operation do def create_operation do %Operation{ tags: ["Lists"], - summary: "Create a list", + summary: "Create a list", description: "Fetch the list with the given ID. Used for verifying the title of a list.", operationId: "ListController.create", requestBody: create_update_request(), @@ -49,7 +49,7 @@ def create_operation do def show_operation do %Operation{ tags: ["Lists"], - summary: "Show a single list", + summary: "Retrieve a list", description: "Fetch the list with the given ID. Used for verifying the title of a list.", operationId: "ListController.show", parameters: [id_param()], @@ -93,7 +93,7 @@ def delete_operation do def list_accounts_operation do %Operation{ tags: ["Lists"], - summary: "View accounts in list", + summary: "Retrieve accounts in list", operationId: "ListController.list_accounts", parameters: [id_param()], security: [%{"oAuth" => ["read:lists"]}], diff --git a/lib/pleroma/web/api_spec/operations/media_operation.ex b/lib/pleroma/web/api_spec/operations/media_operation.ex index 7de0d7da5..85aa14869 100644 --- a/lib/pleroma/web/api_spec/operations/media_operation.ex +++ b/lib/pleroma/web/api_spec/operations/media_operation.ex @@ -16,7 +16,7 @@ def open_api_operation(action) do def create_operation do %Operation{ - tags: ["media"], + tags: ["Media attachments"], summary: "Upload media as attachment", description: "Creates an attachment to be used with a new status.", operationId: "MediaController.create", @@ -56,8 +56,8 @@ defp create_request do def update_operation do %Operation{ - tags: ["media"], - summary: "Upload media as attachment", + tags: ["Media attachments"], + summary: "Update attachment", description: "Creates an attachment to be used with a new status.", operationId: "MediaController.update", security: [%{"oAuth" => ["write:media"]}], @@ -97,8 +97,8 @@ defp update_request do def show_operation do %Operation{ - tags: ["media"], - summary: "Show Uploaded media attachment", + tags: ["Media attachments"], + summary: "Attachment", operationId: "MediaController.show", parameters: [id_param()], security: [%{"oAuth" => ["read:media"]}], @@ -112,8 +112,8 @@ def show_operation do def create2_operation do %Operation{ - tags: ["media"], - summary: "Upload media as attachment", + tags: ["Media attachments"], + summary: "Upload media as attachment (v2)", description: "Creates an attachment to be used with a new status.", operationId: "MediaController.create2", security: [%{"oAuth" => ["write:media"]}], diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex index b7e391264..ec88eabe1 100644 --- a/lib/pleroma/web/api_spec/operations/notification_operation.ex +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -22,7 +22,7 @@ def open_api_operation(action) do def index_operation do %Operation{ tags: ["Notifications"], - summary: "Get all notifications", + summary: "Retrieve a list of notifications", description: "Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and `id` values.", operationId: "NotificationController.index", @@ -74,7 +74,7 @@ def index_operation do def show_operation do %Operation{ tags: ["Notifications"], - summary: "Get a single notification", + summary: "Retrieve a notification", description: "View information about a notification with a given ID.", operationId: "NotificationController.show", security: [%{"oAuth" => ["read:notifications"]}], @@ -99,7 +99,7 @@ def clear_operation do def dismiss_operation do %Operation{ tags: ["Notifications"], - summary: "Dismiss a single notification", + summary: "Dismiss a notification", description: "Clear a single notification from the server.", operationId: "NotificationController.dismiss", parameters: [id_param()], diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex index caa13afee..ad49f6426 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex @@ -18,8 +18,9 @@ def open_api_operation(action) do def confirmation_resend_operation do %Operation{ - tags: ["Accounts"], - summary: "Resend confirmation email. Expects `email` or `nickname`", + tags: ["Account credentials"], + summary: "Resend confirmation email", + description: "Expects `email` or `nickname`.", operationId: "PleromaAPI.AccountController.confirmation_resend", parameters: [ Operation.parameter(:email, :query, :string, "Email of that needs to be verified", @@ -41,8 +42,10 @@ def confirmation_resend_operation do def favourites_operation do %Operation{ - tags: ["Accounts"], - summary: "Returns favorites timeline of any user", + tags: ["Retrieve account information"], + summary: "Favorites", + description: + "Only returns data if the user has opted into sharing it. See `hide_favorites` in [Update account credentials](#operation/AccountController.update_credentials).", operationId: "PleromaAPI.AccountController.favourites", parameters: [id_param() | pagination_params()], security: [%{"oAuth" => ["read:favourites"]}], @@ -61,8 +64,9 @@ def favourites_operation do def subscribe_operation do %Operation{ - tags: ["Accounts"], - summary: "Subscribe to receive notifications for all statuses posted by a user", + tags: ["Account actions"], + summary: "Subscribe", + description: "Receive notifications for all statuses posted by the account.", operationId: "PleromaAPI.AccountController.subscribe", parameters: [id_param()], security: [%{"oAuth" => ["follow", "write:follows"]}], @@ -75,8 +79,9 @@ def subscribe_operation do def unsubscribe_operation do %Operation{ - tags: ["Accounts"], - summary: "Unsubscribe to stop receiving notifications from user statuses", + tags: ["Account actions"], + summary: "Unsubscribe", + description: "Stop receiving notifications for all statuses posted by the account.", operationId: "PleromaAPI.AccountController.unsubscribe", parameters: [id_param()], security: [%{"oAuth" => ["follow", "write:follows"]}], diff --git a/lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex index 7752f4676..12fb8ed36 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex @@ -19,7 +19,7 @@ def open_api_operation(action) do def show_operation do %Operation{ tags: ["Conversations"], - summary: "The conversation with the given ID", + summary: "Conversation", parameters: [ Operation.parameter(:id, :path, :string, "Conversation ID", example: "123", @@ -37,7 +37,7 @@ def show_operation do def statuses_operation do %Operation{ tags: ["Conversations"], - summary: "Timeline for a given conversation", + summary: "Timeline for conversation", parameters: [ Operation.parameter(:id, :path, :string, "Conversation ID", example: "123", @@ -61,7 +61,8 @@ def statuses_operation do def update_operation do %Operation{ tags: ["Conversations"], - summary: "Update a conversation. Used to change the set of recipients.", + summary: "Update conversation", + description: "Change set of recipients for the conversation.", parameters: [ Operation.parameter(:id, :path, :string, "Conversation ID", example: "123", @@ -86,7 +87,7 @@ def update_operation do def mark_as_read_operation do %Operation{ tags: ["Conversations"], - summary: "Marks all user's conversations as read", + summary: "Marks all conversations as read", security: [%{"oAuth" => ["write:conversations"]}], operationId: "PleromaAPI.ConversationController.mark_as_read", responses: %{ diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex index 83981f4e7..bed9511ef 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -16,7 +16,7 @@ def open_api_operation(action) do def create_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Add new file to the pack", operationId: "PleromaAPI.EmojiPackController.add_file", security: [%{"oAuth" => ["write"]}], @@ -62,7 +62,7 @@ defp create_request do def update_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Add new file to the pack", operationId: "PleromaAPI.EmojiPackController.update_file", security: [%{"oAuth" => ["write"]}], @@ -106,7 +106,7 @@ defp update_request do def delete_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Delete emoji file from pack", operationId: "PleromaAPI.EmojiPackController.delete_file", security: [%{"oAuth" => ["write"]}], diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex index ceff3f67a..48dafa5f2 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex @@ -16,7 +16,7 @@ def open_api_operation(action) do def remote_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Make request to another instance for emoji packs list", security: [%{"oAuth" => ["write"]}], parameters: [ @@ -44,7 +44,7 @@ def remote_operation do def index_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Lists local custom emoji packs", operationId: "PleromaAPI.EmojiPackController.index", parameters: [ @@ -69,7 +69,7 @@ def index_operation do def show_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Show emoji pack", operationId: "PleromaAPI.EmojiPackController.show", parameters: [ @@ -97,7 +97,7 @@ def show_operation do def archive_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Requests a local pack archive from the instance", operationId: "PleromaAPI.EmojiPackController.archive", parameters: [name_param()], @@ -115,7 +115,7 @@ def archive_operation do def download_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Download pack from another instance", operationId: "PleromaAPI.EmojiPackController.download", security: [%{"oAuth" => ["write"]}], @@ -145,7 +145,7 @@ defp download_request do def create_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Create an empty pack", operationId: "PleromaAPI.EmojiPackController.create", security: [%{"oAuth" => ["write"]}], @@ -161,7 +161,7 @@ def create_operation do def delete_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Delete a custom emoji pack", operationId: "PleromaAPI.EmojiPackController.delete", security: [%{"oAuth" => ["write"]}], @@ -177,7 +177,7 @@ def delete_operation do def update_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Updates (replaces) pack metadata", operationId: "PleromaAPI.EmojiPackController.update", security: [%{"oAuth" => ["write"]}], @@ -193,7 +193,7 @@ def update_operation do def import_from_filesystem_operation do %Operation{ - tags: ["Emoji Packs"], + tags: ["Emoji packs"], summary: "Imports packs from filesystem", operationId: "PleromaAPI.EmojiPackController.import", security: [%{"oAuth" => ["write"]}], diff --git a/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex index c9519f769..612113147 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex @@ -13,8 +13,8 @@ def open_api_operation(action) do def show_operation do %Operation{ - tags: ["PleromaInstances"], - summary: "Instances federation status", + tags: ["Instance"], + summary: "Retrieve federation status", description: "Information about instances deemed unreachable by the server", operationId: "PleromaInstances.show", responses: %{ diff --git a/lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex index 226d95054..6191cb97d 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex @@ -17,7 +17,7 @@ def open_api_operation(action) do def show_operation do %Operation{ tags: ["Mascot"], - summary: "Gets user mascot image", + summary: "Retrieve mascot", security: [%{"oAuth" => ["read:accounts"]}], operationId: "PleromaAPI.MascotController.show", responses: %{ @@ -29,7 +29,7 @@ def show_operation do def update_operation do %Operation{ tags: ["Mascot"], - summary: "Set/clear user avatar image", + summary: "Set or clear mascot", description: "Behaves exactly the same as `POST /api/v1/upload`. Can only accept images - any attempt to upload non-image files will be met with `HTTP 415 Unsupported Media Type`.", operationId: "PleromaAPI.MascotController.update", diff --git a/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex index c26fb2736..1dda39240 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex @@ -18,7 +18,8 @@ def open_api_operation(action) do def mark_as_read_operation do %Operation{ tags: ["Notifications"], - summary: "Mark notifications as read. Query parameters are mutually exclusive.", + summary: "Mark notifications as read", + description: "Query parameters are mutually exclusive.", requestBody: request_body("Parameters", %Schema{ type: :object, @@ -32,7 +33,7 @@ def mark_as_read_operation do responses: %{ 200 => Operation.response( - "A Notification or array of Motifications", + "A Notification or array of Notifications", "application/json", %Schema{ anyOf: [ diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex index 792d5cb51..b744efa60 100644 --- a/lib/pleroma/web/api_spec/operations/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/report_operation.ex @@ -16,7 +16,7 @@ def open_api_operation(action) do def create_operation do %Operation{ - tags: ["reports"], + tags: ["Reports"], summary: "File a report", description: "Report problematic users to your moderators", operationId: "ReportController.create", diff --git a/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex index 873ed3a80..b9c5b35c1 100644 --- a/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex +++ b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex @@ -18,7 +18,7 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Scheduled Statuses"], + tags: ["Scheduled statuses"], summary: "View scheduled statuses", security: [%{"oAuth" => ["read:statuses"]}], parameters: pagination_params(), @@ -35,7 +35,7 @@ def index_operation do def show_operation do %Operation{ - tags: ["Scheduled Statuses"], + tags: ["Scheduled statuses"], summary: "View a single scheduled status", security: [%{"oAuth" => ["read:statuses"]}], parameters: [id_param()], @@ -49,7 +49,7 @@ def show_operation do def update_operation do %Operation{ - tags: ["Scheduled Statuses"], + tags: ["Scheduled statuses"], summary: "Schedule a status", operationId: "ScheduledActivity.update", security: [%{"oAuth" => ["write:statuses"]}], @@ -75,7 +75,7 @@ def update_operation do def delete_operation do %Operation{ - tags: ["Scheduled Statuses"], + tags: ["Scheduled statuses"], summary: "Cancel a scheduled status", security: [%{"oAuth" => ["write:statuses"]}], parameters: [id_param()], diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index fd29f5139..40edc747d 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -22,8 +22,8 @@ def open_api_operation(action) do def index_operation do %Operation{ - tags: ["Statuses"], - summary: "Get multiple statuses by IDs", + tags: ["Retrieve status information"], + summary: "Multiple statuses", security: [%{"oAuth" => ["read:statuses"]}], parameters: [ Operation.parameter( @@ -48,7 +48,7 @@ def index_operation do def create_operation do %Operation{ - tags: ["Statuses"], + tags: ["Status actions"], summary: "Publish new status", security: [%{"oAuth" => ["write:statuses"]}], description: "Post a new status", @@ -68,8 +68,8 @@ def create_operation do def show_operation do %Operation{ - tags: ["Statuses"], - summary: "View specific status", + tags: ["Retrieve status information"], + summary: "Status", description: "View information about a status", operationId: "StatusController.show", security: [%{"oAuth" => ["read:statuses"]}], @@ -91,8 +91,8 @@ def show_operation do def delete_operation do %Operation{ - tags: ["Statuses"], - summary: "Delete status", + tags: ["Status actions"], + summary: "Delete", security: [%{"oAuth" => ["write:statuses"]}], description: "Delete one of your own statuses", operationId: "StatusController.delete", @@ -107,8 +107,8 @@ def delete_operation do def reblog_operation do %Operation{ - tags: ["Statuses"], - summary: "Boost", + tags: ["Status actions"], + summary: "Reblog", security: [%{"oAuth" => ["write:statuses"]}], description: "Share a status", operationId: "StatusController.reblog", @@ -129,8 +129,8 @@ def reblog_operation do def unreblog_operation do %Operation{ - tags: ["Statuses"], - summary: "Undo boost", + tags: ["Status actions"], + summary: "Undo reblog", security: [%{"oAuth" => ["write:statuses"]}], description: "Undo a reshare of a status", operationId: "StatusController.unreblog", @@ -144,7 +144,7 @@ def unreblog_operation do def favourite_operation do %Operation{ - tags: ["Statuses"], + tags: ["Status actions"], summary: "Favourite", security: [%{"oAuth" => ["write:favourites"]}], description: "Add a status to your favourites list", @@ -159,7 +159,7 @@ def favourite_operation do def unfavourite_operation do %Operation{ - tags: ["Statuses"], + tags: ["Status actions"], summary: "Undo favourite", security: [%{"oAuth" => ["write:favourites"]}], description: "Remove a status from your favourites list", @@ -174,7 +174,7 @@ def unfavourite_operation do def pin_operation do %Operation{ - tags: ["Statuses"], + tags: ["Status actions"], summary: "Pin to profile", security: [%{"oAuth" => ["write:accounts"]}], description: "Feature one of your own public statuses at the top of your profile", @@ -189,8 +189,8 @@ def pin_operation do def unpin_operation do %Operation{ - tags: ["Statuses"], - summary: "Unpin to profile", + tags: ["Status actions"], + summary: "Unpin from profile", security: [%{"oAuth" => ["write:accounts"]}], description: "Unfeature a status from the top of your profile", operationId: "StatusController.unpin", @@ -204,7 +204,7 @@ def unpin_operation do def bookmark_operation do %Operation{ - tags: ["Statuses"], + tags: ["Status actions"], summary: "Bookmark", security: [%{"oAuth" => ["write:bookmarks"]}], description: "Privately bookmark a status", @@ -218,7 +218,7 @@ def bookmark_operation do def unbookmark_operation do %Operation{ - tags: ["Statuses"], + tags: ["Status actions"], summary: "Undo bookmark", security: [%{"oAuth" => ["write:bookmarks"]}], description: "Remove a status from your private bookmarks", @@ -232,7 +232,7 @@ def unbookmark_operation do def mute_conversation_operation do %Operation{ - tags: ["Statuses"], + tags: ["Status actions"], summary: "Mute conversation", security: [%{"oAuth" => ["write:mutes"]}], description: "Do not receive notifications for the thread that this status is part of.", @@ -267,7 +267,7 @@ def mute_conversation_operation do def unmute_conversation_operation do %Operation{ - tags: ["Statuses"], + tags: ["Status actions"], summary: "Unmute conversation", security: [%{"oAuth" => ["write:mutes"]}], description: @@ -283,7 +283,7 @@ def unmute_conversation_operation do def card_operation do %Operation{ - tags: ["Statuses"], + tags: ["Retrieve status information"], deprecated: true, summary: "Preview card", description: "Deprecated in favor of card property inlined on Status entity", @@ -311,7 +311,7 @@ def card_operation do def favourited_by_operation do %Operation{ - tags: ["Statuses"], + tags: ["Retrieve status information"], summary: "Favourited by", description: "View who favourited a given status", operationId: "StatusController.favourited_by", @@ -331,9 +331,9 @@ def favourited_by_operation do def reblogged_by_operation do %Operation{ - tags: ["Statuses"], - summary: "Boosted by", - description: "View who boosted a given status", + tags: ["Retrieve status information"], + summary: "Reblogged by", + description: "View who reblogged a given status", operationId: "StatusController.reblogged_by", security: [%{"oAuth" => ["read:accounts"]}], parameters: [id_param()], @@ -351,7 +351,7 @@ def reblogged_by_operation do def context_operation do %Operation{ - tags: ["Statuses"], + tags: ["Retrieve status information"], summary: "Parent and child statuses", description: "View statuses above and below this status in the thread", operationId: "StatusController.context", @@ -365,7 +365,7 @@ def context_operation do def favourites_operation do %Operation{ - tags: ["Statuses"], + tags: ["Timelines"], summary: "Favourited statuses", description: "Statuses the user has favourited. Please note that you have to use the link headers to paginate this. You can not build the query parameters yourself.", @@ -380,7 +380,7 @@ def favourites_operation do def bookmarks_operation do %Operation{ - tags: ["Statuses"], + tags: ["Timelines"], summary: "Bookmarked statuses", description: "Statuses the user has bookmarked", operationId: "StatusController.bookmarks", @@ -413,34 +413,7 @@ defp create_request do items: %Schema{type: :string}, description: "Array of Attachment ids to be attached as media." }, - poll: %Schema{ - nullable: true, - type: :object, - required: [:options], - properties: %{ - options: %Schema{ - type: :array, - items: %Schema{type: :string}, - description: "Array of possible answers. Must be provided with `poll[expires_in]`." - }, - expires_in: %Schema{ - type: :integer, - nullable: true, - description: - "Duration the poll should be open, in seconds. Must be provided with `poll[options]`" - }, - multiple: %Schema{ - allOf: [BooleanLike], - nullable: true, - description: "Allow multiple choices?" - }, - hide_totals: %Schema{ - allOf: [BooleanLike], - nullable: true, - description: "Hide vote counts until the poll ends?" - } - } - }, + poll: poll_params(), in_reply_to_id: %Schema{ nullable: true, allOf: [FlakeID], @@ -522,6 +495,37 @@ defp create_request do } end + def poll_params do + %Schema{ + nullable: true, + type: :object, + required: [:options, :expires_in], + properties: %{ + options: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: "Array of possible answers. Must be provided with `poll[expires_in]`." + }, + expires_in: %Schema{ + type: :integer, + nullable: true, + description: + "Duration the poll should be open, in seconds. Must be provided with `poll[options]`" + }, + multiple: %Schema{ + allOf: [BooleanLike], + nullable: true, + description: "Allow multiple choices?" + }, + hide_totals: %Schema{ + allOf: [BooleanLike], + nullable: true, + description: "Hide vote counts until the poll ends?" + } + } + } + end + def id_param do Operation.parameter(:id, :path, FlakeID, "Status ID", example: "9umDrYheeY451cQnEe", diff --git a/lib/pleroma/web/api_spec/operations/subscription_operation.ex b/lib/pleroma/web/api_spec/operations/subscription_operation.ex index 1374a6ff4..60a7fb3b0 100644 --- a/lib/pleroma/web/api_spec/operations/subscription_operation.ex +++ b/lib/pleroma/web/api_spec/operations/subscription_operation.ex @@ -17,7 +17,7 @@ def open_api_operation(action) do def create_operation do %Operation{ - tags: ["Push Subscriptions"], + tags: ["Push subscriptions"], summary: "Subscribe to push notifications", description: "Add a Web Push API subscription to receive notifications. Each access token can have one push subscription. If you create a new subscription, the old subscription is deleted.", @@ -25,7 +25,7 @@ def create_operation do security: [%{"oAuth" => ["push"]}], requestBody: Helpers.request_body("Parameters", create_request(), required: true), responses: %{ - 200 => Operation.response("Push Subscription", "application/json", PushSubscription), + 200 => Operation.response("Push subscription", "application/json", PushSubscription), 400 => Operation.response("Error", "application/json", ApiError), 403 => Operation.response("Error", "application/json", ApiError) } @@ -34,13 +34,13 @@ def create_operation do def show_operation do %Operation{ - tags: ["Push Subscriptions"], + tags: ["Push subscriptions"], summary: "Get current subscription", description: "View the PushSubscription currently associated with this access token.", operationId: "SubscriptionController.show", security: [%{"oAuth" => ["push"]}], responses: %{ - 200 => Operation.response("Push Subscription", "application/json", PushSubscription), + 200 => Operation.response("Push subscription", "application/json", PushSubscription), 403 => Operation.response("Error", "application/json", ApiError), 404 => Operation.response("Error", "application/json", ApiError) } @@ -49,7 +49,7 @@ def show_operation do def update_operation do %Operation{ - tags: ["Push Subscriptions"], + tags: ["Push subscriptions"], summary: "Change types of notifications", description: "Updates the current push subscription. Only the data part can be updated. To change fundamentals, a new subscription must be created instead.", @@ -57,7 +57,7 @@ def update_operation do security: [%{"oAuth" => ["push"]}], requestBody: Helpers.request_body("Parameters", update_request(), required: true), responses: %{ - 200 => Operation.response("Push Subscription", "application/json", PushSubscription), + 200 => Operation.response("Push subscription", "application/json", PushSubscription), 403 => Operation.response("Error", "application/json", ApiError) } } @@ -65,7 +65,7 @@ def update_operation do def delete_operation do %Operation{ - tags: ["Push Subscriptions"], + tags: ["Push subscriptions"], summary: "Remove current subscription", description: "Removes the current Web Push API subscription.", operationId: "SubscriptionController.delete", diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex index 01396642c..cae18c758 100644 --- a/lib/pleroma/web/api_spec/operations/timeline_operation.ex +++ b/lib/pleroma/web/api_spec/operations/timeline_operation.ex @@ -43,8 +43,7 @@ def direct_operation do tags: ["Timelines"], summary: "Direct timeline", description: - "View statuses with a “direct” privacy, from your account or in your notifications", - deprecated: true, + "View statuses with a “direct” scope addressed to the account. Using this endpoint is discouraged, please use [conversations](#tag/Conversations) or [chats](#tag/Chats).", parameters: [with_muted_param() | pagination_params()], security: [%{"oAuth" => ["read:statuses"]}], operationId: "TimelineController.direct", diff --git a/lib/pleroma/web/api_spec/operations/user_import_operation.ex b/lib/pleroma/web/api_spec/operations/user_import_operation.ex index 859404ded..6292e2004 100644 --- a/lib/pleroma/web/api_spec/operations/user_import_operation.ex +++ b/lib/pleroma/web/api_spec/operations/user_import_operation.ex @@ -17,8 +17,8 @@ def open_api_operation(action) do def follow_operation do %Operation{ - tags: ["follow_import"], - summary: "Imports your follows.", + tags: ["Data import"], + summary: "Import follows", operationId: "UserImportController.follow", requestBody: request_body("Parameters", import_request(), required: true), responses: %{ @@ -31,8 +31,8 @@ def follow_operation do def blocks_operation do %Operation{ - tags: ["blocks_import"], - summary: "Imports your blocks.", + tags: ["Data import"], + summary: "Import blocks", operationId: "UserImportController.blocks", requestBody: request_body("Parameters", import_request(), required: true), responses: %{ @@ -45,8 +45,8 @@ def blocks_operation do def mutes_operation do %Operation{ - tags: ["mutes_import"], - summary: "Imports your mutes.", + tags: ["Data import"], + summary: "Import mutes", operationId: "UserImportController.mutes", requestBody: request_body("Parameters", import_request(), required: true), responses: %{ diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 4f9b564d1..bd7143ab9 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -96,7 +96,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do hide_notification_contents: %Schema{type: :boolean} } }, - relationship: AccountRelationship, + relationship: %Schema{allOf: [AccountRelationship], nullable: true}, settings_store: %Schema{ type: :object, description: diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex index 2cda19631..16b73ebb4 100644 --- a/lib/pleroma/web/api_spec/schemas/account_relationship.ex +++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do OpenApiSpex.schema(%{ title: "AccountRelationship", - description: "Response schema for relationship", + description: "Relationship between current account and requested account", type: :object, properties: %{ blocked_by: %Schema{type: :boolean}, diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex index dd0d9aa8f..cc051046a 100644 --- a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex +++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.Attachment - alias Pleroma.Web.ApiSpec.Schemas.Poll alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + alias Pleroma.Web.ApiSpec.StatusOperation require OpenApiSpex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do spoiler_text: %Schema{type: :string, nullable: true}, visibility: %Schema{allOf: [VisibilityScope], nullable: true}, scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true}, - poll: %Schema{allOf: [Poll], nullable: true}, + poll: StatusOperation.poll_params(), in_reply_to_id: %Schema{type: :string, nullable: true} } } diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index c8b4a3095..9b1ae809d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -20,6 +20,8 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FilterOperation + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + @doc "GET /api/v1/filters" def index(%{assigns: %{user: user}} = conn, _) do filters = Filter.get_filters(user) @@ -29,25 +31,23 @@ def index(%{assigns: %{user: user}} = conn, _) do @doc "POST /api/v1/filters" def create(%{assigns: %{user: user}, body_params: params} = conn, _) do - query = %Filter{ - user_id: user.id, - phrase: params.phrase, - context: params.context, - hide: params.irreversible, - whole_word: params.whole_word - # TODO: support `expires_in` parameter (as in Mastodon API) - } - - {:ok, response} = Filter.create(query) - - render(conn, "show.json", filter: response) + with {:ok, response} <- + params + |> Map.put(:user_id, user.id) + |> Map.put(:hide, params[:irreversible]) + |> Map.delete(:irreversible) + |> Filter.create() do + render(conn, "show.json", filter: response) + end end @doc "GET /api/v1/filters/:id" def show(%{assigns: %{user: user}} = conn, %{id: filter_id}) do - filter = Filter.get(filter_id, user) - - render(conn, "show.json", filter: filter) + with %Filter{} = filter <- Filter.get(filter_id, user) do + render(conn, "show.json", filter: filter) + else + nil -> {:error, :not_found} + end end @doc "PUT /api/v1/filters/:id" @@ -56,28 +56,31 @@ def update( %{id: filter_id} ) do params = - params - |> Map.delete(:irreversible) - |> Map.put(:hide, params[:irreversible]) - |> Enum.reject(fn {_key, value} -> is_nil(value) end) - |> Map.new() - - # TODO: support `expires_in` parameter (as in Mastodon API) + if is_boolean(params[:irreversible]) do + params + |> Map.put(:hide, params[:irreversible]) + |> Map.delete(:irreversible) + else + params + end with %Filter{} = filter <- Filter.get(filter_id, user), {:ok, %Filter{} = filter} <- Filter.update(filter, params) do render(conn, "show.json", filter: filter) + else + nil -> {:error, :not_found} + error -> error end end @doc "DELETE /api/v1/filters/:id" def delete(%{assigns: %{user: user}} = conn, %{id: filter_id}) do - query = %Filter{ - user_id: user.id, - filter_id: filter_id - } - - {:ok, _} = Filter.delete(query) - json(conn, %{}) + with %Filter{} = filter <- Filter.get(filter_id, user), + {:ok, _} <- Filter.delete(filter) do + json(conn, %{}) + else + nil -> {:error, :not_found} + error -> error + end end end diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index 450aae042..da3264149 100644 --- a/lib/pleroma/web/o_status/o_status_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -73,12 +73,8 @@ def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do cond do format in ["json", "activity+json"] -> - if activity.local do - %{data: %{"id" => redirect_url}} = Object.normalize(activity, fetch: false) - redirect(conn, external: redirect_url) - else - {:error, :not_found} - end + %{data: %{"id" => redirect_url}} = Object.normalize(activity, fetch: false) + redirect(conn, external: redirect_url) activity.data["type"] == "Create" -> %Object{} = object = Object.normalize(activity, fetch: false) diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index bca8e679c..165afd3b4 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -56,7 +56,7 @@ def confirmation_resend(conn, params) do nickname_or_email = params[:email] || params[:nickname] with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email), - {:ok, _} <- User.try_send_confirmation_email(user) do + {:ok, _} <- User.maybe_send_confirmation_email(user) do json_response(conn, :no_content, "") end end diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex index fc6d74b42..592b9dcdc 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex @@ -9,7 +9,6 @@ <%= activity_context(@activity) %> - <%= activity_context(@activity) %> <%= if @data["summary"] do %> <%= escape(@data["summary"]) %> @@ -21,6 +20,8 @@ <%= @data["external_url"] %> <% end %> + <%= activity_context(@activity) %> + <%= for tag <- Pleroma.Object.hashtags(@object) do %> <% end %> diff --git a/lib/pleroma/workers/purge_expired_filter.ex b/lib/pleroma/workers/purge_expired_filter.ex new file mode 100644 index 000000000..4740d52e9 --- /dev/null +++ b/lib/pleroma/workers/purge_expired_filter.ex @@ -0,0 +1,43 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredFilter do + @moduledoc """ + Worker which purges expired filters + """ + + use Oban.Worker, queue: :filter_expiration, max_attempts: 1, unique: [period: :infinity] + + import Ecto.Query + + alias Oban.Job + alias Pleroma.Repo + + @spec enqueue(%{filter_id: integer(), expires_at: DateTime.t()}) :: + {:ok, Job.t()} | {:error, Ecto.Changeset.t()} + def enqueue(args) do + {scheduled_at, args} = Map.pop(args, :expires_at) + + args + |> new(scheduled_at: scheduled_at) + |> Oban.insert() + end + + @impl true + def perform(%Job{args: %{"filter_id" => id}}) do + Pleroma.Filter + |> Repo.get(id) + |> Repo.delete() + end + + @spec get_expiration(pos_integer()) :: Job.t() | nil + def get_expiration(id) do + from(j in Job, + where: j.state == "scheduled", + where: j.queue == "filter_expiration", + where: fragment("?->'filter_id' = ?", j.args, ^id) + ) + |> Repo.one() + end +end diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex index cf965999c..a4ab9928d 100644 --- a/lib/pleroma/workers/scheduled_activity_worker.ex +++ b/lib/pleroma/workers/scheduled_activity_worker.ex @@ -9,38 +9,50 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities" - alias Pleroma.Config + alias Pleroma.Repo alias Pleroma.ScheduledActivity alias Pleroma.User - alias Pleroma.Web.CommonAPI require Logger @impl Oban.Worker def perform(%Job{args: %{"activity_id" => activity_id}}) do - if Config.get([ScheduledActivity, :enabled]) do - case Pleroma.Repo.get(ScheduledActivity, activity_id) do - %ScheduledActivity{} = scheduled_activity -> - post_activity(scheduled_activity) + with %ScheduledActivity{} = scheduled_activity <- find_scheduled_activity(activity_id), + %User{} = user <- find_user(scheduled_activity.user_id) do + params = atomize_keys(scheduled_activity.params) - _ -> - Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}") - end + Repo.transaction(fn -> + {:ok, activity} = Pleroma.Web.CommonAPI.post(user, params) + {:ok, _} = ScheduledActivity.delete(scheduled_activity) + activity + end) + else + {:error, :scheduled_activity_not_found} = error -> + Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}") + error + + {:error, :user_not_found} = error -> + Logger.error("#{__MODULE__} Couldn't find user for scheduled activity: #{activity_id}") + error end end - defp post_activity(%ScheduledActivity{user_id: user_id, params: params} = scheduled_activity) do - params = Map.new(params, fn {key, value} -> {String.to_existing_atom(key), value} end) - - with {:delete, {:ok, _}} <- {:delete, ScheduledActivity.delete(scheduled_activity)}, - {:user, %User{} = user} <- {:user, User.get_cached_by_id(user_id)}, - {:post, {:ok, _}} <- {:post, CommonAPI.post(user, params)} do - :ok - else - error -> - Logger.error( - "#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}" - ) + defp find_scheduled_activity(id) do + with nil <- Repo.get(ScheduledActivity, id) do + {:error, :scheduled_activity_not_found} end end + + defp find_user(id) do + with nil <- User.get_cached_by_id(id) do + {:error, :user_not_found} + end + end + + defp atomize_keys(map) do + Map.new(map, fn + {key, value} when is_map(value) -> {String.to_existing_atom(key), atomize_keys(value)} + {key, value} -> {String.to_existing_atom(key), value} + end) + end end diff --git a/priv/repo/migrations/20210121080964_add_default_text_search_config.exs b/priv/repo/migrations/20210121080964_add_default_text_search_config.exs new file mode 100644 index 000000000..09b6cccc9 --- /dev/null +++ b/priv/repo/migrations/20210121080964_add_default_text_search_config.exs @@ -0,0 +1,11 @@ +defmodule Pleroma.Repo.Migrations.AddDefaultTextSearchConfig do + use Ecto.Migration + + def change do + execute("DO $$ + BEGIN + execute 'ALTER DATABASE '||current_database()||' SET default_text_search_config = ''english'' '; + END + $$;") + end +end diff --git a/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs b/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs index 82e02281d..88476fb57 100644 --- a/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs +++ b/priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs @@ -17,7 +17,7 @@ def up do execute("CREATE FUNCTION objects_fts_update() RETURNS trigger AS $$ begin - new.fts_content := to_tsvector('english', new.data->>'content'); + new.fts_content := to_tsvector(new.data->>'content'); return new; end $$ LANGUAGE plpgsql") diff --git a/test/config/emoji.txt b/test/config/emoji.txt new file mode 100644 index 000000000..14dd0c332 --- /dev/null +++ b/test/config/emoji.txt @@ -0,0 +1 @@ +external_emoji, https://example.com/emoji.png diff --git a/test/pleroma/config/deprecation_warnings_test.exs b/test/pleroma/config/deprecation_warnings_test.exs index 37e02fae2..15f4982ea 100644 --- a/test/pleroma/config/deprecation_warnings_test.exs +++ b/test/pleroma/config/deprecation_warnings_test.exs @@ -87,7 +87,7 @@ test "check_hellthread_threshold/0" do end test "check_activity_expiration_config/0" do - clear_config(Pleroma.ActivityExpiration, enabled: true) + clear_config([Pleroma.ActivityExpiration], enabled: true) assert capture_log(fn -> DeprecationWarnings.check_activity_expiration_config() @@ -95,7 +95,7 @@ test "check_activity_expiration_config/0" do end test "check_uploders_s3_public_endpoint/0" do - clear_config(Pleroma.Uploaders.S3, public_endpoint: "https://fake.amazonaws.com/bucket/") + clear_config([Pleroma.Uploaders.S3], public_endpoint: "https://fake.amazonaws.com/bucket/") assert capture_log(fn -> DeprecationWarnings.check_uploders_s3_public_endpoint() diff --git a/test/pleroma/filter_test.exs b/test/pleroma/filter_test.exs index a9e256e8c..19ad6b8c0 100644 --- a/test/pleroma/filter_test.exs +++ b/test/pleroma/filter_test.exs @@ -7,81 +7,120 @@ defmodule Pleroma.FilterTest do import Pleroma.Factory + alias Oban.Job alias Pleroma.Filter - alias Pleroma.Repo + + setup do + [user: insert(:user)] + end describe "creating filters" do - test "creating one filter" do - user = insert(:user) + test "creation validation error", %{user: user} do + attrs = %{ + user_id: user.id, + expires_in: 60 + } - query = %Filter{ + {:error, _} = Filter.create(attrs) + + assert Repo.all(Job) == [] + end + + test "use passed expires_at instead expires_in", %{user: user} do + now = NaiveDateTime.utc_now() + + attrs = %{ + user_id: user.id, + expires_at: now, + phrase: "knights", + context: ["home"], + expires_in: 600 + } + + {:ok, %Filter{} = filter} = Filter.create(attrs) + + result = Filter.get(filter.filter_id, user) + assert result.expires_at == NaiveDateTime.truncate(now, :second) + + [job] = Repo.all(Job) + + assert DateTime.truncate(job.scheduled_at, :second) == + now |> NaiveDateTime.truncate(:second) |> DateTime.from_naive!("Etc/UTC") + end + + test "creating one filter", %{user: user} do + attrs = %{ user_id: user.id, filter_id: 42, phrase: "knights", context: ["home"] } - {:ok, %Filter{} = filter} = Filter.create(query) + {:ok, %Filter{} = filter} = Filter.create(attrs) result = Filter.get(filter.filter_id, user) - assert query.phrase == result.phrase + assert attrs.phrase == result.phrase end - test "creating one filter without a pre-defined filter_id" do - user = insert(:user) + test "creating with expired_at", %{user: user} do + attrs = %{ + user_id: user.id, + filter_id: 42, + phrase: "knights", + context: ["home"], + expires_in: 60 + } - query = %Filter{ + {:ok, %Filter{} = filter} = Filter.create(attrs) + result = Filter.get(filter.filter_id, user) + assert attrs.phrase == result.phrase + + assert [_] = Repo.all(Job) + end + + test "creating one filter without a pre-defined filter_id", %{user: user} do + attrs = %{ user_id: user.id, phrase: "knights", context: ["home"] } - {:ok, %Filter{} = filter} = Filter.create(query) + {:ok, %Filter{} = filter} = Filter.create(attrs) # Should start at 1 assert filter.filter_id == 1 end - test "creating additional filters uses previous highest filter_id + 1" do - user = insert(:user) + test "creating additional filters uses previous highest filter_id + 1", %{user: user} do + filter1 = insert(:filter, user: user) - query_one = %Filter{ - user_id: user.id, - filter_id: 42, - phrase: "knights", - context: ["home"] - } - - {:ok, %Filter{} = filter_one} = Filter.create(query_one) - - query_two = %Filter{ + attrs = %{ user_id: user.id, # No filter_id phrase: "who", context: ["home"] } - {:ok, %Filter{} = filter_two} = Filter.create(query_two) - assert filter_two.filter_id == filter_one.filter_id + 1 + {:ok, %Filter{} = filter2} = Filter.create(attrs) + assert filter2.filter_id == filter1.filter_id + 1 end - test "filter_id is unique per user" do - user_one = insert(:user) + test "filter_id is unique per user", %{user: user_one} do user_two = insert(:user) - query_one = %Filter{ + attrs1 = %{ user_id: user_one.id, phrase: "knights", context: ["home"] } - {:ok, %Filter{} = filter_one} = Filter.create(query_one) + {:ok, %Filter{} = filter_one} = Filter.create(attrs1) - query_two = %Filter{ + attrs2 = %{ user_id: user_two.id, phrase: "who", context: ["home"] } - {:ok, %Filter{} = filter_two} = Filter.create(query_two) + {:ok, %Filter{} = filter_two} = Filter.create(attrs2) assert filter_one.filter_id == 1 assert filter_two.filter_id == 1 @@ -94,65 +133,61 @@ test "filter_id is unique per user" do end end - test "deleting a filter" do - user = insert(:user) + test "deleting a filter", %{user: user} do + filter = insert(:filter, user: user) - query = %Filter{ - user_id: user.id, - filter_id: 0, - phrase: "knights", - context: ["home"] - } - - {:ok, _filter} = Filter.create(query) - {:ok, filter} = Filter.delete(query) - assert is_nil(Repo.get(Filter, filter.filter_id)) + assert Repo.get(Filter, filter.id) + {:ok, filter} = Filter.delete(filter) + refute Repo.get(Filter, filter.id) end - test "getting all filters by an user" do - user = insert(:user) - - query_one = %Filter{ + test "deleting a filter with expires_at is removing Oban job too", %{user: user} do + attrs = %{ user_id: user.id, - filter_id: 1, - phrase: "knights", - context: ["home"] + phrase: "cofe", + context: ["home"], + expires_in: 600 } - query_two = %Filter{ - user_id: user.id, - filter_id: 2, - phrase: "who", - context: ["home"] - } + {:ok, filter} = Filter.create(attrs) + assert %Job{id: job_id} = Pleroma.Workers.PurgeExpiredFilter.get_expiration(filter.id) + {:ok, _} = Filter.delete(filter) - {:ok, filter_one} = Filter.create(query_one) - {:ok, filter_two} = Filter.create(query_two) - filters = Filter.get_filters(user) - assert filter_one in filters - assert filter_two in filters + assert Repo.get(Job, job_id) == nil end - test "updating a filter" do - user = insert(:user) + test "getting all filters by an user", %{user: user} do + filter1 = insert(:filter, user: user) + filter2 = insert(:filter, user: user) - query_one = %Filter{ - user_id: user.id, - filter_id: 1, - phrase: "knights", - context: ["home"] - } + filter_ids = user |> Filter.get_filters() |> collect_ids() + + assert filter1.id in filter_ids + assert filter2.id in filter_ids + end + + test "updating a filter", %{user: user} do + filter = insert(:filter, user: user) changes = %{ phrase: "who", context: ["home", "timeline"] } - {:ok, filter_one} = Filter.create(query_one) - {:ok, filter_two} = Filter.update(filter_one, changes) + {:ok, updated_filter} = Filter.update(filter, changes) - assert filter_one != filter_two - assert filter_two.phrase == changes.phrase - assert filter_two.context == changes.context + assert filter != updated_filter + assert updated_filter.phrase == changes.phrase + assert updated_filter.context == changes.context + end + + test "updating with error", %{user: user} do + filter = insert(:filter, user: user) + + changes = %{ + phrase: nil + } + + {:error, _} = Filter.update(filter, changes) end end diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index 0c6ebfb76..948587292 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -45,6 +45,20 @@ test "creates a notification for a report" do assert notification.type == "pleroma:report" end + test "suppresses notification to reporter if reporter is an admin" do + reporting_admin = insert(:user, is_admin: true) + reported_user = insert(:user) + other_admin = insert(:user, is_admin: true) + + {:ok, activity} = CommonAPI.report(reporting_admin, %{account_id: reported_user.id}) + + {:ok, [notification]} = Notification.create_notifications(activity) + + refute notification.user_id == reporting_admin.id + assert notification.user_id == other_admin.id + assert notification.type == "pleroma:report" + end + test "creates a notification for an emoji reaction" do user = insert(:user) other_user = insert(:user) diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index be39339f3..6f5bcab57 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -551,6 +551,27 @@ test "sends a pending approval email" do ) end + test "it sends a registration confirmed email if no others will be sent" do + clear_config([:welcome, :email, :enabled], false) + clear_config([:instance, :account_activation_required], false) + clear_config([:instance, :account_approval_required], false) + + {:ok, user} = + User.register_changeset(%User{}, @full_user_data) + |> User.register() + + ObanHelpers.perform_all() + + instance_name = Pleroma.Config.get([:instance, :name]) + sender = Pleroma.Config.get([:instance, :notify_email]) + + assert_email_sent( + from: {instance_name, sender}, + to: {user.name, user.email}, + subject: "Account registered on #{instance_name}" + ) + end + test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do clear_config([:instance, :account_activation_required], true) diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 91a3109bb..5e53b8afc 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -229,6 +229,24 @@ test "it doesn't return a local-only object", %{conn: conn} do assert json_response(conn, 404) end + test "returns local-only objects when authenticated", %{conn: conn} do + user = insert(:user) + {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"}) + + assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post) + + object = Object.normalize(post, fetch: false) + uuid = String.split(object.data["id"], "/") |> List.last() + + assert response = + conn + |> assign(:user, user) + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}") + + assert json_response(response, 200) == ObjectView.render("object.json", %{object: object}) + end + test "it returns a json representation of the object with accept application/json", %{ conn: conn } do @@ -285,6 +303,28 @@ test "it returns 404 for non-public messages", %{conn: conn} do assert json_response(conn, 404) end + test "returns visible non-public messages when authenticated", %{conn: conn} do + note = insert(:direct_note) + uuid = String.split(note.data["id"], "/") |> List.last() + user = User.get_by_ap_id(note.data["actor"]) + marisa = insert(:user) + + assert conn + |> assign(:user, marisa) + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}") + |> json_response(404) + + assert response = + conn + |> assign(:user, user) + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}") + |> json_response(200) + + assert response == ObjectView.render("object.json", %{object: note}) + end + test "it returns 404 for tombstone objects", %{conn: conn} do tombstone = insert(:tombstone) uuid = String.split(tombstone.data["id"], "/") |> List.last() @@ -358,6 +398,23 @@ test "it doesn't return a local-only activity", %{conn: conn} do assert json_response(conn, 404) end + test "returns local-only activities when authenticated", %{conn: conn} do + user = insert(:user) + {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"}) + + assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post) + + uuid = String.split(post.data["id"], "/") |> List.last() + + assert response = + conn + |> assign(:user, user) + |> put_req_header("accept", "application/activity+json") + |> get("/activities/#{uuid}") + + assert json_response(response, 200) == ObjectView.render("object.json", %{object: post}) + end + test "it returns a json representation of the activity", %{conn: conn} do activity = insert(:note_activity) uuid = String.split(activity.data["id"], "/") |> List.last() @@ -382,6 +439,28 @@ test "it returns 404 for non-public activities", %{conn: conn} do assert json_response(conn, 404) end + test "returns visible non-public messages when authenticated", %{conn: conn} do + note = insert(:direct_note_activity) + uuid = String.split(note.data["id"], "/") |> List.last() + user = User.get_by_ap_id(note.data["actor"]) + marisa = insert(:user) + + assert conn + |> assign(:user, marisa) + |> put_req_header("accept", "application/activity+json") + |> get("/activities/#{uuid}") + |> json_response(404) + + assert response = + conn + |> assign(:user, user) + |> put_req_header("accept", "application/activity+json") + |> get("/activities/#{uuid}") + |> json_response(200) + + assert response == ObjectView.render("object.json", %{object: note}) + end + test "it caches a response", %{conn: conn} do activity = insert(:note_activity) uuid = String.split(activity.data["id"], "/") |> List.last() diff --git a/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs b/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs new file mode 100644 index 000000000..fbcf68414 --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs @@ -0,0 +1,154 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicyTest do + use Pleroma.DataCase + alias Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy + + setup_all do: clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy]) + + test "Notes with content are exempt" do + message = %{ + "actor" => "http://localhost:4001/users/testuser", + "cc" => ["http://localhost:4001/users/testuser/followers"], + "object" => %{ + "actor" => "http://localhost:4001/users/testuser", + "attachment" => [], + "cc" => ["http://localhost:4001/users/testuser/followers"], + "source" => "this is a test post", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "type" => "Note" + }, + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "type" => "Create" + } + + assert NoEmptyPolicy.filter(message) == {:ok, message} + end + + test "Polls are exempt" do + message = %{ + "actor" => "http://localhost:4001/users/testuser", + "cc" => ["http://localhost:4001/users/testuser/followers"], + "object" => %{ + "actor" => "http://localhost:4001/users/testuser", + "attachment" => [], + "cc" => ["http://localhost:4001/users/testuser/followers"], + "oneOf" => [ + %{ + "name" => "chocolate", + "replies" => %{"totalItems" => 0, "type" => "Collection"}, + "type" => "Note" + }, + %{ + "name" => "vanilla", + "replies" => %{"totalItems" => 0, "type" => "Collection"}, + "type" => "Note" + } + ], + "source" => "@user2", + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Question" + }, + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Create" + } + + assert NoEmptyPolicy.filter(message) == {:ok, message} + end + + test "Notes with attachments are exempt" do + message = %{ + "actor" => "http://localhost:4001/users/testuser", + "cc" => ["http://localhost:4001/users/testuser/followers"], + "object" => %{ + "actor" => "http://localhost:4001/users/testuser", + "attachment" => [ + %{ + "actor" => "http://localhost:4001/users/testuser", + "mediaType" => "image/png", + "name" => "", + "type" => "Document", + "url" => [ + %{ + "href" => + "http://localhost:4001/media/68ba231cf12e1382ce458f1979969f8ed5cc07ba198a02e653464abaf39bdb90.png", + "mediaType" => "image/png", + "type" => "Link" + } + ] + } + ], + "cc" => ["http://localhost:4001/users/testuser/followers"], + "source" => "@user2", + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Note" + }, + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Create" + } + + assert NoEmptyPolicy.filter(message) == {:ok, message} + end + + test "Notes with only mentions are denied" do + message = %{ + "actor" => "http://localhost:4001/users/testuser", + "cc" => ["http://localhost:4001/users/testuser/followers"], + "object" => %{ + "actor" => "http://localhost:4001/users/testuser", + "attachment" => [], + "cc" => ["http://localhost:4001/users/testuser/followers"], + "source" => "@user2", + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Note" + }, + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + "http://localhost:4001/users/user2" + ], + "type" => "Create" + } + + assert NoEmptyPolicy.filter(message) == {:reject, "[NoEmptyPolicy]"} + end + + test "Notes with no content are denied" do + message = %{ + "actor" => "http://localhost:4001/users/testuser", + "cc" => ["http://localhost:4001/users/testuser/followers"], + "object" => %{ + "actor" => "http://localhost:4001/users/testuser", + "attachment" => [], + "cc" => ["http://localhost:4001/users/testuser/followers"], + "source" => "", + "to" => [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type" => "Note" + }, + "to" => [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type" => "Create" + } + + assert NoEmptyPolicy.filter(message) == {:reject, "[NoEmptyPolicy]"} + end +end diff --git a/test/pleroma/web/activity_pub/visibility_test.exs b/test/pleroma/web/activity_pub/visibility_test.exs index d8544279a..23485225d 100644 --- a/test/pleroma/web/activity_pub/visibility_test.exs +++ b/test/pleroma/web/activity_pub/visibility_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do use Pleroma.DataCase, async: true alias Pleroma.Activity + alias Pleroma.Object alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI import Pleroma.Factory @@ -107,7 +108,7 @@ test "is_list?", %{ assert Visibility.is_list?(list) end - test "visible_for_user?", %{ + test "visible_for_user? Activity", %{ public: public, private: private, direct: direct, @@ -149,10 +150,76 @@ test "visible_for_user?", %{ refute Visibility.visible_for_user?(private, unrelated) refute Visibility.visible_for_user?(direct, unrelated) + # Public and unlisted visible for unauthenticated + + assert Visibility.visible_for_user?(public, nil) + assert Visibility.visible_for_user?(unlisted, nil) + refute Visibility.visible_for_user?(private, nil) + refute Visibility.visible_for_user?(direct, nil) + # Visible for a list member assert Visibility.visible_for_user?(list, unrelated) end + test "visible_for_user? Object", %{ + public: public, + private: private, + direct: direct, + unlisted: unlisted, + user: user, + mentioned: mentioned, + following: following, + unrelated: unrelated, + list: list + } do + public = Object.normalize(public) + private = Object.normalize(private) + unlisted = Object.normalize(unlisted) + direct = Object.normalize(direct) + list = Object.normalize(list) + + # All visible to author + + assert Visibility.visible_for_user?(public, user) + assert Visibility.visible_for_user?(private, user) + assert Visibility.visible_for_user?(unlisted, user) + assert Visibility.visible_for_user?(direct, user) + assert Visibility.visible_for_user?(list, user) + + # All visible to a mentioned user + + assert Visibility.visible_for_user?(public, mentioned) + assert Visibility.visible_for_user?(private, mentioned) + assert Visibility.visible_for_user?(unlisted, mentioned) + assert Visibility.visible_for_user?(direct, mentioned) + assert Visibility.visible_for_user?(list, mentioned) + + # DM not visible for just follower + + assert Visibility.visible_for_user?(public, following) + assert Visibility.visible_for_user?(private, following) + assert Visibility.visible_for_user?(unlisted, following) + refute Visibility.visible_for_user?(direct, following) + refute Visibility.visible_for_user?(list, following) + + # Public and unlisted visible for unrelated user + + assert Visibility.visible_for_user?(public, unrelated) + assert Visibility.visible_for_user?(unlisted, unrelated) + refute Visibility.visible_for_user?(private, unrelated) + refute Visibility.visible_for_user?(direct, unrelated) + + # Public and unlisted visible for unauthenticated + + assert Visibility.visible_for_user?(public, nil) + assert Visibility.visible_for_user?(unlisted, nil) + refute Visibility.visible_for_user?(private, nil) + refute Visibility.visible_for_user?(direct, nil) + + # Visible for a list member + # assert Visibility.visible_for_user?(list, unrelated) + end + test "doesn't die when the user doesn't exist", %{ direct: direct, diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs index 23e4bc3af..e7688c728 100644 --- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs @@ -405,13 +405,9 @@ test "need_reboot flag", %{conn: conn} do setup do user = insert(:user) - date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!() - date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!() - date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!() - - insert(:note_activity, user: user, published: date1) - insert(:note_activity, user: user, published: date2) - insert(:note_activity, user: user, published: date3) + insert(:note_activity, user: user) + insert(:note_activity, user: user) + insert(:note_activity, user: user) %{user: user} end @@ -419,23 +415,22 @@ test "need_reboot flag", %{conn: conn} do test "renders user's statuses", %{conn: conn, user: user} do conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses") - assert json_response(conn, 200) |> length() == 3 + assert %{"total" => 3, "activities" => activities} = json_response(conn, 200) + assert length(activities) == 3 end test "renders user's statuses with pagination", %{conn: conn, user: user} do - conn1 = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=1") + %{"total" => 3, "activities" => [activity1]} = + conn + |> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=1") + |> json_response(200) - response1 = json_response(conn1, 200) + %{"total" => 3, "activities" => [activity2]} = + conn + |> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=2") + |> json_response(200) - assert response1 |> length() == 1 - - conn2 = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=2") - - response2 = json_response(conn2, 200) - - assert response2 |> length() == 1 - - refute response1 == response2 + refute activity1 == activity2 end test "doesn't return private statuses by default", %{conn: conn, user: user} do @@ -443,9 +438,12 @@ test "doesn't return private statuses by default", %{conn: conn, user: user} do {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"}) - conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses") + %{"total" => 4, "activities" => activities} = + conn + |> get("/api/pleroma/admin/users/#{user.nickname}/statuses") + |> json_response(200) - assert json_response(conn, 200) |> length() == 4 + assert length(activities) == 4 end test "returns private statuses with godmode on", %{conn: conn, user: user} do @@ -453,9 +451,12 @@ test "returns private statuses with godmode on", %{conn: conn, user: user} do {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"}) - conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true") + %{"total" => 5, "activities" => activities} = + conn + |> get("/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true") + |> json_response(200) - assert json_response(conn, 200) |> length() == 5 + assert length(activities) == 5 end test "excludes reblogs by default", %{conn: conn, user: user} do @@ -463,13 +464,17 @@ test "excludes reblogs by default", %{conn: conn, user: user} do {:ok, activity} = CommonAPI.post(user, %{status: "."}) {:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user) - conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses") - assert json_response(conn_res, 200) |> length() == 0 + assert %{"total" => 0, "activities" => []} == + conn + |> get("/api/pleroma/admin/users/#{other_user.nickname}/statuses") + |> json_response(200) - conn_res = - get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true") - - assert json_response(conn_res, 200) |> length() == 1 + assert %{"total" => 1, "activities" => [_]} = + conn + |> get( + "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true" + ) + |> json_response(200) end end @@ -859,33 +864,30 @@ test "GET /instances/:instance/statuses", %{conn: conn} do insert_pair(:note_activity, user: user) activity = insert(:note_activity, user: user2) - ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses") + %{"total" => 2, "activities" => activities} = + conn |> get("/api/pleroma/admin/instances/archae.me/statuses") |> json_response(200) - response = json_response(ret_conn, 200) + assert length(activities) == 2 - assert length(response) == 2 + %{"total" => 1, "activities" => [_]} = + conn |> get("/api/pleroma/admin/instances/test.com/statuses") |> json_response(200) - ret_conn = get(conn, "/api/pleroma/admin/instances/test.com/statuses") - - response = json_response(ret_conn, 200) - - assert length(response) == 1 - - ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses") - - response = json_response(ret_conn, 200) - - assert Enum.empty?(response) + %{"total" => 0, "activities" => []} = + conn |> get("/api/pleroma/admin/instances/nonexistent.com/statuses") |> json_response(200) CommonAPI.repeat(activity.id, user) - ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses") - response = json_response(ret_conn, 200) - assert length(response) == 2 + %{"total" => 2, "activities" => activities} = + conn |> get("/api/pleroma/admin/instances/archae.me/statuses") |> json_response(200) - ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true") - response = json_response(ret_conn, 200) - assert length(response) == 3 + assert length(activities) == 2 + + %{"total" => 3, "activities" => activities} = + conn + |> get("/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true") + |> json_response(200) + + assert length(activities) == 3 end end diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs index 77688c7a3..578a4c914 100644 --- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs @@ -31,7 +31,7 @@ test "when configuration from database is off", %{conn: conn} do assert json_response_and_validate_schema(conn, 400) == %{ - "error" => "To use this endpoint you need to enable configuration from database." + "error" => "You must enable configurable_from_database in your config file." } end @@ -170,7 +170,7 @@ test "POST /api/pleroma/admin/config with configdb disabled", %{conn: conn} do |> post("/api/pleroma/admin/config", %{"configs" => []}) assert json_response_and_validate_schema(conn, 400) == - %{"error" => "To use this endpoint you need to enable configuration from database."} + %{"error" => "You must enable configurable_from_database in your config file."} end describe "POST /api/pleroma/admin/config" do diff --git a/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs index dc6739178..98ab9e717 100644 --- a/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs @@ -4,149 +4,412 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do use Pleroma.Web.ConnCase, async: true + use Oban.Testing, repo: Pleroma.Repo - alias Pleroma.Web.MastodonAPI.FilterView + import Pleroma.Factory - test "creating a filter" do - %{conn: conn} = oauth_access(["write:filters"]) + alias Pleroma.Filter + alias Pleroma.Repo + alias Pleroma.Workers.PurgeExpiredFilter - filter = %Pleroma.Filter{ - phrase: "knights", - context: ["home"] - } - - conn = + test "non authenticated creation request", %{conn: conn} do + response = conn |> put_req_header("content-type", "application/json") - |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) + |> post("/api/v1/filters", %{"phrase" => "knights", context: ["home"]}) + |> json_response(403) - assert response = json_response_and_validate_schema(conn, 200) - assert response["phrase"] == filter.phrase - assert response["context"] == filter.context - assert response["irreversible"] == false - assert response["id"] != nil - assert response["id"] != "" + assert response["error"] == "Invalid credentials." + end + + describe "creating" do + setup do: oauth_access(["write:filters"]) + + test "a common filter", %{conn: conn, user: user} do + params = %{ + phrase: "knights", + context: ["home"], + irreversible: true + } + + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/filters", params) + |> json_response_and_validate_schema(200) + + assert response["phrase"] == params.phrase + assert response["context"] == params.context + assert response["irreversible"] == true + assert response["id"] != nil + assert response["id"] != "" + assert response["expires_at"] == nil + + filter = Filter.get(response["id"], user) + assert filter.hide == true + end + + test "a filter with expires_in", %{conn: conn, user: user} do + in_seconds = 600 + + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/filters", %{ + "phrase" => "knights", + context: ["home"], + expires_in: in_seconds + }) + |> json_response_and_validate_schema(200) + + assert response["irreversible"] == false + + expires_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(in_seconds) + |> Pleroma.Web.CommonAPI.Utils.to_masto_date() + + assert response["expires_at"] == expires_at + + filter = Filter.get(response["id"], user) + + id = filter.id + + assert_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: filter.id} + ) + + assert {:ok, %{id: ^id}} = + perform_job(PurgeExpiredFilter, %{ + filter_id: filter.id + }) + + assert Repo.aggregate(Filter, :count, :id) == 0 + end end test "fetching a list of filters" do %{user: user, conn: conn} = oauth_access(["read:filters"]) - query_one = %Pleroma.Filter{ - user_id: user.id, - filter_id: 1, - phrase: "knights", - context: ["home"] - } + %{filter_id: id1} = insert(:filter, user: user) + %{filter_id: id2} = insert(:filter, user: user) - query_two = %Pleroma.Filter{ - user_id: user.id, - filter_id: 2, - phrase: "who", - context: ["home"] - } + id1 = to_string(id1) + id2 = to_string(id2) - {:ok, filter_one} = Pleroma.Filter.create(query_one) - {:ok, filter_two} = Pleroma.Filter.create(query_two) + assert [%{"id" => ^id2}, %{"id" => ^id1}] = + conn + |> get("/api/v1/filters") + |> json_response_and_validate_schema(200) + end + + test "fetching a list of filters without token", %{conn: conn} do + insert(:filter) response = conn |> get("/api/v1/filters") - |> json_response_and_validate_schema(200) + |> json_response(403) - assert response == - render_json( - FilterView, - "index.json", - filters: [filter_two, filter_one] - ) + assert response["error"] == "Invalid credentials." end test "get a filter" do %{user: user, conn: conn} = oauth_access(["read:filters"]) # check whole_word false - query = %Pleroma.Filter{ - user_id: user.id, - filter_id: 2, - phrase: "knight", - context: ["home"], - whole_word: false - } + filter = insert(:filter, user: user, whole_word: false) - {:ok, filter} = Pleroma.Filter.create(query) + resp1 = + conn |> get("/api/v1/filters/#{filter.filter_id}") |> json_response_and_validate_schema(200) - conn = get(conn, "/api/v1/filters/#{filter.filter_id}") - - assert response = json_response_and_validate_schema(conn, 200) - assert response["whole_word"] == false + assert resp1["whole_word"] == false # check whole_word true - %{user: user, conn: conn} = oauth_access(["read:filters"]) + filter = insert(:filter, user: user, whole_word: true) - query = %Pleroma.Filter{ - user_id: user.id, - filter_id: 3, - phrase: "knight", - context: ["home"], - whole_word: true - } + resp2 = + conn |> get("/api/v1/filters/#{filter.filter_id}") |> json_response_and_validate_schema(200) - {:ok, filter} = Pleroma.Filter.create(query) - - conn = get(conn, "/api/v1/filters/#{filter.filter_id}") - - assert response = json_response_and_validate_schema(conn, 200) - assert response["whole_word"] == true + assert resp2["whole_word"] == true end - test "update a filter" do - %{user: user, conn: conn} = oauth_access(["write:filters"]) + test "get a filter not_found error" do + filter = insert(:filter) + %{conn: conn} = oauth_access(["read:filters"]) - query = %Pleroma.Filter{ - user_id: user.id, - filter_id: 2, - phrase: "knight", - context: ["home"], - hide: true, - whole_word: true - } + response = + conn |> get("/api/v1/filters/#{filter.filter_id}") |> json_response_and_validate_schema(404) - {:ok, _filter} = Pleroma.Filter.create(query) + assert response["error"] == "Record not found" + end - new = %Pleroma.Filter{ - phrase: "nii", - context: ["home"] - } + describe "updating a filter" do + setup do: oauth_access(["write:filters"]) - conn = + test "common" do + %{conn: conn, user: user} = oauth_access(["write:filters"]) + + filter = + insert(:filter, + user: user, + hide: true, + whole_word: true + ) + + params = %{ + phrase: "nii", + context: ["public"], + irreversible: false + } + + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/filters/#{filter.filter_id}", params) + |> json_response_and_validate_schema(200) + + assert response["phrase"] == params.phrase + assert response["context"] == params.context + assert response["irreversible"] == false + assert response["whole_word"] == true + end + + test "with adding expires_at", %{conn: conn, user: user} do + filter = insert(:filter, user: user) + in_seconds = 600 + + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/filters/#{filter.filter_id}", %{ + phrase: "nii", + context: ["public"], + expires_in: in_seconds, + irreversible: true + }) + |> json_response_and_validate_schema(200) + + assert response["irreversible"] == true + + assert response["expires_at"] == + NaiveDateTime.utc_now() + |> NaiveDateTime.add(in_seconds) + |> Pleroma.Web.CommonAPI.Utils.to_masto_date() + + filter = Filter.get(response["id"], user) + + id = filter.id + + assert_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: id} + ) + + assert {:ok, %{id: ^id}} = + perform_job(PurgeExpiredFilter, %{ + filter_id: id + }) + + assert Repo.aggregate(Filter, :count, :id) == 0 + end + + test "with removing expires_at", %{conn: conn, user: user} do + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/filters", %{ + phrase: "cofe", + context: ["home"], + expires_in: 600 + }) + |> json_response_and_validate_schema(200) + + filter = Filter.get(response["id"], user) + + assert_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: filter.id} + ) + + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/filters/#{filter.filter_id}", %{ + phrase: "nii", + context: ["public"], + expires_in: nil, + whole_word: true + }) + |> json_response_and_validate_schema(200) + + refute_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: filter.id} + ) + + assert response["irreversible"] == false + assert response["whole_word"] == true + assert response["expires_at"] == nil + end + + test "expires_at is the same in create and update so job is in db", %{conn: conn, user: user} do + resp1 = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/filters", %{ + phrase: "cofe", + context: ["home"], + expires_in: 600 + }) + |> json_response_and_validate_schema(200) + + filter = Filter.get(resp1["id"], user) + + assert_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: filter.id} + ) + + job = PurgeExpiredFilter.get_expiration(filter.id) + + resp2 = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/filters/#{filter.filter_id}", %{ + phrase: "nii", + context: ["public"] + }) + |> json_response_and_validate_schema(200) + + updated_filter = Filter.get(resp2["id"], user) + + assert_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: updated_filter.id} + ) + + after_update = PurgeExpiredFilter.get_expiration(updated_filter.id) + + assert resp1["expires_at"] == resp2["expires_at"] + + assert job.scheduled_at == after_update.scheduled_at + end + + test "updating expires_at updates oban job too", %{conn: conn, user: user} do + resp1 = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/filters", %{ + phrase: "cofe", + context: ["home"], + expires_in: 600 + }) + |> json_response_and_validate_schema(200) + + filter = Filter.get(resp1["id"], user) + + assert_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: filter.id} + ) + + job = PurgeExpiredFilter.get_expiration(filter.id) + + resp2 = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/filters/#{filter.filter_id}", %{ + phrase: "nii", + context: ["public"], + expires_in: 300 + }) + |> json_response_and_validate_schema(200) + + updated_filter = Filter.get(resp2["id"], user) + + assert_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: updated_filter.id} + ) + + after_update = PurgeExpiredFilter.get_expiration(updated_filter.id) + + refute resp1["expires_at"] == resp2["expires_at"] + + refute job.scheduled_at == after_update.scheduled_at + end + end + + test "update filter without token", %{conn: conn} do + filter = insert(:filter) + + response = conn |> put_req_header("content-type", "application/json") - |> put("/api/v1/filters/#{query.filter_id}", %{ - phrase: new.phrase, - context: new.context + |> put("/api/v1/filters/#{filter.filter_id}", %{ + phrase: "nii", + context: ["public"] }) + |> json_response(403) - assert response = json_response_and_validate_schema(conn, 200) - assert response["phrase"] == new.phrase - assert response["context"] == new.context - assert response["irreversible"] == true - assert response["whole_word"] == true + assert response["error"] == "Invalid credentials." end - test "delete a filter" do - %{user: user, conn: conn} = oauth_access(["write:filters"]) + describe "delete a filter" do + setup do: oauth_access(["write:filters"]) - query = %Pleroma.Filter{ - user_id: user.id, - filter_id: 2, - phrase: "knight", - context: ["home"] - } + test "common", %{conn: conn, user: user} do + filter = insert(:filter, user: user) - {:ok, filter} = Pleroma.Filter.create(query) + assert conn + |> delete("/api/v1/filters/#{filter.filter_id}") + |> json_response_and_validate_schema(200) == %{} - conn = delete(conn, "/api/v1/filters/#{filter.filter_id}") + assert Repo.all(Filter) == [] + end - assert json_response_and_validate_schema(conn, 200) == %{} + test "with expires_at", %{conn: conn, user: user} do + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/filters", %{ + phrase: "cofe", + context: ["home"], + expires_in: 600 + }) + |> json_response_and_validate_schema(200) + + filter = Filter.get(response["id"], user) + + assert_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: filter.id} + ) + + assert conn + |> delete("/api/v1/filters/#{filter.filter_id}") + |> json_response_and_validate_schema(200) == %{} + + refute_enqueued( + worker: PurgeExpiredFilter, + args: %{filter_id: filter.id} + ) + + assert Repo.all(Filter) == [] + assert Repo.all(Oban.Job) == [] + end + end + + test "delete a filter without token", %{conn: conn} do + filter = insert(:filter) + + response = + conn + |> delete("/api/v1/filters/#{filter.filter_id}") + |> json_response(403) + + assert response["error"] == "Invalid credentials." end end diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 3c73eb514..dcd1e6d5b 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -516,7 +516,7 @@ test "posting a poll", %{conn: conn} do end) assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 - refute response["poll"]["expred"] + assert response["poll"]["expired"] == false question = Object.get_by_id(response["poll"]["id"]) @@ -592,6 +592,44 @@ test "maximum date limit is enforced", %{conn: conn} do %{"error" => error} = json_response_and_validate_schema(conn, 422) assert error == "Expiration date is too far in the future" end + + test "scheduled poll", %{conn: conn} do + clear_config([ScheduledActivity, :enabled], true) + + scheduled_at = + NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond) + |> NaiveDateTime.to_iso8601() + |> Kernel.<>("Z") + + %{"id" => scheduled_id} = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "very cool poll", + "poll" => %{ + "options" => ~w(a b c), + "expires_in" => 420 + }, + "scheduled_at" => scheduled_at + }) + |> json_response_and_validate_schema(200) + + assert {:ok, %{id: activity_id}} = + perform_job(Pleroma.Workers.ScheduledActivityWorker, %{ + activity_id: scheduled_id + }) + + assert Repo.all(Oban.Job) == [] + + object = + Activity + |> Repo.get(activity_id) + |> Object.normalize() + + assert object.data["content"] == "very cool poll" + assert object.data["type"] == "Question" + assert length(object.data["oneOf"]) == 3 + end end test "get a status" do diff --git a/test/pleroma/web/o_status/o_status_controller_test.exs b/test/pleroma/web/o_status/o_status_controller_test.exs index 5cdca019a..2038f4ddd 100644 --- a/test/pleroma/web/o_status/o_status_controller_test.exs +++ b/test/pleroma/web/o_status/o_status_controller_test.exs @@ -144,13 +144,19 @@ test "redirects to a proper object URL when json requested and the object is loc assert redirect_url == expected_redirect_url end - test "returns a 404 on remote notice when json requested", %{conn: conn} do + test "redirects to a proper object URL when json requested and the object is remote", %{ + conn: conn + } do note_activity = insert(:note_activity, local: false) + expected_redirect_url = Object.normalize(note_activity, fetch: false).data["id"] - conn - |> put_req_header("accept", "application/activity+json") - |> get("/notice/#{note_activity.id}") - |> response(404) + redirect_url = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/notice/#{note_activity.id}") + |> redirected_to() + + assert redirect_url == expected_redirect_url end test "500s when actor not found", %{conn: conn} do diff --git a/test/pleroma/workers/purge_expired_filter_test.exs b/test/pleroma/workers/purge_expired_filter_test.exs new file mode 100644 index 000000000..d10586be9 --- /dev/null +++ b/test/pleroma/workers/purge_expired_filter_test.exs @@ -0,0 +1,30 @@ +defmodule Pleroma.Workers.PurgeExpiredFilterTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Repo + + import Pleroma.Factory + + test "purges expired filter" do + %{id: user_id} = insert(:user) + + {:ok, %{id: id}} = + Pleroma.Filter.create(%{ + user_id: user_id, + phrase: "cofe", + context: ["home"], + expires_in: 600 + }) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredFilter, + args: %{filter_id: id} + ) + + assert {:ok, %{id: ^id}} = + perform_job(Pleroma.Workers.PurgeExpiredFilter, %{ + filter_id: id + }) + + assert Repo.aggregate(Pleroma.Filter, :count, :id) == 0 + end +end diff --git a/test/pleroma/workers/scheduled_activity_worker_test.exs b/test/pleroma/workers/scheduled_activity_worker_test.exs index 6e11642d5..5558d5b5f 100644 --- a/test/pleroma/workers/scheduled_activity_worker_test.exs +++ b/test/pleroma/workers/scheduled_activity_worker_test.exs @@ -11,10 +11,9 @@ defmodule Pleroma.Workers.ScheduledActivityWorkerTest do import Pleroma.Factory import ExUnit.CaptureLog - setup do: clear_config([ScheduledActivity, :enabled]) + setup do: clear_config([ScheduledActivity, :enabled], true) test "creates a status from the scheduled activity" do - clear_config([ScheduledActivity, :enabled], true) user = insert(:user) naive_datetime = @@ -32,18 +31,22 @@ test "creates a status from the scheduled activity" do params: %{status: "hi"} ) - ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => scheduled_activity.id}}) + {:ok, %{id: activity_id}} = + ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => scheduled_activity.id}}) refute Repo.get(ScheduledActivity, scheduled_activity.id) - activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id)) - assert Pleroma.Object.normalize(activity, fetch: false).data["content"] == "hi" + + object = + Pleroma.Activity + |> Repo.get(activity_id) + |> Pleroma.Object.normalize() + + assert object.data["content"] == "hi" end - test "adds log message if ScheduledActivity isn't find" do - clear_config([ScheduledActivity, :enabled], true) - + test "error message for non-existent scheduled activity" do assert capture_log([level: :error], fn -> ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => 42}}) - end) =~ "Couldn't find scheduled activity" + end) =~ "Couldn't find scheduled activity: 42" end end diff --git a/test/support/factory.ex b/test/support/factory.ex index 436e19409..af4fff45b 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -486,7 +486,8 @@ def filter_factory do %Pleroma.Filter{ user: build(:user), filter_id: sequence(:filter_id, & &1), - phrase: "cofe" + phrase: "cofe", + context: ["home"] } end end