Browse Source

Merge remote-tracking branch 'remotes/origin/develop' into feature/object-hashtags-rework

# Conflicts:
#	CHANGELOG.md
#	lib/mix/tasks/pleroma/database.ex
#	lib/pleroma/web/templates/feed/feed/_activity.rss.eex
build-docker/feature/object-hashtags-rework
Ivan Tashkinov 3 years ago
parent
commit
5992382cf8
93 changed files with 2025 additions and 779 deletions
  1. +22
    -0
      .gitlab-ci.yml
  2. +19
    -4
      CHANGELOG.md
  3. +3
    -2
      benchmarks/load_testing/fetcher.ex
  4. +1
    -0
      config/config.exs
  5. +83
    -117
      config/description.exs
  6. +0
    -1
      config/emoji.txt
  7. +18
    -0
      docs/administration/CLI_tasks/database.md
  8. +42
    -0
      docs/configuration/howto_search_cjk.md
  9. +24
    -2
      docs/development/API/admin_api.md
  10. +7
    -0
      installation/pleroma.vcl
  11. +47
    -0
      lib/mix/tasks/pleroma/database.ex
  12. +1
    -1
      lib/mix/tasks/pleroma/email.ex
  13. +8
    -0
      lib/mix/tasks/pleroma/openapi_spec.ex
  14. +4
    -4
      lib/pleroma/activity/search.ex
  15. +5
    -5
      lib/pleroma/application.ex
  16. +16
    -2
      lib/pleroma/emails/user_email.ex
  17. +12
    -1
      lib/pleroma/emoji/loader.ex
  18. +107
    -17
      lib/pleroma/filter.ex
  19. +2
    -2
      lib/pleroma/notification.ex
  20. +3
    -1
      lib/pleroma/uploaders/uploader.ex
  21. +30
    -11
      lib/pleroma/user.ex
  22. +27
    -3
      lib/pleroma/web/activity_pub/activity_pub.ex
  23. +11
    -13
      lib/pleroma/web/activity_pub/activity_pub_controller.ex
  24. +61
    -0
      lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex
  25. +10
    -9
      lib/pleroma/web/activity_pub/visibility.ex
  26. +9
    -7
      lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
  27. +1
    -1
      lib/pleroma/web/admin_api/controllers/config_controller.ex
  28. +4
    -0
      lib/pleroma/web/admin_api/views/status_view.ex
  29. +84
    -9
      lib/pleroma/web/api_spec.ex
  30. +1
    -1
      lib/pleroma/web/api_spec/helpers.ex
  31. +23
    -23
      lib/pleroma/web/api_spec/operations/account_operation.ex
  32. +4
    -4
      lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
  33. +6
    -6
      lib/pleroma/web/api_spec/operations/admin/config_operation.ex
  34. +3
    -3
      lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex
  35. +6
    -6
      lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex
  36. +4
    -4
      lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
  37. +7
    -7
      lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex
  38. +8
    -8
      lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex
  39. +6
    -6
      lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
  40. +10
    -10
      lib/pleroma/web/api_spec/operations/admin/report_operation.ex
  41. +8
    -7
      lib/pleroma/web/api_spec/operations/admin/status_operation.ex
  42. +3
    -3
      lib/pleroma/web/api_spec/operations/app_operation.ex
  43. +13
    -13
      lib/pleroma/web/api_spec/operations/chat_operation.ex
  44. +2
    -2
      lib/pleroma/web/api_spec/operations/conversation_operation.ex
  45. +2
    -2
      lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
  46. +4
    -5
      lib/pleroma/web/api_spec/operations/domain_block_operation.ex
  47. +4
    -4
      lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
  48. +27
    -20
      lib/pleroma/web/api_spec/operations/filter_operation.ex
  49. +6
    -6
      lib/pleroma/web/api_spec/operations/follow_request_operation.ex
  50. +2
    -2
      lib/pleroma/web/api_spec/operations/instance_operation.ex
  51. +4
    -4
      lib/pleroma/web/api_spec/operations/list_operation.ex
  52. +7
    -7
      lib/pleroma/web/api_spec/operations/media_operation.ex
  53. +3
    -3
      lib/pleroma/web/api_spec/operations/notification_operation.ex
  54. +13
    -8
      lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
  55. +5
    -4
      lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex
  56. +3
    -3
      lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex
  57. +9
    -9
      lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
  58. +2
    -2
      lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex
  59. +2
    -2
      lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex
  60. +3
    -2
      lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex
  61. +1
    -1
      lib/pleroma/web/api_spec/operations/report_operation.ex
  62. +4
    -4
      lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex
  63. +60
    -56
      lib/pleroma/web/api_spec/operations/status_operation.ex
  64. +7
    -7
      lib/pleroma/web/api_spec/operations/subscription_operation.ex
  65. +1
    -2
      lib/pleroma/web/api_spec/operations/timeline_operation.ex
  66. +6
    -6
      lib/pleroma/web/api_spec/operations/user_import_operation.ex
  67. +1
    -1
      lib/pleroma/web/api_spec/schemas/account.ex
  68. +1
    -1
      lib/pleroma/web/api_spec/schemas/account_relationship.ex
  69. +2
    -2
      lib/pleroma/web/api_spec/schemas/scheduled_status.ex
  70. +32
    -29
      lib/pleroma/web/mastodon_api/controllers/filter_controller.ex
  71. +2
    -6
      lib/pleroma/web/o_status/o_status_controller.ex
  72. +1
    -1
      lib/pleroma/web/pleroma_api/controllers/account_controller.ex
  73. +2
    -1
      lib/pleroma/web/templates/feed/feed/_activity.rss.eex
  74. +43
    -0
      lib/pleroma/workers/purge_expired_filter.ex
  75. +33
    -21
      lib/pleroma/workers/scheduled_activity_worker.ex
  76. +11
    -0
      priv/repo/migrations/20210121080964_add_default_text_search_config.exs
  77. +1
    -1
      priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs
  78. +1
    -0
      test/config/emoji.txt
  79. +2
    -2
      test/pleroma/config/deprecation_warnings_test.exs
  80. +107
    -72
      test/pleroma/filter_test.exs
  81. +14
    -0
      test/pleroma/notification_test.exs
  82. +21
    -0
      test/pleroma/user_test.exs
  83. +79
    -0
      test/pleroma/web/activity_pub/activity_pub_controller_test.exs
  84. +154
    -0
      test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs
  85. +68
    -1
      test/pleroma/web/activity_pub/visibility_test.exs
  86. +50
    -48
      test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
  87. +2
    -2
      test/pleroma/web/admin_api/controllers/config_controller_test.exs
  88. +364
    -101
      test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs
  89. +39
    -1
      test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
  90. +11
    -5
      test/pleroma/web/o_status/o_status_controller_test.exs
  91. +30
    -0
      test/pleroma/workers/purge_expired_filter_test.exs
  92. +12
    -9
      test/pleroma/workers/scheduled_activity_worker_test.exs
  93. +2
    -1
      test/support/factory.ex

+ 22
- 0
.gitlab-ci.yml View File

@@ -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


+ 19
- 4
CHANGELOG.md View File

@@ -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.

<details>
<summary>API Changes</summary>

- **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

</details>
- 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)

<details>
<summary>API Changes</summary>
@@ -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.
</details>

## Unreleased (Patch)


+ 3
- 2
benchmarks/load_testing/fetcher.ex View File

@@ -33,10 +33,11 @@ defmodule Pleroma.LoadTesting.Fetcher 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



+ 1
- 0
config/config.exs View File

@@ -543,6 +543,7 @@ config :pleroma, Oban,
queues: [
activity_expiration: 10,
token_expiration: 5,
filter_expiration: 1,
backup: 1,
federator_incoming: 50,
federator_outgoing: 50,


+ 83
- 117
config/description.exs View File

@@ -99,7 +99,8 @@ config :pleroma, :config_description, [
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"
]
@@ -215,252 +216,215 @@ config :pleroma, :config_description, [
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.SMTP,
Swoosh.Adapters.Sendgrid,
Swoosh.Adapters.Sendmail,
Swoosh.Adapters.Mandrill,
Swoosh.Adapters.AmazonSES,
Swoosh.Adapters.Dyn,
Swoosh.Adapters.Gmail,
Swoosh.Adapters.Mailgun,
Swoosh.Adapters.Mailjet,
Swoosh.Adapters.Mandrill,
Swoosh.Adapters.Postmark,
Swoosh.Adapters.SparkPost,
Swoosh.Adapters.AmazonSES,
Swoosh.Adapters.Dyn,
Swoosh.Adapters.SMTP,
Swoosh.Adapters.Sendgrid,
Swoosh.Adapters.Sendmail,
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"]
description: "Hostname or IP address",
suggestions: ["smtp.example.com"]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :port,
type: :integer,
description: "SMTP port",
suggestions: ["1025"]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :username,
type: :string,
description: "`Swoosh.Adapters.SMTP` adapter specific setting",
suggestions: ["pleroma"]
description: "SMTP AUTH username",
suggestions: ["user@example.com"]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :password,
type: :string,
description: "`Swoosh.Adapters.SMTP` adapter specific setting",
description: "SMTP AUTH password",
suggestions: ["password"]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :ssl,
label: "SSL",
label: "Use SSL",
type: :boolean,
description: "`Swoosh.Adapters.SMTP` adapter specific setting"
description: "Use Implicit SSL/TLS. e.g. port 465"
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :tls,
label: "TLS",
type: :atom,
description: "`Swoosh.Adapters.SMTP` adapter specific setting",
suggestions: [:always, :never, :if_available]
label: "STARTTLS Mode",
type: {:dropdown, :atom},
description: "Explicit TLS (STARTTLS) enforcement mode",
suggestions: [:if_available, :always, :never]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :auth,
type: :atom,
description: "`Swoosh.Adapters.SMTP` adapter specific setting",
suggestions: [:always, :never, :if_available]
},
%{
group: {:subgroup, Swoosh.Adapters.SMTP},
key: :port,
type: :integer,
description: "`Swoosh.Adapters.SMTP` adapter specific setting",
suggestions: [1025]
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,
key: :access_key,
label: "AWS Access Key",
type: :string,
description: "`Swoosh.Adapters.AmazonSES` adapter specific setting",
suggestions: ["us-east-1", "us-east-2"]
suggestions: ["AWS_ACCESS_KEY"]
},
%{
group: {:subgroup, Swoosh.Adapters.AmazonSES},
key: :access_key,
key: :secret,
label: "AWS Secret Key",
type: :string,
description: "`Swoosh.Adapters.AmazonSES` adapter specific setting",
suggestions: ["aws-access-key"]
suggestions: ["AWS_SECRET_KEY"]
},
%{
group: {:subgroup, Swoosh.Adapters.AmazonSES},
key: :secret,
key: :region,
label: "AWS Region",
type: :string,
description: "`Swoosh.Adapters.AmazonSES` adapter specific setting",
suggestions: ["aws-secret-key"]
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"]
suggestions: ["apikey"]
},
%{
group: {:subgroup, Swoosh.Adapters.SocketLabs},
key: :server_id,
key: :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: :api_key,
label: "API key",
key: :server_id,
label: "Server ID",
type: :string,
description: "`Swoosh.Adapters.SocketLabs` adapter specific setting"
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 @@ config :pleroma, :config_description, [
%{
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 @@ config :pleroma, :config_description, [
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 @@ config :pleroma, :config_description, [
},
%{
key: :url_format,
label: "URL Format",
type: :string,
description:
"Optional URL format preprocessing. Only required for Apache's htcacheclean.",
@@ -2902,7 +2868,7 @@ config :pleroma, :config_description, [
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 @@ config :pleroma, :config_description, [
},
%{
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,


+ 0
- 1
config/emoji.txt View File

@@ -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

+ 18
- 0
docs/administration/CLI_tasks/database.md View File

@@ -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.

+ 42
- 0
docs/configuration/howto_search_cjk.md View File

@@ -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.

+ 24
- 2
docs/development/API/admin_api.md View File

@@ -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`



+ 7
- 0
installation/pleroma.vcl View File

@@ -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;


+ 47
- 0
lib/mix/tasks/pleroma/database.ex View File

@@ -171,6 +171,53 @@ defmodule Mix.Tasks.Pleroma.Database 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


+ 1
- 1
lib/mix/tasks/pleroma/email.ex View File

@@ -38,7 +38,7 @@ defmodule Mix.Tasks.Pleroma.Email 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

+ 8
- 0
lib/mix/tasks/pleroma/openapi_spec.ex View File

@@ -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

+ 4
- 4
lib/pleroma/activity/search.ex View File

@@ -64,7 +64,7 @@ defmodule Pleroma.Activity.Search 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 @@ defmodule Pleroma.Activity.Search 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 @@ defmodule Pleroma.Activity.Search do
from([a, o] in q,
where:
fragment(
"? @@ plainto_tsquery('english', ?)",
"? @@ plainto_tsquery(?)",
o.fts_content,
^search_query
),
@@ -98,7 +98,7 @@ defmodule Pleroma.Activity.Search do
from([a, o] in q,
where:
fragment(
"? @@ websearch_to_tsquery('english', ?)",
"? @@ websearch_to_tsquery(?)",
o.fts_content,
^search_query
),


+ 5
- 5
lib/pleroma/application.ex View File

@@ -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 @@ defmodule Pleroma.Application 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 @@ defmodule Pleroma.Application 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)


+ 16
- 2
lib/pleroma/emails/user_email.ex View File

@@ -81,9 +81,9 @@ defmodule Pleroma.Emails.UserEmail do
)

html_body = """
<h3>Welcome to #{instance_name()}!</h3>
<h3>Thank you for registering on #{instance_name()}</h3>
<p>Email confirmation is required to activate the account.</p>
<p>Click the following link to proceed: <a href="#{confirmation_url}">activate your account</a>.</p>
<p>Please click the following link to <a href="#{confirmation_url}">activate your account</a>.</p>
"""

new()
@@ -106,6 +106,20 @@ defmodule Pleroma.Emails.UserEmail do
|> html_body(html_body)
end

def successful_registration_email(user) do
html_body = """
<h3>Hello @#{user.nickname},</h3>
<p>Your account at #{instance_name()} has been registered successfully.</p>
<p>No further action is required to activate your account.</p>
"""

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


+ 12
- 1
lib/pleroma/emoji/loader.ex View File

@@ -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 @@ defmodule Pleroma.Emoji.Loader 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)


+ 107
- 17
lib/pleroma/filter.ex View File

@@ -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 @@ defmodule Pleroma.Filter 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 @@ defmodule Pleroma.Filter 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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.Filter do
max_id + 1
end

filter
|> Map.put(:filter_id, filter_id)
|> Repo.insert()
change(changeset, filter_id: filter_id)
end

# 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 create(%Pleroma.Filter{} = filter) do
Repo.insert(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_number(filter_key) do
Repo.delete(filter)
defp maybe_add_expires_at(changeset, %{expires_in: nil}) do
change(changeset, expires_at: nil)
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})
defp maybe_add_expires_at(changeset, _), do: changeset

filter
|> Map.put(:id, id)
|> Repo.delete()
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

def update(%Pleroma.Filter{} = filter, params) do
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


+ 2
- 2
lib/pleroma/notification.ex View File

@@ -507,8 +507,8 @@ defmodule Pleroma.Notification do
[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


+ 3
- 1
lib/pleroma/uploaders/uploader.ex View File

@@ -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 @@ defmodule Pleroma.Uploaders.Uploader do
end

defp callback_timeout do
case Mix.env() do
case @mix_env do
:test -> 1_000
_ -> 30_000
end


+ 30
- 11
lib/pleroma/user.ex View File

@@ -798,7 +798,7 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.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 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User do
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 @@ defmodule Pleroma.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


+ 27
- 3
lib/pleroma/web/activity_pub/activity_pub.ex View File

@@ -377,6 +377,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
: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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
}
|> user_activities_recipients()
|> fetch_activities(params, :offset)
|> Enum.reverse()
end

defp user_activities_recipients(%{godmode: true}), do: []


+ 11
- 13
lib/pleroma/web/activity_pub/activity_pub_controller.ex View File

@@ -79,11 +79,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController 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


+ 61
- 0
lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex View File

@@ -0,0 +1,61 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.ActivityPub.MRF.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

+ 10
- 9
lib/pleroma/web/activity_pub/visibility.ex View File

@@ -56,11 +56,10 @@ defmodule Pleroma.Web.ActivityPub.Visibility 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 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|> 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


+ 9
- 7
lib/pleroma/web/admin_api/controllers/admin_api_controller.ex View File

@@ -85,17 +85,18 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController 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



+ 1
- 1
lib/pleroma/web/admin_api/controllers/config_controller.ex View File

@@ -122,7 +122,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController 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



+ 4
- 0
lib/pleroma/web/admin_api/views/status_view.ex View File

@@ -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


+ 84
- 9
lib/pleroma/web/api_spec.ex View File

@@ -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 @@ defmodule Pleroma.Web.ApiSpec 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 @@ defmodule Pleroma.Web.ApiSpec 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


+ 1
- 1
lib/pleroma/web/api_spec/helpers.ex View File

@@ -63,7 +63,7 @@ defmodule Pleroma.Web.ApiSpec.Helpers 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



+ 23
- 23
lib/pleroma/web/api_spec/operations/account_operation.ex View File

@@ -26,7 +26,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do

def endorsements_operation do
%Operation{
tags: ["accounts"],
tags: ["Retrieve account information"],
summary: "Endorsements",
operationId: "AccountController.endorsements",
description: "Not implemented",
@@ -386,7 +386,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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


+ 4
- 4
lib/pleroma/web/api_spec/operations/admin/chat_operation.ex View File

@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do

def show_operation do
%Operation{
tags: ["chat"],
tags: ["Chat administration"],
summary: "Create a chat",
operationId: "AdminAPI.ChatController.show",
parameters: [


+ 6
- 6
lib/pleroma/web/api_spec/operations/admin/config_operation.ex View File

@@ -16,8 +16,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation 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(),


+ 3
- 3
lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex View File

@@ -16,8 +16,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do

def install_operation do
%Operation{
tags: ["Admin", "Reports"],
tags: ["Frontend managment"],
summary: "Install a frontend",
operationId: "AdminAPI.FrontendController.install",
security: [%{"oAuth" => ["read"]}],


+ 6
- 6
lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex View File

@@ -15,8 +15,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation 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: [


+ 4
- 4
lib/pleroma/web/api_spec/operations/admin/invite_operation.ex View File

@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation 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"]}],


+ 7
- 7
lib/pleroma/web/api_spec/operations/admin/media_proxy_cache_operation.ex View File

@@ -16,8 +16,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.MediaProxyCacheOperation 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(),


+ 8
- 8
lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex View File

@@ -16,8 +16,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation 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"]}],


+ 6
- 6
lib/pleroma/web/api_spec/operations/admin/relay_operation.ex View File

@@ -15,8 +15,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation 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(),


+ 10
- 10
lib/pleroma/web/api_spec/operations/admin/report_operation.ex View File

@@ -19,8 +19,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation 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"),


+ 8
- 7
lib/pleroma/web/api_spec/operations/admin/status_operation.ex View File

@@ -21,8 +21,9 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation 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"]}],


+ 3
- 3
lib/pleroma/web/api_spec/operations/app_operation.ex View File

@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation 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 @@ defmodule Pleroma.Web.ApiSpec.AppOperation 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"]}],


+ 13
- 13
lib/pleroma/web/api_spec/operations/chat_operation.ex View File

@@ -20,7 +20,7 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do

def create_operation do
%Operation{
tags: ["chat"],
tags: ["Chats"],
summary: "Create a chat",
operationId: "ChatController.create",
parameters: [
@@ -130,8 +130,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation 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"),


+ 2
- 2
lib/pleroma/web/api_spec/operations/conversation_operation.ex View File

@@ -18,7 +18,7 @@ defmodule Pleroma.Web.ApiSpec.ConversationOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ConversationOperation 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",


+ 2
- 2
lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex View File

@@ -14,8 +14,8 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation 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: %{


+ 4
- 5
lib/pleroma/web/api_spec/operations/domain_block_operation.ex View File

@@ -14,9 +14,8 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation 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 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation 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 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation 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",


+ 4
- 4
lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex View File

@@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation 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 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation 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 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation 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 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation 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]


+ 27
- 20
lib/pleroma/web/api_spec/operations/filter_operation.ex View File

@@ -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 @@ defmodule Pleroma.Web.ApiSpec.FilterOperation 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 @@ defmodule Pleroma.Web.ApiSpec.FilterOperation do
Operation.response("Filter", "application/json", %Schema{
type: :object,
description: "Empty object"
})
}),
403 => Operation.response("Error", "application/json", ApiError)
}
}
end
@@ -210,15 +219,13 @@ defmodule Pleroma.Web.ApiSpec.FilterOperation 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: %{


+ 6
- 6
lib/pleroma/web/api_spec/operations/follow_request_operation.ex View File

@@ -15,8 +15,8 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation 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 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation 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 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation 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"]}],


+ 2
- 2
lib/pleroma/web/api_spec/operations/instance_operation.ex View File

@@ -14,7 +14,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation 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 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation 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())


+ 4
- 4
lib/pleroma/web/api_spec/operations/list_operation.ex View File

@@ -20,7 +20,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ListOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ListOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ListOperation 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"]}],


+ 7
- 7
lib/pleroma/web/api_spec/operations/media_operation.ex View File

@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation 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 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation 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 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation 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 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation 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"]}],


+ 3
- 3
lib/pleroma/web/api_spec/operations/notification_operation.ex View File

@@ -22,7 +22,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation 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 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation 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 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation 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()],


+ 13
- 8
lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex View File

@@ -18,8 +18,9 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation 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"]}],


+ 5
- 4
lib/pleroma/web/api_spec/operations/pleroma_conversation_operation.ex View File

@@ -19,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaConversationOperation 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: %{


+ 3
- 3
lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex View File

@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation 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"]}],


+ 9
- 9
lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex View File

@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation 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"]}],


+ 2
- 2
lib/pleroma/web/api_spec/operations/pleroma_instances_operation.ex View File

@@ -13,8 +13,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaInstancesOperation 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: %{


+ 2
- 2
lib/pleroma/web/api_spec/operations/pleroma_mascot_operation.ex View File

@@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaMascotOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaMascotOperation 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",


+ 3
- 2
lib/pleroma/web/api_spec/operations/pleroma_notification_operation.ex View File

@@ -18,7 +18,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation 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 @@ defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do
responses: %{
200 =>
Operation.response(
"A Notification or array of Motifications",
"A Notification or array of Notifications",
"application/json",
%Schema{
anyOf: [


+ 1
- 1
lib/pleroma/web/api_spec/operations/report_operation.ex View File

@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do

def create_operation do
%Operation{
tags: ["reports"],
tags: ["Reports"],
summary: "File a report",
description: "Report problematic users to your moderators",
operationId: "ReportController.create",


+ 4
- 4
lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex View File

@@ -18,7 +18,7 @@ defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation 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 @@ defmodule Pleroma.Web.ApiSpec.ScheduledActivityOperation do

def delete_operation do
%Operation{
tags: ["Scheduled Statuses"],
tags: ["Scheduled statuses"],
summary: "Cancel a scheduled status",
security: [%{"oAuth" => ["write:statuses"]}],
parameters: [id_param()],


+ 60
- 56
lib/pleroma/web/api_spec/operations/status_operation.ex View File

@@ -22,8 +22,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do

def unmute_conversation_operation do
%Operation{
tags: ["Statuses"],
tags: ["Status actions"],
summary: "Unmute conversation",
security: [%{"oAuth" => ["write:mutes"]}],
description:
@@ -283,7 +283,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation 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",


+ 7
- 7
lib/pleroma/web/api_spec/operations/subscription_operation.ex View File

@@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation 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 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation 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 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation 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 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation 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 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation 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 @@ defmodule Pleroma.Web.ApiSpec.SubscriptionOperation 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",


+ 1
- 2
lib/pleroma/web/api_spec/operations/timeline_operation.ex View File

@@ -43,8 +43,7 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation 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",


+ 6
- 6
lib/pleroma/web/api_spec/operations/user_import_operation.ex View File

@@ -17,8 +17,8 @@ defmodule Pleroma.Web.ApiSpec.UserImportOperation 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 @@ defmodule Pleroma.Web.ApiSpec.UserImportOperation 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 @@ defmodule Pleroma.Web.ApiSpec.UserImportOperation 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: %{


+ 1
- 1
lib/pleroma/web/api_spec/schemas/account.ex View File

@@ -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:


+ 1
- 1
lib/pleroma/web/api_spec/schemas/account_relationship.ex View File

@@ -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},


+ 2
- 2
lib/pleroma/web/api_spec/schemas/scheduled_status.ex View File

@@ -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}
}
}


+ 32
- 29
lib/pleroma/web/mastodon_api/controllers/filter_controller.ex View File

@@ -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 @@ defmodule Pleroma.Web.MastodonAPI.FilterController 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 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
%{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

+ 2
- 6
lib/pleroma/web/o_status/o_status_controller.ex View File

@@ -73,12 +73,8 @@ defmodule Pleroma.Web.OStatus.OStatusController 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)


+ 1
- 1
lib/pleroma/web/pleroma_api/controllers/account_controller.ex View File

@@ -56,7 +56,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController 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


+ 2
- 1
lib/pleroma/web/templates/feed/feed/_activity.rss.eex View File

@@ -9,7 +9,6 @@
<ostatus:conversation ref="<%= activity_context(@activity) %>">
<%= activity_context(@activity) %>
</ostatus:conversation>
<link rel="ostatus:conversation"><%= activity_context(@activity) %></link>

<%= if @data["summary"] do %>
<description><%= escape(@data["summary"]) %></description>
@@ -21,6 +20,8 @@
<link><%= @data["external_url"] %></link>
<% end %>

<link rel="ostatus:conversation"><%= activity_context(@activity) %></link>

<%= for tag <- Pleroma.Object.hashtags(@object) do %>
<category term="<%= tag %>"></category>
<% end %>


+ 43
- 0
lib/pleroma/workers/purge_expired_filter.ex View File

@@ -0,0 +1,43 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# 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

+ 33
- 21
lib/pleroma/workers/scheduled_activity_worker.ex View File

@@ -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)

_ ->
Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}")
end
with %ScheduledActivity{} = scheduled_activity <- find_scheduled_activity(activity_id),
%User{} = user <- find_user(scheduled_activity.user_id) do
params = atomize_keys(scheduled_activity.params)

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)
defp find_scheduled_activity(id) do
with nil <- Repo.get(ScheduledActivity, id) do
{:error, :scheduled_activity_not_found}
end
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_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

+ 11
- 0
priv/repo/migrations/20210121080964_add_default_text_search_config.exs View File

@@ -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

+ 1
- 1
priv/repo/optional_migrations/rum_indexing/20190510135645_add_fts_index_to_objects_two.exs View File

@@ -17,7 +17,7 @@ defmodule Pleroma.Repo.Migrations.AddFtsIndexToObjectsTwo 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")


+ 1
- 0
test/config/emoji.txt View File

@@ -0,0 +1 @@
external_emoji, https://example.com/emoji.png

+ 2
- 2
test/pleroma/config/deprecation_warnings_test.exs View File

@@ -87,7 +87,7 @@ defmodule Pleroma.Config.DeprecationWarningsTest 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 @@ defmodule Pleroma.Config.DeprecationWarningsTest 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()


+ 107
- 72
test/pleroma/filter_test.exs View File

@@ -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
}

{:error, _} = Filter.create(attrs)

assert Repo.all(Job) == []
end

query = %Filter{
test "use passed expires_at instead expires_in", %{user: user} do
now = NaiveDateTime.utc_now()

attrs = %{
user_id: user.id,
filter_id: 42,
expires_at: now,
phrase: "knights",
context: ["home"]
context: ["home"],
expires_in: 600
}

{:ok, %Filter{} = filter} = Filter.create(query)
{:ok, %Filter{} = filter} = Filter.create(attrs)

result = Filter.get(filter.filter_id, user)
assert query.phrase == result.phrase
end
assert result.expires_at == NaiveDateTime.truncate(now, :second)

test "creating one filter without a pre-defined filter_id" do
user = insert(:user)
[job] = Repo.all(Job)

query = %Filter{
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)
# Should start at 1
assert filter.filter_id == 1
{:ok, %Filter{} = filter} = Filter.create(attrs)
result = Filter.get(filter.filter_id, user)
assert attrs.phrase == result.phrase
end

test "creating additional filters uses previous highest filter_id + 1" do
user = insert(:user)

query_one = %Filter{
test "creating with expired_at", %{user: user} do
attrs = %{
user_id: user.id,
filter_id: 42,
phrase: "knights",
context: ["home"],
expires_in: 60
}

{: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_one} = Filter.create(query_one)
{: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", %{user: user} do
filter1 = insert(:filter, user: user)

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 @@ defmodule Pleroma.FilterTest 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 != 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
}

assert filter_one != filter_two
assert filter_two.phrase == changes.phrase
assert filter_two.context == changes.context
{:error, _} = Filter.update(filter, changes)
end
end

+ 14
- 0
test/pleroma/notification_test.exs View File

@@ -45,6 +45,20 @@ defmodule Pleroma.NotificationTest 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)


+ 21
- 0
test/pleroma/user_test.exs View File

@@ -551,6 +551,27 @@ defmodule Pleroma.UserTest 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)



+ 79
- 0
test/pleroma/web/activity_pub/activity_pub_controller_test.exs View File

@@ -229,6 +229,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest 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()


+ 154
- 0
test/pleroma/web/activity_pub/mrf/no_empty_policy_test.exs View File

@@ -0,0 +1,154 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.ActivityPub.MRF.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

+ 68
- 1
test/pleroma/web/activity_pub/visibility_test.exs View File

@@ -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 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
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 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
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,


+ 50
- 48
test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs View File

@@ -405,13 +405,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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")

response1 = json_response(conn1, 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)
%{"total" => 3, "activities" => [activity1]} =
conn
|> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=1")
|> json_response(200)

assert response2 |> length() == 1
%{"total" => 3, "activities" => [activity2]} =
conn
|> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=2")
|> json_response(200)

refute response1 == response2
refute activity1 == activity2
end

test "doesn't return private statuses by default", %{conn: conn, user: user} do
@@ -443,9 +438,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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

conn_res =
get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true")
assert %{"total" => 0, "activities" => []} ==
conn
|> get("/api/pleroma/admin/users/#{other_user.nickname}/statuses")
|> json_response(200)

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 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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")
%{"total" => 0, "activities" => []} =
conn |> get("/api/pleroma/admin/instances/nonexistent.com/statuses") |> json_response(200)

response = json_response(ret_conn, 200)

assert length(response) == 1

ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses")
CommonAPI.repeat(activity.id, user)

response = json_response(ret_conn, 200)
%{"total" => 2, "activities" => activities} =
conn |> get("/api/pleroma/admin/instances/archae.me/statuses") |> json_response(200)

assert Enum.empty?(response)
assert length(activities) == 2

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" => 3, "activities" => activities} =
conn
|> get("/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true")
|> 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) == 3
end
end



+ 2
- 2
test/pleroma/web/admin_api/controllers/config_controller_test.exs View File

@@ -31,7 +31,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest 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 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest 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


+ 364
- 101
test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs View File

@@ -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})

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"] != ""
|> post("/api/v1/filters", %{"phrase" => "knights", context: ["home"]})
|> json_response(403)

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)

assert response ==
render_json(
FilterView,
"index.json",
filters: [filter_two, filter_one]
)
|> json_response(403)

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
}

{:ok, filter} = Pleroma.Filter.create(query)
filter = insert(:filter, user: user, whole_word: false)

conn = get(conn, "/api/v1/filters/#{filter.filter_id}")
resp1 =
conn |> get("/api/v1/filters/#{filter.filter_id}") |> json_response_and_validate_schema(200)

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"])

query = %Pleroma.Filter{
user_id: user.id,
filter_id: 3,
phrase: "knight",
context: ["home"],
whole_word: true
}
filter = insert(:filter, user: user, whole_word: true)

{:ok, filter} = Pleroma.Filter.create(query)
resp2 =
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"] == 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

describe "updating a filter" do
setup do: oauth_access(["write:filters"])

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

new = %Pleroma.Filter{
phrase: "nii",
context: ["home"]
}
test "update filter without token", %{conn: conn} do
filter = insert(:filter)

conn =
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"])

query = %Pleroma.Filter{
user_id: user.id,
filter_id: 2,
phrase: "knight",
context: ["home"]
}
describe "delete a filter" do
setup do: oauth_access(["write:filters"])

test "common", %{conn: conn, user: user} do
filter = insert(:filter, user: user)

assert conn
|> delete("/api/v1/filters/#{filter.filter_id}")
|> json_response_and_validate_schema(200) == %{}

assert Repo.all(Filter) == []
end

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

{:ok, filter} = Pleroma.Filter.create(query)
test "delete a filter without token", %{conn: conn} do
filter = insert(:filter)

conn = delete(conn, "/api/v1/filters/#{filter.filter_id}")
response =
conn
|> delete("/api/v1/filters/#{filter.filter_id}")
|> json_response(403)

assert json_response_and_validate_schema(conn, 200) == %{}
assert response["error"] == "Invalid credentials."
end
end

+ 39
- 1
test/pleroma/web/mastodon_api/controllers/status_controller_test.exs View File

@@ -516,7 +516,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest 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 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest 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


+ 11
- 5
test/pleroma/web/o_status/o_status_controller_test.exs View File

@@ -144,13 +144,19 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
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


+ 30
- 0
test/pleroma/workers/purge_expired_filter_test.exs View File

@@ -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

+ 12
- 9
test/pleroma/workers/scheduled_activity_worker_test.exs View File

@@ -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 @@ defmodule Pleroma.Workers.ScheduledActivityWorkerTest 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"
end

test "adds log message if ScheduledActivity isn't find" do
clear_config([ScheduledActivity, :enabled], true)
object =
Pleroma.Activity
|> Repo.get(activity_id)
|> Pleroma.Object.normalize()

assert object.data["content"] == "hi"
end

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

+ 2
- 1
test/support/factory.ex View File

@@ -486,7 +486,8 @@ defmodule Pleroma.Factory do
%Pleroma.Filter{
user: build(:user),
filter_id: sequence(:filter_id, & &1),
phrase: "cofe"
phrase: "cofe",
context: ["home"]
}
end
end

Loading…
Cancel
Save