[#1234] Merge remote-tracking branch 'remotes/upstream/develop' into 1234-mastodon...
authorIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 15 Sep 2019 15:52:27 +0000 (18:52 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 15 Sep 2019 15:52:27 +0000 (18:52 +0300)
# Conflicts:
# lib/pleroma/web/activity_pub/activity_pub_controller.ex
# lib/pleroma/web/router.ex

48 files changed:
.gitignore
CHANGELOG.md
config/config.exs
config/description.exs [new file with mode: 0644]
docs/api/admin_api.md
docs/api/differences_in_mastoapi_responses.md
docs/api/pleroma_api.md
docs/config.md
lib/mix/tasks/pleroma/benchmark.ex
lib/mix/tasks/pleroma/docs.ex [new file with mode: 0644]
lib/pleroma/activity.ex
lib/pleroma/activity/queries.ex
lib/pleroma/application.ex
lib/pleroma/docs/generator.ex [new file with mode: 0644]
lib/pleroma/docs/json.ex [new file with mode: 0644]
lib/pleroma/docs/markdown.ex [new file with mode: 0644]
lib/pleroma/healthcheck.ex [moved from lib/healthcheck.ex with 98% similarity]
lib/pleroma/object.ex
lib/pleroma/plugs/cache.ex [new file with mode: 0644]
lib/pleroma/user.ex
lib/pleroma/user/info.ex
lib/pleroma/web/activity_pub/activity_pub_controller.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/admin_api/config.ex
lib/pleroma/web/controller_helper.ex
lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/pleroma_api/pleroma_api_controller.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/controllers/util_controller.ex
lib/pleroma/web/web.ex
mix.exs
mix.lock
test/activity_test.exs
test/integration/mastodon_websocket_test.exs
test/object_test.exs
test/plugs/cache_test.exs [new file with mode: 0644]
test/user_test.exs
test/web/activity_pub/activity_pub_controller_test.exs
test/web/activity_pub/publisher_test.exs
test/web/activity_pub/relay_test.exs
test/web/activity_pub/transmogrifier_test.exs
test/web/admin_api/admin_api_controller_test.exs
test/web/admin_api/config_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs
test/web/twitter_api/util_controller_test.exs
test/web/web_finger/web_finger_controller_test.exs

index 9591f997639479396f5c5e458e561cdc15b841a2..4e71a7df0d37346ed4381f6f203e7a6163f2851e 100644 (file)
@@ -38,6 +38,7 @@ erl_crash.dump
 
 # Prevent committing docs files
 /priv/static/doc/*
+docs/generated_config.md
 
 # Code test coverage
 /cover
index f489c52f5ed74a3aaf5c635322dc1fc8494a06e4..0c5e43123059fa7cbeced825f09888a556f42db7 100644 (file)
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Security
 - OStatus: eliminate the possibility of a protocol downgrade attack.
 - OStatus: prevent following locked accounts, bypassing the approval process.
+- Mastodon API: respect post privacy in `/api/v1/statuses/:id/{favourited,reblogged}_by`
 
 ### Removed
 - **Breaking:** GNU Social API with Qvitter extensions support
@@ -20,11 +21,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - **Breaking:** `/api/pleroma/notifications/read` is moved to `/api/v1/pleroma/notifications/read` and now supports `max_id` and responds with Mastodon API entities.
 - Configuration: OpenGraph and TwitterCard providers enabled by default
 - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
-- Mastodon API: `pleroma.thread_muted` key in the Status entity
+- Configuration: added `config/description.exs`, from which `docs/config.md` is generated
 - Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set
 - NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
 - NodeInfo: Return `mailerEnabled` in `metadata`
 - Mastodon API: Unsubscribe followers when they unfollow a user
+- Mastodon API: `pleroma.thread_muted` key in the Status entity
 - AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
 - Improve digest email template
 – Pagination: (optional) return `total` alongside with `items` when paginating
@@ -60,6 +62,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Reverse Proxy limiting `max_body_length` was incorrectly defined and only checked `Content-Length` headers which may not be sufficient in some circumstances
 - MRF: fix use of unserializable keyword lists in describe() implementations
 - ActivityPub: Deactivated user deletion
+- ActivityPub: Fix `/users/:nickname/inbox` crashing without an authenticated user
 - MRF: fix ability to follow a relay when AntiFollowbotPolicy was enabled
 
 ### Added
@@ -104,10 +107,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - ActivityPub: Optional signing of ActivityPub object fetches.
 - Admin API: Endpoint for fetching latest user's statuses
 - Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
+- Pleroma API: Email change endpoint.
 - Relays: Added a task to list relay subscriptions.
 - Mix Tasks: `mix pleroma.database fix_likes_collections`
 - Federation: Remove `likes` from objects.
 - Admin API: Added moderation log
+- Web response cache (currently, enabled for ActivityPub)
+- Mastodon API: Added an endpoint to get multiple statuses by IDs (`GET /api/v1/statuses/?ids[]=1&ids[]=2`)
 
 ### Changed
 - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
index f630771a3d28a39b8605877b3b82460cdad5554c..0e91df88660a83e3a40ab91bc6dc12ff321b8a24 100644 (file)
@@ -373,6 +373,8 @@ config :pleroma, :chat, enabled: true
 
 config :phoenix, :format_encoders, json: Jason
 
+config :phoenix, :json_library, Jason
+
 config :pleroma, :gopher,
   enabled: false,
   ip: {0, 0, 0, 0},
@@ -560,6 +562,10 @@ config :pleroma, :rate_limit, nil
 
 config :pleroma, Pleroma.ActivityExpiration, enabled: true
 
+config :pleroma, :web_cache_ttl,
+  activity_pub: nil,
+  activity_pub_question: 30_000
+
 # Import environment specific config. This must remain at the bottom
 # of this file so it overrides the configuration defined above.
 import_config "#{Mix.env()}.exs"
diff --git a/config/description.exs b/config/description.exs
new file mode 100644 (file)
index 0000000..c5ae639
--- /dev/null
@@ -0,0 +1,2837 @@
+use Mix.Config
+alias Pleroma.Docs.Generator
+
+websocket_config = [
+  path: "/websocket",
+  serializer: [
+    {Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"},
+    {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"}
+  ],
+  timeout: 60_000,
+  transport_log: false,
+  compress: false
+]
+
+config :pleroma, :config_description, [
+  %{
+    group: :pleroma,
+    key: Pleroma.Upload,
+    type: :group,
+    description: "Upload general settings",
+    children: [
+      %{
+        key: :uploader,
+        type: :module,
+        description: "Module which will be used for uploads",
+        suggestions: [
+          Generator.uploaders_list()
+        ]
+      },
+      %{
+        key: :filters,
+        type: {:list, :module},
+        description: "List of filter modules for uploads",
+        suggestions: [
+          Generator.filters_list()
+        ]
+      },
+      %{
+        key: :link_name,
+        type: :boolean,
+        description:
+          "If enabled, a name parameter will be added to the url of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :base_url,
+        type: :string,
+        description: "Base url for the uploads, needed if you use CDN",
+        suggestions: [
+          "https://cdn-host.com"
+        ]
+      },
+      %{
+        key: :proxy_remote,
+        type: :boolean,
+        description:
+          "If enabled, requests to media stored using a remote uploader will be proxied instead of being redirected.",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :proxy_opts,
+        type: :keyword,
+        description: "Proxy options, see `Pleroma.ReverseProxy` documentation"
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Uploaders.Local,
+    type: :group,
+    description: "Local uploader-related settings",
+    children: [
+      %{
+        key: :uploads,
+        type: :string,
+        description: "Path where user uploads will be saved",
+        suggestions: [
+          "uploads"
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Uploaders.S3,
+    type: :group,
+    description: "S3 uploader-related settings",
+    children: [
+      %{
+        key: :bucket,
+        type: :string,
+        description: "S3 bucket",
+        suggestions: [
+          "bucket"
+        ]
+      },
+      %{
+        key: :bucket_namespace,
+        type: :string,
+        description: "S3 bucket namespace",
+        suggestions: ["pleroma"]
+      },
+      %{
+        key: :public_endpoint,
+        type: :string,
+        description: "S3 endpoint",
+        suggestions: ["https://s3.amazonaws.com"]
+      },
+      %{
+        key: :truncated_namespace,
+        type: :string,
+        description:
+          "If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or \"\" etc." <>
+            " For example, when using CDN to S3 virtual host format, set \"\". At this time, write CNAME to CDN in public_endpoint.",
+        suggestions: [""]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Upload.Filter.Mogrify,
+    type: :group,
+    description: "Uploads mogrify filter settings",
+    children: [
+      %{
+        key: :args,
+        type: [:string, {:list, :string}, {:list, :tuple}],
+        description: "List of actions for the mogrify command",
+        suggestions: [
+          "strip",
+          ["strip", "auto-orient"],
+          [{"implode", "1"}],
+          ["strip", "auto-orient", {"implode", "1"}]
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Upload.Filter.AnonymizeFilename,
+    type: :group,
+    description: "Filter replaces the filename of the upload",
+    children: [
+      %{
+        key: :text,
+        type: :string,
+        description:
+          "Text to replace filenames in links. If no setting, {random}.extension will be used. You can get the original" <>
+            " filename extension by using {extension}, for example custom-file-name.{extension}",
+        suggestions: [
+          "custom-file-name.{extension}",
+          nil
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Emails.Mailer,
+    type: :group,
+    description: "Mailer-related settings",
+    children: [
+      %{
+        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",
+        suggestions: [
+          Swoosh.Adapters.SMTP,
+          Swoosh.Adapters.Sendgrid,
+          Swoosh.Adapters.Sendmail,
+          Swoosh.Adapters.Mandrill,
+          Swoosh.Adapters.Mailgun,
+          Swoosh.Adapters.Mailjet,
+          Swoosh.Adapters.Postmark,
+          Swoosh.Adapters.SparkPost,
+          Swoosh.Adapters.AmazonSES,
+          Swoosh.Adapters.Dyn,
+          Swoosh.Adapters.SocketLabs,
+          Swoosh.Adapters.Gmail
+        ]
+      },
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "Allow/disallow send emails",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SMTP},
+        key: :relay,
+        type: :string,
+        description: "`Swoosh.Adapters.SMTP` adapter specific setting",
+        suggestions: ["smtp.gmail.com"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SMTP},
+        key: :username,
+        type: :string,
+        description: "`Swoosh.Adapters.SMTP` adapter specific setting",
+        suggestions: ["pleroma"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SMTP},
+        key: :password,
+        type: :string,
+        description: "`Swoosh.Adapters.SMTP` adapter specific setting",
+        suggestions: ["password"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SMTP},
+        key: :ssl,
+        type: :boolean,
+        description: "`Swoosh.Adapters.SMTP` adapter specific setting",
+        suggestions: [true, false]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SMTP},
+        key: :tls,
+        type: :atom,
+        description: "`Swoosh.Adapters.SMTP` adapter specific setting",
+        suggestions: [:always, :never, :if_available]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SMTP},
+        key: :auth,
+        type: :atom,
+        description: "`Swoosh.Adapters.SMTP` adapter specific setting",
+        suggestions: [:always, :never, :if_available]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SMTP},
+        key: :port,
+        type: :integer,
+        description: "`Swoosh.Adapters.SMTP` adapter specific setting",
+        suggestions: [1025]
+      },
+      %{
+        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,
+        type: :boolean,
+        description: "`Swoosh.Adapters.SMTP` adapter specific setting",
+        suggestions: [true, false]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.Sendgrid},
+        key: :api_key,
+        type: :string,
+        description: "`Swoosh.Adapters.Sendgrid` adapter specific setting",
+        suggestions: ["my-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",
+        suggestions: [true, false]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.Mandrill},
+        key: :api_key,
+        type: :string,
+        description: "`Swoosh.Adapters.Mandrill` adapter specific setting",
+        suggestions: ["my-api-key"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.Mailgun},
+        key: :api_key,
+        type: :string,
+        description: "`Swoosh.Adapters.Mailgun` adapter specific setting",
+        suggestions: ["my-api-key"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.Mailgun},
+        key: :domain,
+        type: :string,
+        description: "`Swoosh.Adapters.Mailgun` adapter specific setting",
+        suggestions: ["pleroma.com"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.Mailjet},
+        key: :api_key,
+        type: :string,
+        description: "`Swoosh.Adapters.Mailjet` adapter specific setting",
+        suggestions: ["my-api-key"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.Mailjet},
+        key: :secret,
+        type: :string,
+        description: "`Swoosh.Adapters.Mailjet` adapter specific setting",
+        suggestions: ["my-secret-key"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.Postmark},
+        key: :api_key,
+        type: :string,
+        description: "`Swoosh.Adapters.Postmark` adapter specific setting",
+        suggestions: ["my-api-key"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SparkPost},
+        key: :api_key,
+        type: :string,
+        description: "`Swoosh.Adapters.SparkPost` adapter specific setting",
+        suggestions: ["my-api-key"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SparkPost},
+        key: :endpoint,
+        type: :string,
+        description: "`Swoosh.Adapters.SparkPost` adapter specific setting",
+        suggestions: ["https://api.sparkpost.com/api/v1"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.AmazonSES},
+        key: :region,
+        type: {:string},
+        description: "`Swoosh.Adapters.AmazonSES` adapter specific setting",
+        suggestions: ["us-east-1", "us-east-2"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.AmazonSES},
+        key: :access_key,
+        type: :string,
+        description: "`Swoosh.Adapters.AmazonSES` adapter specific setting",
+        suggestions: ["aws-access-key"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.AmazonSES},
+        key: :secret,
+        type: :string,
+        description: "`Swoosh.Adapters.AmazonSES` adapter specific setting",
+        suggestions: ["aws-secret-key"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.Dyn},
+        key: :api_key,
+        type: :string,
+        description: "`Swoosh.Adapters.Dyn` adapter specific setting",
+        suggestions: ["my-api-key"]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SocketLabs},
+        key: :server_id,
+        type: :string,
+        description: "`Swoosh.Adapters.SocketLabs` adapter specific setting",
+        suggestions: [""]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.SocketLabs},
+        key: :api_key,
+        type: :string,
+        description: "`Swoosh.Adapters.SocketLabs` adapter specific setting",
+        suggestions: [""]
+      },
+      %{
+        group: {:subgroup, Swoosh.Adapters.Gmail},
+        key: :access_token,
+        type: :string,
+        description: "`Swoosh.Adapters.Gmail` adapter specific setting",
+        suggestions: [""]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :uri_schemes,
+    type: :group,
+    description: "URI schemes related settings",
+    children: [
+      %{
+        key: :valid_schemes,
+        type: {:list, :string},
+        description: "List of the scheme part that is considered valid to be an URL",
+        suggestions: [
+          [
+            "https",
+            "http",
+            "dat",
+            "dweb",
+            "gopher",
+            "ipfs",
+            "ipns",
+            "irc",
+            "ircs",
+            "magnet",
+            "mailto",
+            "mumble",
+            "ssb",
+            "xmpp"
+          ]
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :instance,
+    type: :group,
+    description: "Instance-related settings",
+    children: [
+      %{
+        key: :name,
+        type: :string,
+        description: "Name of the instance",
+        suggestions: [
+          "Pleroma"
+        ]
+      },
+      %{
+        key: :email,
+        type: :string,
+        description: "Email used to reach an Administrator/Moderator of the instance",
+        suggestions: [
+          "email@example.com"
+        ]
+      },
+      %{
+        key: :notify_email,
+        type: :string,
+        description: "Email used for notifications",
+        suggestions: [
+          "notify@example.com"
+        ]
+      },
+      %{
+        key: :description,
+        type: :string,
+        description: "The instance's description, can be seen in nodeinfo and /api/v1/instance",
+        suggestions: [
+          "Very cool instance"
+        ]
+      },
+      %{
+        key: :limit,
+        type: :integer,
+        description: "Posts character limit (CW/Subject included in the counter)",
+        suggestions: [
+          5_000
+        ]
+      },
+      %{
+        key: :remote_limit,
+        type: :integer,
+        description: "Hard character limit beyond which remote posts will be dropped",
+        suggestions: [
+          100_000
+        ]
+      },
+      %{
+        key: :upload_limit,
+        type: :integer,
+        description: "File size limit of uploads (except for avatar, background, banner)",
+        suggestions: [
+          16_000_000
+        ]
+      },
+      %{
+        key: :avatar_upload_limit,
+        type: :integer,
+        description: "File size limit of user's profile avatars",
+        suggestions: [
+          2_000_000
+        ]
+      },
+      %{
+        key: :background_upload_limit,
+        type: :integer,
+        description: "File size limit of user's profile backgrounds",
+        suggestions: [
+          4_000_000
+        ]
+      },
+      %{
+        key: :banner_upload_limit,
+        type: :integer,
+        description: "File size limit of user's profile banners",
+        suggestions: [
+          4_000_000
+        ]
+      },
+      %{
+        key: :poll_limits,
+        type: :map,
+        description: "A map with poll limits for local polls",
+        suggestions: [
+          %{
+            max_options: 20,
+            max_option_chars: 200,
+            min_expiration: 0,
+            max_expiration: 31_536_000
+          }
+        ],
+        children: [
+          %{
+            key: :max_options,
+            type: :integer,
+            description: "Maximum number of options",
+            suggestions: [20]
+          },
+          %{
+            key: :max_option_chars,
+            type: :integer,
+            description: "Maximum number of characters per option",
+            suggestions: [200]
+          },
+          %{
+            key: :min_expiration,
+            type: :integer,
+            description: "Minimum expiration time (in seconds)",
+            suggestions: [0]
+          },
+          %{
+            key: :max_expiration,
+            type: :integer,
+            description: "Maximum expiration time (in seconds)",
+            suggestions: [3600]
+          }
+        ]
+      },
+      %{
+        key: :registrations_open,
+        type: :boolean,
+        description: "Enable registrations for anyone, invitations can be enabled when false",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :invites_enabled,
+        type: :boolean,
+        description: "Enable user invitations for admins (depends on registrations_open: false)",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :account_activation_required,
+        type: :boolean,
+        description: "Require users to confirm their emails before signing in",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :federating,
+        type: :boolean,
+        description: "Enable federation with other instances",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :federation_incoming_replies_max_depth,
+        type: :integer,
+        description:
+          "Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while" <>
+            " fetching very long threads. If set to nil, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes",
+        suggestions: [
+          100
+        ]
+      },
+      %{
+        key: :federation_reachability_timeout_days,
+        type: :integer,
+        description:
+          "Timeout (in days) of each external federation target being unreachable prior to pausing federating to it",
+        suggestions: [
+          7
+        ]
+      },
+      %{
+        key: :federation_publisher_modules,
+        type: [:list, :module],
+        description: "List of modules for federation publishing",
+        suggestions: [
+          Pleroma.Web.ActivityPub.Publisher,
+          Pleroma.Web.Websub,
+          Pleroma.Web.Salmo
+        ]
+      },
+      %{
+        key: :allow_relay,
+        type: :boolean,
+        description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :rewrite_policy,
+        type: {:list, :module},
+        description: "A list of MRF policies enabled",
+        suggestions: [
+          Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
+          Generator.mrf_list()
+        ]
+      },
+      %{
+        key: :public,
+        type: :boolean,
+        description:
+          "Makes the client API in authentificated mode-only except for user-profiles." <>
+            " Useful for disabling the Local Timeline and The Whole Known Network",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :quarantined_instances,
+        type: {:list, :string},
+        description:
+          "List of ActivityPub instances where private(DMs, followers-only) activities will not be send",
+        suggestions: [
+          "quarantined.com",
+          "*.quarantined.com"
+        ]
+      },
+      %{
+        key: :managed_config,
+        type: :boolean,
+        description:
+          "Whenether the config for pleroma-fe is configured in this config or in static/config.json",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :static_dir,
+        type: :string,
+        description: "Instance static directory",
+        suggestions: [
+          "instance/static/"
+        ]
+      },
+      %{
+        key: :allowed_post_formats,
+        type: {:list, :string},
+        description: "MIME-type list of formats allowed to be posted (transformed into HTML)",
+        suggestions: [
+          [
+            "text/plain",
+            "text/html",
+            "text/markdown",
+            "text/bbcode"
+          ]
+        ]
+      },
+      %{
+        key: :mrf_transparency,
+        type: :boolean,
+        description:
+          "Make the content of your Message Rewrite Facility settings public (via nodeinfo)",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :mrf_transparency_exclusions,
+        type: {:list, :string},
+        description:
+          "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value",
+        suggestions: [
+          ["exclusion.com"]
+        ]
+      },
+      %{
+        key: :extended_nickname_format,
+        type: :boolean,
+        description:
+          "Set to true to use extended local nicknames format (allows underscores/dashes)." <>
+            " This will break federation with older software for theses nicknames",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :max_pinned_statuses,
+        type: :integer,
+        description: "The maximum number of pinned statuses. 0 will disable the feature",
+        suggestions: [
+          0,
+          1,
+          3
+        ]
+      },
+      %{
+        key: :autofollowed_nicknames,
+        type: {:list, :string},
+        description:
+          "Set to nicknames of (local) users that every new user should automatically follow",
+        suggestions: [
+          "lain",
+          "kaniini",
+          "lanodan",
+          "rinpatch"
+        ]
+      },
+      %{
+        key: :no_attachment_links,
+        type: :boolean,
+        description:
+          "Set to true to disable automatically adding attachment link text to statuses",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :welcome_message,
+        type: :string,
+        description:
+          "A message that will be send to a newly registered users as a direct message",
+        suggestions: [
+          "Hi, @username! Welcome to the board!",
+          nil
+        ]
+      },
+      %{
+        key: :welcome_user_nickname,
+        type: :string,
+        description: "The nickname of the local user that sends the welcome message",
+        suggestions: [
+          "lain",
+          nil
+        ]
+      },
+      %{
+        key: :max_report_comment_size,
+        type: :integer,
+        description: "The maximum size of the report comment (Default: 1000)",
+        suggestions: [
+          1_000
+        ]
+      },
+      %{
+        key: :safe_dm_mentions,
+        type: :boolean,
+        description:
+          "If set to true, only mentions at the beginning of a post will be used to address people in direct messages." <>
+            " This is to prevent accidental mentioning of people when talking about them (e.g. \"@friend hey i really don't like @enemy\")." <>
+            " Default: false",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :healthcheck,
+        type: :boolean,
+        description: "If set to true, system data will be shown on /api/pleroma/healthcheck",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :remote_post_retention_days,
+        type: :integer,
+        description:
+          "The default amount of days to retain remote posts when pruning the database",
+        suggestions: [
+          90
+        ]
+      },
+      %{
+        key: :user_bio_length,
+        type: :integer,
+        description: "A user bio maximum length (default: 5000)",
+        suggestions: [
+          5_000
+        ]
+      },
+      %{
+        key: :user_name_length,
+        type: :integer,
+        description: "A user name maximum length (default: 100)",
+        suggestions: [
+          100
+        ]
+      },
+      %{
+        key: :skip_thread_containment,
+        type: :boolean,
+        description: "Skip filter out broken threads. The default is true",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :limit_to_local_content,
+        type: [:atom, false],
+        description:
+          "Limit unauthenticated users to search for local statutes and users only. The default is :unauthenticated ",
+        suggestions: [
+          :unauthenticated,
+          :all,
+          false
+        ]
+      },
+      %{
+        key: :dynamic_configuration,
+        type: :boolean,
+        description:
+          "Allow transferring configuration to DB with the subsequent customization from Admin api. Defaults to `false`",
+        suggestions: [
+          true,
+          false
+        ]
+      },
+      %{
+        key: :max_account_fields,
+        type: :integer,
+        description: "The maximum number of custom fields in the user profile (default: 10)",
+        suggestions: [
+          10
+        ]
+      },
+      %{
+        key: :max_remote_account_fields,
+        type: :integer,
+        description:
+          "The maximum number of custom fields in the remote user profile (default: 20)",
+        suggestions: [
+          20
+        ]
+      },
+      %{
+        key: :account_field_name_length,
+        type: :integer,
+        description: "An account field name maximum length (default: 512)",
+        suggestions: [
+          512
+        ]
+      },
+      %{
+        key: :account_field_value_length,
+        type: :integer,
+        description: "An account field value maximum length (default: 512)",
+        suggestions: [
+          512
+        ]
+      },
+      %{
+        key: :external_user_synchronization,
+        type: :boolean,
+        description: "Enabling following/followers counters synchronization for external users",
+        suggestions: [
+          true,
+          false
+        ]
+      }
+    ]
+  },
+  %{
+    group: :logger,
+    type: :group,
+    description: "Logger-related settings",
+    children: [
+      %{
+        key: :backends,
+        type: [:atom, :tuple, :module],
+        description:
+          "Where logs will be send, :console - send logs to stdout, {ExSyslogger, :ex_syslogger} - to syslog, Quack.Logger - to Slack.",
+        suggestions: [[:console, {ExSyslogger, :ex_syslogger}, Quack.Logger]]
+      }
+    ]
+  },
+  %{
+    group: :logger,
+    type: :group,
+    key: :ex_syslogger,
+    description: "ExSyslogger-related settings",
+    children: [
+      %{
+        key: :level,
+        type: :atom,
+        description: "Log level",
+        suggestions: [:debug, :info, :warn, :error]
+      },
+      %{
+        key: :ident,
+        type: :string,
+        description:
+          "A string that's prepended to every message, and is typically set to the app name",
+        suggestions: ["pleroma"]
+      },
+      %{
+        key: :format,
+        type: :string,
+        description: "It defaults to \"$date $time [$level] $levelpad$node $metadata $message\"",
+        suggestions: ["$metadata[$level] $message"]
+      },
+      %{
+        key: :metadata,
+        type: {:list, :atom},
+        description: "",
+        suggestions: [[:request_id]]
+      }
+    ]
+  },
+  %{
+    group: :logger,
+    type: :group,
+    key: :console,
+    description: "Console logger settings",
+    children: [
+      %{
+        key: :level,
+        type: :atom,
+        description: "Log level",
+        suggestions: [:debug, :info, :warn, :error]
+      },
+      %{
+        key: :format,
+        type: :string,
+        description: "It defaults to \"$date $time [$level] $levelpad$node $metadata $message\"",
+        suggestions: ["$metadata[$level] $message"]
+      },
+      %{
+        key: :metadata,
+        type: {:list, :atom},
+        description: "",
+        suggestions: [[:request_id]]
+      }
+    ]
+  },
+  %{
+    group: :quack,
+    type: :group,
+    description: "Quack-related settings",
+    children: [
+      %{
+        key: :level,
+        type: :atom,
+        description: "Log level",
+        suggestions: [:debug, :info, :warn, :error]
+      },
+      %{
+        key: :meta,
+        type: {:list, :atom},
+        description: "Configure which metadata you want to report on",
+        suggestions: [
+          :application,
+          :module,
+          :file,
+          :function,
+          :line,
+          :pid,
+          :crash_reason,
+          :initial_call,
+          :registered_name,
+          :all,
+          :none
+        ]
+      },
+      %{
+        key: :webhook_url,
+        type: :string,
+        description: "Configure the Slack incoming webhook",
+        suggestions: ["https://hooks.slack.com/services/YOUR-KEY-HERE"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :frontend_configurations,
+    type: :group,
+    description: "A keyword list that keeps the configuration data for any kind of frontend",
+    children: [
+      %{
+        key: :pleroma_fe,
+        type: :map,
+        description: "Settings for Pleroma FE",
+        suggestions: [
+          %{
+            theme: "pleroma-dark",
+            logo: "/static/logo.png",
+            background: "/images/city.jpg",
+            redirectRootNoLogin: "/main/all",
+            redirectRootLogin: "/main/friends",
+            showInstanceSpecificPanel: true,
+            scopeOptionsEnabled: false,
+            formattingOptionsEnabled: false,
+            collapseMessageWithSubject: false,
+            hidePostStats: false,
+            hideUserStats: false,
+            scopeCopy: true,
+            subjectLineBehavior: "email",
+            alwaysShowSubjectInput: true
+          }
+        ],
+        children: [
+          %{
+            key: :theme,
+            type: :string,
+            description: "Which theme to use, they are defined in styles.json",
+            suggestions: ["pleroma-dark"]
+          },
+          %{
+            key: :logo,
+            type: :string,
+            description: "URL of the logo, defaults to Pleroma's logo",
+            suggestions: ["/static/logo.png"]
+          },
+          %{
+            key: :background,
+            type: :string,
+            description:
+              "URL of the background, unless viewing a user profile with a background that is set",
+            suggestions: ["/images/city.jpg"]
+          },
+          %{
+            key: :redirectRootNoLogin,
+            type: :string,
+            description:
+              "relative URL which indicates where to redirect when a user isn't logged in",
+            suggestions: ["/main/all"]
+          },
+          %{
+            key: :redirectRootLogin,
+            type: :string,
+            description:
+              "relative URL which indicates where to redirect when a user is logged in",
+            suggestions: ["/main/friends"]
+          },
+          %{
+            key: :showInstanceSpecificPanel,
+            type: :boolean,
+            description: "Whenether to show the instance's specific panel",
+            suggestions: [true, false]
+          },
+          %{
+            key: :scopeOptionsEnabled,
+            type: :boolean,
+            description: "Enable setting an notice visibility and subject/CW when posting",
+            suggestions: [true, false]
+          },
+          %{
+            key: :formattingOptionsEnabled,
+            type: :boolean,
+            description:
+              "Enable setting a formatting different than plain-text (ie. HTML, Markdown) when posting, relates to :instance, allowed_post_formats",
+            suggestions: [true, false]
+          },
+          %{
+            key: :collapseMessageWithSubject,
+            type: :boolean,
+            description:
+              "When a message has a subject(aka Content Warning), collapse it by default",
+            suggestions: [true, false]
+          },
+          %{
+            key: :hidePostStats,
+            type: :boolean,
+            description: "Hide notices statistics(repeats, favorites, ...)",
+            suggestions: [true, false]
+          },
+          %{
+            key: :hideUserStats,
+            type: :boolean,
+            description:
+              "Hide profile statistics(posts, posts per day, followers, followings, ...)",
+            suggestions: [true, false]
+          },
+          %{
+            key: :scopeCopy,
+            type: :boolean,
+            description:
+              "Copy the scope (private/unlisted/public) in replies to posts by default",
+            suggestions: [true, false]
+          },
+          %{
+            key: :subjectLineBehavior,
+            type: :string,
+            description: "Allows changing the default behaviour of subject lines in replies.
+          `email`: Copy and preprend re:, as in email,
+          `masto`: Copy verbatim, as in Mastodon,
+          `noop`: Don't copy the subjec",
+            suggestions: ["email", "masto", "noop"]
+          },
+          %{
+            key: :alwaysShowSubjectInput,
+            type: :boolean,
+            description: "When set to false, auto-hide the subject field when it's empty",
+            suggestions: [true, false]
+          }
+        ]
+      },
+      %{
+        key: :masto_fe,
+        type: :map,
+        description: "Settings for Masto FE",
+        suggestions: [
+          %{
+            showInstanceSpecificPanel: true
+          }
+        ],
+        children: [
+          %{
+            key: :showInstanceSpecificPanel,
+            type: :boolean,
+            description: "Whenether to show the instance's specific panel",
+            suggestions: [true, false]
+          }
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :assets,
+    type: :group,
+    description:
+      "This section configures assets to be used with various frontends. Currently the only option relates to mascots on the mastodon frontend",
+    children: [
+      %{
+        key: :mascots,
+        type: :keyword,
+        description:
+          "Keyword of mascots, each element MUST contain both a url and a mime_type key",
+        suggestions: [
+          [
+            pleroma_fox_tan: %{
+              url: "/images/pleroma-fox-tan-smol.png",
+              mime_type: "image/png"
+            },
+            pleroma_fox_tan_shy: %{
+              url: "/images/pleroma-fox-tan-shy.png",
+              mime_type: "image/png"
+            }
+          ]
+        ]
+      },
+      %{
+        key: :default_mascot,
+        type: :atom,
+        description:
+          "This will be used as the default mascot on MastoFE (default: :pleroma_fox_tan)",
+        suggestions: [
+          :pleroma_fox_tan
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :mrf_simple,
+    type: :group,
+    description: "Message Rewrite Facility",
+    children: [
+      %{
+        key: :media_removal,
+        type: {:list, :string},
+        description: "List of instances to remove medias from",
+        suggestions: ["example.com", "*.example.com"]
+      },
+      %{
+        key: :media_nsfw,
+        type: {:list, :string},
+        description: "List of instances to put medias as NSFW(sensitive) from",
+        suggestions: ["example.com", "*.example.com"]
+      },
+      %{
+        key: :federated_timeline_removal,
+        type: {:list, :string},
+        description:
+          "List of instances to remove from Federated (aka The Whole Known Network) Timeline",
+        suggestions: ["example.com", "*.example.com"]
+      },
+      %{
+        key: :reject,
+        type: {:list, :string},
+        description: "List of instances to reject any activities from",
+        suggestions: ["example.com", "*.example.com"]
+      },
+      %{
+        key: :accept,
+        type: {:list, :string},
+        description: "List of instances to accept any activities from",
+        suggestions: ["example.com", "*.example.com"]
+      },
+      %{
+        key: :report_removal,
+        type: {:list, :string},
+        description: "List of instances to reject reports from",
+        suggestions: ["example.com", "*.example.com"]
+      },
+      %{
+        key: :avatar_removal,
+        type: {:list, :string},
+        description: "List of instances to strip avatars from",
+        suggestions: ["example.com", "*.example.com"]
+      },
+      %{
+        key: :banner_removal,
+        type: {:list, :string},
+        description: "List of instances to strip banners from",
+        suggestions: ["example.com", "*.example.com"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :mrf_subchain,
+    type: :group,
+    description:
+      "This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
+        " All criteria are configured as a map of regular expressions to lists of policy modules.",
+    children: [
+      %{
+        key: :match_actor,
+        type: :map,
+        description: "Matches a series of regular expressions against the actor field",
+        suggestions: [
+          %{
+            ~r/https:\/\/example.com/s => [Pleroma.Web.ActivityPub.MRF.DropPolicy]
+          }
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :mrf_rejectnonpublic,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :allow_followersonly,
+        type: :boolean,
+        description: "whether to allow followers-only posts",
+        suggestions: [true, false]
+      },
+      %{
+        key: :allow_direct,
+        type: :boolean,
+        description: "whether to allow direct messages",
+        suggestions: [true, false]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :mrf_hellthread,
+    type: :group,
+    description: "Block messages with too much mentions",
+    children: [
+      %{
+        key: :delist_threshold,
+        type: :integer,
+        description:
+          "Number of mentioned users after which the message gets delisted (the message can still be seen, " <>
+            " but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable",
+        suggestions: [10]
+      },
+      %{
+        key: :reject_threshold,
+        type: :integer,
+        description:
+          "Number of mentioned users after which the messaged gets rejected. Set to 0 to disable",
+        suggestions: [20]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :mrf_keyword,
+    type: :group,
+    description: "Reject or Word-Replace messages with a keyword or regex",
+    children: [
+      %{
+        key: :reject,
+        type: [:string, :regex],
+        description:
+          "A list of patterns which result in message being rejected, each pattern can be a string or a regular expression",
+        suggestions: ["foo", ~r/foo/iu]
+      },
+      %{
+        key: :federated_timeline_removal,
+        type: [:string, :regex],
+        description:
+          "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a regular expression",
+        suggestions: ["foo", ~r/foo/iu]
+      },
+      %{
+        key: :replace,
+        type: [{:string, :string}, {:regex, :string}],
+        description:
+          "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a regular expression",
+        suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :mrf_mention,
+    type: :group,
+    description: "Block messages which mention a user",
+    children: [
+      %{
+        key: :actors,
+        type: {:list, :string},
+        description: "A list of actors, for which to drop any posts mentioning",
+        suggestions: [["actor1", "actor2"]]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :mrf_vocabulary,
+    type: :group,
+    description: "Filter messages which belong to certain activity vocabularies",
+    children: [
+      %{
+        key: :accept,
+        type: {:list, :string},
+        description:
+          "A list of ActivityStreams terms to accept. If empty, all supported messages are accepted",
+        suggestions: [["Create", "Follow", "Mention", "Announce", "Like"]]
+      },
+      %{
+        key: :reject,
+        type: {:list, :string},
+        description:
+          "A list of ActivityStreams terms to reject. If empty, no messages are rejected",
+        suggestions: [["Create", "Follow", "Mention", "Announce", "Like"]]
+      }
+    ]
+  },
+  # %{
+  #   group: :pleroma,
+  #   key: :mrf_user_allowlist,
+  #   type: :group,
+  #   description:
+  #     "The keys in this section are the domain names that the policy should apply to." <>
+  #       " Each key should be assigned a list of users that should be allowed through by their ActivityPub ID",
+  #   children: [
+  #     ["example.org": ["https://example.org/users/admin"]],
+  #     suggestions: [
+  #       ["example.org": ["https://example.org/users/admin"]]
+  #     ]
+  #   ]
+  # },
+  %{
+    group: :pleroma,
+    key: :media_proxy,
+    type: :group,
+    description: "Media proxy",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "Enables proxying of remote media to the instance's proxy",
+        suggestions: [true, false]
+      },
+      %{
+        key: :base_url,
+        type: :string,
+        description:
+          "The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts",
+        suggestions: ["https://example.com"]
+      },
+      %{
+        key: :proxy_opts,
+        type: :keyword,
+        description: "Options for Pleroma.ReverseProxy",
+        suggestions: [[max_body_length: 25 * 1_048_576, redirect_on_failure: false]]
+      },
+      %{
+        key: :whitelist,
+        type: {:list, :string},
+        description: "List of domains to bypass the mediaproxy",
+        suggestions: ["example.com"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :gopher,
+    type: :group,
+    description: "Gopher settings",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "Enables the gopher interface",
+        suggestions: [true, false]
+      },
+      %{
+        key: :ip,
+        type: :tuple,
+        description: "IP address to bind to",
+        suggestions: [{0, 0, 0, 0}]
+      },
+      %{
+        key: :port,
+        type: :integer,
+        description: "Port to bind to",
+        suggestions: [9999]
+      },
+      %{
+        key: :dstport,
+        type: :integer,
+        description: "Port advertised in urls (optional, defaults to port)",
+        suggestions: [9999]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Web.Endpoint,
+    type: :group,
+    description: "Phoenix endpoint configuration",
+    children: [
+      %{
+        key: :http,
+        type: :keyword,
+        description: "http protocol configuration",
+        suggestions: [
+          [port: 8080, ip: {127, 0, 0, 1}]
+        ],
+        children: [
+          %{
+            key: :dispatch,
+            type: {:list, :tuple},
+            description: "dispatch settings",
+            suggestions: [
+              [
+                {:_,
+                 [
+                   {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
+                   {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
+                    {Phoenix.Transports.WebSocket,
+                     {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
+                   {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
+                 ]}
+                # end copied from config.exs
+              ]
+            ]
+          },
+          %{
+            key: :ip,
+            type: :tuple,
+            description: "ip",
+            suggestions: [
+              {0, 0, 0, 0}
+            ]
+          },
+          %{
+            key: :port,
+            type: :integer,
+            description: "port",
+            suggestions: [
+              2020
+            ]
+          }
+        ]
+      },
+      %{
+        key: :url,
+        type: :keyword,
+        description: "configuration for generating urls",
+        suggestions: [
+          [host: "example.com", port: 2020, scheme: "https"]
+        ],
+        children: [
+          %{
+            key: :host,
+            type: :string,
+            description: "Host",
+            suggestions: [
+              "example.com"
+            ]
+          },
+          %{
+            key: :port,
+            type: :integer,
+            description: "port",
+            suggestions: [
+              2020
+            ]
+          },
+          %{
+            key: :scheme,
+            type: :string,
+            description: "Scheme",
+            suggestions: [
+              "https",
+              "https"
+            ]
+          }
+        ]
+      },
+      %{
+        key: :instrumenters,
+        type: {:list, :module},
+        description: "",
+        suggestions: [Pleroma.Web.Endpoint.Instrumenter]
+      },
+      %{
+        key: :protocol,
+        type: :string,
+        description: "",
+        suggestions: ["https"]
+      },
+      %{
+        key: :secret_key_base,
+        type: :string,
+        description: "",
+        suggestions: ["aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl"]
+      },
+      %{
+        key: :signing_salt,
+        type: :string,
+        description: "",
+        suggestions: ["CqaoopA2"]
+      },
+      %{
+        key: :render_errors,
+        type: :keyword,
+        description: "",
+        suggestions: [[view: Pleroma.Web.ErrorView, accepts: ~w(json)]],
+        children: [
+          %{
+            key: :view,
+            type: :module,
+            description: "",
+            suggestions: [Pleroma.Web.ErrorView]
+          },
+          %{
+            key: :accepts,
+            type: {:list, :string},
+            description: "",
+            suggestions: ["json"]
+          }
+        ]
+      },
+      %{
+        key: :pubsub,
+        type: :keyword,
+        description: "",
+        suggestions: [[name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]],
+        children: [
+          %{
+            key: :name,
+            type: :module,
+            description: "",
+            suggestions: [Pleroma.PubSub]
+          },
+          %{
+            key: :adapter,
+            type: :module,
+            description: "",
+            suggestions: [Phoenix.PubSub.PG2]
+          }
+        ]
+      },
+      %{
+        key: :secure_cookie_flag,
+        type: :boolean,
+        description: "",
+        suggestions: [true, false]
+      },
+      %{
+        key: :extra_cookie_attrs,
+        type: {:list, :string},
+        description: "",
+        suggestions: ["SameSite=Lax"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :activitypub,
+    type: :group,
+    description: "ActivityPub-related settings",
+    children: [
+      %{
+        key: :unfollow_blocked,
+        type: :boolean,
+        description: "Whether blocks result in people getting unfollowed",
+        suggestions: [true, false]
+      },
+      %{
+        key: :outgoing_blocks,
+        type: :boolean,
+        description: "Whether to federate blocks to other instances",
+        suggestions: [true, false]
+      },
+      %{
+        key: :sign_object_fetches,
+        type: :boolean,
+        description: "Sign object fetches with HTTP signatures",
+        suggestions: [true, false]
+      },
+      %{
+        key: :follow_handshake_timeout,
+        type: :integer,
+        description: "Following handshake timeout",
+        suggestions: [500]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :http_security,
+    type: :group,
+    description: "HTTP security settings",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "Whether the managed content security policy is enabled",
+        suggestions: [true, false]
+      },
+      %{
+        key: :sts,
+        type: :boolean,
+        description: "Whether to additionally send a Strict-Transport-Security header",
+        suggestions: [true, false]
+      },
+      %{
+        key: :sts_max_age,
+        type: :integer,
+        description: "The maximum age for the Strict-Transport-Security header if sent",
+        suggestions: [31_536_000]
+      },
+      %{
+        key: :ct_max_age,
+        type: :integer,
+        description: "The maximum age for the Expect-CT header if sent",
+        suggestions: [2_592_000]
+      },
+      %{
+        key: :referrer_policy,
+        type: :string,
+        description: "The referrer policy to use, either \"same-origin\" or \"no-referrer\"",
+        suggestions: ["same-origin", "no-referrer"]
+      },
+      %{
+        key: :report_uri,
+        type: :string,
+        description: "Adds the specified url to report-uri and report-to group in CSP header",
+        suggestions: ["https://example.com/report-uri"]
+      }
+    ]
+  },
+  %{
+    group: :web_push_encryption,
+    key: :vapid_details,
+    type: :group,
+    description:
+      "Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it",
+    children: [
+      %{
+        key: :subject,
+        type: :string,
+        description:
+          "a mailto link for the administrative contact." <>
+            " It's best if this email is not a personal email address, but rather a group email so that if a person leaves an organization," <>
+            " is unavailable for an extended period, or otherwise can't respond, someone else on the list can",
+        suggestions: ["Subject"]
+      },
+      %{
+        key: :public_key,
+        type: :string,
+        description: "VAPID public key",
+        suggestions: ["Public key"]
+      },
+      %{
+        key: :private_key,
+        type: :string,
+        description: "VAPID private keyn",
+        suggestions: ["Private key"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Captcha,
+    type: :group,
+    description: "Captcha-related settings",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "Whether the captcha should be shown on registration",
+        suggestions: [true, false]
+      },
+      %{
+        key: :method,
+        type: :module,
+        description: "The method/service to use for captcha",
+        suggestions: [Pleroma.Captcha.Kocaptcha]
+      },
+      %{
+        key: :seconds_valid,
+        type: :integer,
+        description: "The time in seconds for which the captcha is valid",
+        suggestions: [60]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Captcha.Kocaptcha,
+    type: :group,
+    description:
+      "Kocaptcha is a very simple captcha service with a single API endpoint, the source code is" <>
+        " here: https://github.com/koto-bank/kocaptcha. The default endpoint https://captcha.kotobank.ch is hosted by the developer",
+    children: [
+      %{
+        key: :endpoint,
+        type: :string,
+        description: "the kocaptcha endpoint to use",
+        suggestions: ["https://captcha.kotobank.ch"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    type: :group,
+    description:
+      "Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter",
+    children: [
+      %{
+        key: :admin_token,
+        type: :string,
+        description: "Token",
+        suggestions: ["some_random_token"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma_job_queue,
+    key: :queues,
+    type: :group,
+    description: "Pleroma Job Queue configuration: a list of queues with maximum concurrent jobs",
+    children: [
+      %{
+        key: :federator_outgoing,
+        type: :integer,
+        description: "Outgoing federation queue",
+        suggestions: [50]
+      },
+      %{
+        key: :federator_incoming,
+        type: :integer,
+        description: "Incoming federation queue",
+        suggestions: [50]
+      },
+      %{
+        key: :mailer,
+        type: :integer,
+        description: "Email sender queue, see Pleroma.Emails.Mailer",
+        suggestions: [10]
+      },
+      %{
+        key: :web_push,
+        type: :integer,
+        description: "Web push notifications queue",
+        suggestions: [50]
+      },
+      %{
+        key: :transmogrifier,
+        type: :integer,
+        description: "Transmogrifier queue",
+        suggestions: [20]
+      },
+      %{
+        key: :scheduled_activities,
+        type: :integer,
+        description: "Scheduled activities queue, see Pleroma.ScheduledActivities",
+        suggestions: [10]
+      },
+      %{
+        key: :activity_expiration,
+        type: :integer,
+        description: "Activity expiration queue",
+        suggestions: [10]
+      },
+      %{
+        key: :background,
+        type: :integer,
+        description: "Background queue",
+        suggestions: [5]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Web.Federator.RetryQueue,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "If set to true, failed federation jobs will be retried",
+        suggestions: [true, false]
+      },
+      %{
+        key: :max_jobs,
+        type: :integer,
+        description: "The maximum amount of parallel federation jobs running at the same time",
+        suggestions: [20]
+      },
+      %{
+        key: :initial_timeout,
+        type: :integer,
+        description: "The initial timeout in seconds",
+        suggestions: [30]
+      },
+      %{
+        key: :max_retries,
+        type: :integer,
+        description: "The maximum number of times a federation job is retried",
+        suggestions: [5]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Web.Metadata,
+    type: :group,
+    decsription: "Metadata-related settings",
+    children: [
+      %{
+        key: :providers,
+        type: {:list, :module},
+        description: "List of metadata providers to enable",
+        suggestions: [
+          [
+            Pleroma.Web.Metadata.Providers.OpenGraph,
+            Pleroma.Web.Metadata.Providers.TwitterCard,
+            Pleroma.Web.Metadata.Providers.RelMe
+          ]
+        ]
+      },
+      %{
+        key: :unfurl_nsfw,
+        type: :boolean,
+        description: "If set to true nsfw attachments will be shown in previews",
+        suggestions: [
+          true,
+          false
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :rich_media,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description:
+          "if enabled the instance will parse metadata from attached links to generate link previews",
+        suggestions: [true, false]
+      },
+      %{
+        key: :ignore_hosts,
+        type: {:list, :string},
+        description: "list of hosts which will be ignored by the metadata parser",
+        suggestions: [["accounts.google.com", "xss.website"]]
+      },
+      %{
+        key: :ignore_tld,
+        type: {:list, :string},
+        description: "list TLDs (top-level domains) which will ignore for parse metadata",
+        suggestions: [["local", "localdomain", "lan"]]
+      },
+      %{
+        key: :parsers,
+        type: {:list, :module},
+        description: "list of Rich Media parsers",
+        suggestions: [
+          Generator.richmedia_parsers()
+        ]
+      },
+      %{
+        key: :ttl_setters,
+        type: {:list, :module},
+        description: "list of rich media ttl setters",
+        suggestions: [
+          [Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl]
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :fetch_initial_posts,
+    type: :group,
+    description: "Fetching initial posts settings",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description:
+          "if enabled, when a new user is federated with, fetch some of their latest posts",
+        suggestions: [true, false]
+      },
+      %{
+        key: :pages,
+        type: :integer,
+        description: "the amount of pages to fetch",
+        suggestions: [5]
+      }
+    ]
+  },
+  %{
+    group: :auto_linker,
+    key: :opts,
+    type: :group,
+    description: "Configuration for the auto_linker library",
+    children: [
+      %{
+        key: :class,
+        type: [:string, false],
+        description: "specify the class to be added to the generated link. false to clear",
+        suggestions: ["auto-linker", false]
+      },
+      %{
+        key: :rel,
+        type: [:string, false],
+        description: "override the rel attribute. false to clear",
+        suggestions: ["noopener noreferrer", false]
+      },
+      %{
+        key: :new_window,
+        type: :boolean,
+        description: "set to false to remove target='_blank' attribute",
+        suggestions: [true, false]
+      },
+      %{
+        key: :scheme,
+        type: :boolean,
+        description: "Set to true to link urls with schema http://google.com",
+        suggestions: [true, false]
+      },
+      %{
+        key: :truncate,
+        type: [:integer, false],
+        description:
+          "Set to a number to truncate urls longer then the number. Truncated urls will end in `..`",
+        suggestions: [15, false]
+      },
+      %{
+        key: :strip_prefix,
+        type: :boolean,
+        description: "Strip the scheme prefix",
+        suggestions: [true, false]
+      },
+      %{
+        key: :extra,
+        type: :boolean,
+        description: "link urls with rarely used schemes (magnet, ipfs, irc, etc.)",
+        suggestions: [true, false]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.ScheduledActivity,
+    type: :group,
+    description: "Scheduled activities settings",
+    children: [
+      %{
+        key: :daily_user_limit,
+        type: :integer,
+        description:
+          "the number of scheduled activities a user is allowed to create in a single day (Default: 25)",
+        suggestions: [25]
+      },
+      %{
+        key: :total_user_limit,
+        type: :integer,
+        description:
+          "the number of scheduled activities a user is allowed to create in total (Default: 300)",
+        suggestions: [300]
+      },
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "whether scheduled activities are sent to the job queue to be executed",
+        suggestions: [true, false]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.ActivityExpiration,
+    type: :group,
+    description: "Expired activity settings",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "whether expired activities will be sent to the job queue to be deleted",
+        suggestions: [true, false]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    type: :group,
+    description: "Authenticator",
+    children: [
+      %{
+        key: Pleroma.Web.Auth.Authenticator,
+        type: :module,
+        description: "",
+        suggestions: [Pleroma.Web.Auth.PleromaAuthenticator, Pleroma.Web.Auth.LDAPAuthenticator]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :ldap,
+    type: :group,
+    description:
+      "Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <>
+        " will be verified by trying to authenticate (bind) to an LDAP server." <>
+        " If a user exists in the LDAP directory but there is no account with the same name yet on the" <>
+        " Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name.",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "enables LDAP authentication",
+        suggestions: [true, false]
+      },
+      %{
+        key: :host,
+        type: :string,
+        description: "LDAP server hostname",
+        suggestions: ["localhosts"]
+      },
+      %{
+        key: :port,
+        type: :integer,
+        description: "LDAP port, e.g. 389 or 636",
+        suggestions: [389, 636]
+      },
+      %{
+        key: :ssl,
+        type: :boolean,
+        description: "true to use SSL, usually implies the port 636",
+        suggestions: [true, false]
+      },
+      %{
+        key: :sslopts,
+        type: :keyword,
+        description: "additional SSL options",
+        suggestions: []
+      },
+      %{
+        key: :tls,
+        type: :boolean,
+        description: "true to start TLS, usually implies the port 389",
+        suggestions: [true, false]
+      },
+      %{
+        key: :tlsopts,
+        type: :keyword,
+        description: "additional TLS options",
+        suggestions: []
+      },
+      %{
+        key: :base,
+        type: :string,
+        description: "LDAP base, e.g. \"dc=example,dc=com\"",
+        suggestions: ["dc=example,dc=com"]
+      },
+      %{
+        key: :uid,
+        type: :string,
+        description:
+          "LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
+        suggestions: ["cn"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :auth,
+    type: :group,
+    description: "Authentication / authorization settings",
+    children: [
+      %{
+        key: :auth_template,
+        type: :string,
+        description:
+          "authentication form template. By default it's show.html which corresponds to lib/pleroma/web/templates/o_auth/o_auth/show.html.ee",
+        suggestions: ["show.html"]
+      },
+      %{
+        key: :oauth_consumer_template,
+        type: :string,
+        description:
+          "OAuth consumer mode authentication form template. By default it's consumer.html which corresponds to" <>
+            " lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex",
+        suggestions: ["consumer.html"]
+      },
+      %{
+        key: :oauth_consumer_strategies,
+        type: :string,
+        description:
+          "the list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
+            " Each entry in this space-delimited string should be of format <strategy> or <strategy>:<dependency>" <>
+            " (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_<strategy>).",
+        suggestions: ["twitter", "keycloak:ueberauth_keycloak_strategy"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :email_notifications,
+    type: :group,
+    description: "Email notifications settings",
+    children: [
+      %{
+        key: :digest,
+        type: :map,
+        description:
+          "emails of \"what you've missed\" for users who have been inactive for a while",
+        suggestions: [
+          %{
+            active: false,
+            schedule: "0 0 * * 0",
+            interval: 7,
+            inactivity_threshold: 7
+          }
+        ],
+        children: [
+          %{
+            key: :active,
+            type: :boolean,
+            description: "globally enable or disable digest emails",
+            suggestions: [true, false]
+          },
+          %{
+            key: :schedule,
+            type: :string,
+            description:
+              "When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\"",
+            suggestions: ["0 0 * * 0"]
+          },
+          %{
+            key: :interval,
+            type: :ininteger,
+            description: "Minimum interval between digest emails to one user",
+            suggestions: [7]
+          },
+          %{
+            key: :inactivity_threshold,
+            type: :integer,
+            description: "Minimum user inactivity threshold",
+            suggestions: [7]
+          }
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Emails.UserEmail,
+    type: :group,
+    description: "Email template settings",
+    children: [
+      %{
+        key: :logo,
+        # type: [:string, nil],
+        description: "a path to a custom logo. Set it to nil to use the default Pleroma logo",
+        suggestions: ["some/path/logo.png", nil]
+      },
+      %{
+        key: :styling,
+        type: :map,
+        description: "a map with color settings for email templates.",
+        suggestions: [
+          %{
+            link_color: "#d8a070",
+            background_color: "#2C3645",
+            content_background_color: "#1B2635",
+            header_color: "#d8a070",
+            text_color: "#b9b9ba",
+            text_muted_color: "#b9b9ba"
+          }
+        ],
+        children: [
+          %{
+            key: :link_color,
+            type: :string,
+            description: "",
+            suggestions: ["#d8a070"]
+          },
+          %{
+            key: :background_color,
+            type: :string,
+            description: "",
+            suggestions: ["#2C3645"]
+          },
+          %{
+            key: :content_background_color,
+            type: :string,
+            description: "",
+            suggestions: ["#1B2635"]
+          },
+          %{
+            key: :header_color,
+            type: :string,
+            description: "",
+            suggestions: ["#d8a070"]
+          },
+          %{
+            key: :text_color,
+            type: :string,
+            description: "",
+            suggestions: ["#b9b9ba"]
+          },
+          %{
+            key: :text_muted_color,
+            type: :string,
+            description: "",
+            suggestions: ["#b9b9ba"]
+          }
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :oauth2,
+    type: :group,
+    description: "Configure OAuth 2 provider capabilities",
+    children: [
+      %{
+        key: :token_expires_in,
+        type: :integer,
+        description: "The lifetime in seconds of the access token",
+        suggestions: [600]
+      },
+      %{
+        key: :issue_new_refresh_token,
+        type: :boolean,
+        description:
+          "Keeps old refresh token or generate new refresh token when to obtain an access token",
+        suggestions: [true, false]
+      },
+      %{
+        key: :clean_expired_tokens,
+        type: :boolean,
+        description: "Enable a background job to clean expired oauth tokens. Defaults to false",
+        suggestions: [true, false]
+      },
+      %{
+        key: :clean_expired_tokens_interval,
+        type: :integer,
+        description:
+          "Interval to run the job to clean expired tokens. Defaults to 86_400_000 (24 hours).",
+        suggestions: [86_400_000]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :emoji,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :shortcode_globs,
+        type: {:list, :string},
+        description: "Location of custom emoji files. * can be used as a wildcard",
+        suggestions: [["/emoji/custom/**/*.png"]]
+      },
+      %{
+        key: :pack_extensions,
+        type: {:list, :string},
+        description:
+          "A list of file extensions for emojis, when no emoji.txt for a pack is present",
+        suggestions: [[".png", ".gif"]]
+      },
+      %{
+        key: :groups,
+        type: :keyword,
+        description:
+          "Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname" <>
+            " and the value the location or array of locations. * can be used as a wildcard",
+        suggestions: [
+          [
+            # Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
+            Custom: ["/emoji/*.png", "/emoji/**/*.png"]
+          ]
+        ]
+      },
+      %{
+        key: :default_manifest,
+        type: :string,
+        description:
+          "Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download." <>
+            " Currently only one manifest can be added (no arrays)",
+        suggestions: ["https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :database,
+    type: :group,
+    description: "Database related settings",
+    children: [
+      %{
+        key: :rum_enabled,
+        type: :boolean,
+        description: "If RUM indexes should be used. Defaults to false",
+        suggestions: [true, false]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :rate_limit,
+    type: :group,
+    description: "Rate limit settings. This is an advanced feature and disabled by default.",
+    children: [
+      %{
+        key: :search,
+        type: [:tuple, {:list, :tuple}],
+        description: "for the search requests (account & status search etc.)",
+        suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
+      },
+      %{
+        key: :app_account_creation,
+        type: [:tuple, {:list, :tuple}],
+        description: "for registering user accounts from the same IP address",
+        suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
+      },
+      %{
+        key: :relations_actions,
+        type: [:tuple, {:list, :tuple}],
+        description: "for actions on relations with all users (follow, unfollow)",
+        suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
+      },
+      %{
+        key: :relation_id_action,
+        type: [:tuple, {:list, :tuple}],
+        description: "for actions on relation with a specific user (follow, unfollow)",
+        suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
+      },
+      %{
+        key: :statuses_actions,
+        type: [:tuple, {:list, :tuple}],
+        description:
+          "for create / delete / fav / unfav / reblog / unreblog actions on any statuses",
+        suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
+      },
+      %{
+        key: :status_id_action,
+        type: [:tuple, {:list, :tuple}],
+        description:
+          "for fav / unfav or reblog / unreblog actions on the same status by the same user",
+        suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
+      }
+    ]
+  },
+  %{
+    group: :esshd,
+    type: :group,
+    description:
+      "To enable simple command line interface accessible over ssh, add a setting like this to your configuration file",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "Enables ssh",
+        suggestions: [true, false]
+      },
+      %{
+        key: :priv_dir,
+        type: :string,
+        description: "Dir with ssh keys",
+        suggestions: ["/some/path/ssh_keys"]
+      },
+      %{
+        key: :handler,
+        type: :string,
+        description: "Handler module",
+        suggestions: ["Pleroma.BBS.Handler"]
+      },
+      %{
+        key: :port,
+        type: :integer,
+        description: "Port to connect",
+        suggestions: [10_022]
+      },
+      %{
+        key: :password_authenticator,
+        type: :string,
+        description: "Authenticator module",
+        suggestions: ["Pleroma.BBS.Authenticator"]
+      }
+    ]
+  },
+  %{
+    group: :mime,
+    type: :group,
+    description: "Mime types",
+    children: [
+      %{
+        key: :types,
+        type: :map,
+        description: "",
+        suggestions: [
+          %{
+            "application/xml" => ["xml"],
+            "application/xrd+xml" => ["xrd+xml"],
+            "application/jrd+json" => ["jrd+json"],
+            "application/activity+json" => ["activity+json"],
+            "application/ld+json" => ["activity+json"]
+          }
+        ],
+        children: [
+          %{
+            key: "application/xml",
+            type: {:list, :string},
+            description: "",
+            suggestions: [["xml"]]
+          },
+          %{
+            key: "application/xrd+xml",
+            type: {:list, :string},
+            description: "",
+            suggestions: [["xrd+xml"]]
+          },
+          %{
+            key: "application/jrd+json",
+            type: {:list, :string},
+            description: "",
+            suggestions: [["jrd+json"]]
+          },
+          %{
+            key: "application/activity+json",
+            type: {:list, :string},
+            description: "",
+            suggestions: [["activity+json"]]
+          },
+          %{
+            key: "application/ld+json",
+            type: {:list, :string},
+            description: "",
+            suggestions: [["activity+json"]]
+          }
+        ]
+      }
+    ]
+  },
+  %{
+    group: :tesla,
+    type: :group,
+    description: "Tesla settings",
+    children: [
+      %{
+        key: :adapter,
+        type: :module,
+        description: "Tesla adapter",
+        suggestions: [Tesla.Adapter.Hackney]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :chat,
+    type: :group,
+    description: "Pleroma chat settings",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "",
+        suggestions: [true, false]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :suggestions,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :enabled,
+        type: :boolean,
+        description: "Enables suggestions",
+        suggestions: []
+      },
+      %{
+        key: :third_party_engine,
+        type: :string,
+        description: "URL for third party engine",
+        suggestions: [
+          "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
+        ]
+      },
+      %{
+        key: :timeout,
+        type: :integer,
+        description: "Request timeout to third party engine",
+        suggestions: [300_000]
+      },
+      %{
+        key: :limit,
+        type: :integer,
+        description: "Limit for suggestions",
+        suggestions: [40]
+      },
+      %{
+        key: :web,
+        type: :string,
+        description: "",
+        suggestions: ["https://vinayaka.distsn.org"]
+      }
+    ]
+  },
+  %{
+    group: :prometheus,
+    key: Pleroma.Web.Endpoint.MetricsExporter,
+    type: :group,
+    description: "Prometheus settings",
+    children: [
+      %{
+        key: :path,
+        type: :string,
+        description: "API endpoint with metrics",
+        suggestions: ["/api/pleroma/app_metrics"]
+      }
+    ]
+  },
+  %{
+    group: :http_signatures,
+    type: :group,
+    description: "HTTP Signatures settings",
+    children: [
+      %{
+        key: :adapter,
+        type: :module,
+        description: "",
+        suggestions: [Pleroma.Signature]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.Uploaders.MDII,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :cgi,
+        type: :string,
+        description: "",
+        suggestions: ["https://mdii.sakura.ne.jp/mdii-post.cgi"]
+      },
+      %{
+        key: :files,
+        type: :string,
+        description: "",
+        suggestions: ["https://mdii.sakura.ne.jp"]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :http,
+    type: :group,
+    description: "HTTP settings",
+    children: [
+      %{
+        key: :proxy_url,
+        type: [:string, :atom, nil],
+        description: "",
+        suggestions: ["localhost:9020", {:socks5, :localhost, 3090}, nil]
+      },
+      %{
+        key: :send_user_agent,
+        type: :boolean,
+        description: "",
+        suggestions: [true, false]
+      },
+      %{
+        key: :adapter,
+        type: :keyword,
+        description: "",
+        suggestions: [
+          [
+            ssl_options: [
+              # Workaround for remote server certificate chain issues
+              partial_chain: &:hackney_connect.partial_chain/1,
+              # We don't support TLS v1.3 yet
+              versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
+            ]
+          ]
+        ]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :markup,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :allow_inline_images,
+        type: :boolean,
+        description: "",
+        suggestions: [true, false]
+      },
+      %{
+        key: :allow_headings,
+        type: :boolean,
+        description: "",
+        suggestions: [true, false]
+      },
+      %{
+        key: :allow_tables,
+        type: :boolean,
+        description: "",
+        suggestions: [true, false]
+      },
+      %{
+        key: :allow_fonts,
+        type: :boolean,
+        description: "",
+        suggestions: [true, false]
+      },
+      %{
+        key: :scrub_policy,
+        type: {:list, :module},
+        description: "",
+        suggestions: [[Pleroma.HTML.Transform.MediaProxy, Pleroma.HTML.Scrubber.Default]]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :user,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :deny_follow_blocked,
+        type: :boolean,
+        description: "",
+        suggestions: [true, false]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :mrf_normalize_markup,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :scrub_policy,
+        type: :module,
+        description: "",
+        suggestions: [Pleroma.HTML.Scrubber.Default]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: Pleroma.User,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :restricted_nicknames,
+        type: {:list, :string},
+        description: "",
+        suggestions: [
+          [
+            ".well-known",
+            "~",
+            "about",
+            "activities",
+            "api",
+            "auth",
+            "check_password",
+            "dev",
+            "friend-requests",
+            "inbox",
+            "internal",
+            "main",
+            "media",
+            "nodeinfo",
+            "notice",
+            "oauth",
+            "objects",
+            "ostatus_subscribe",
+            "pleroma",
+            "proxy",
+            "push",
+            "registration",
+            "relay",
+            "settings",
+            "status",
+            "tag",
+            "user-search",
+            "user_exists",
+            "users",
+            "web"
+          ]
+        ]
+      }
+    ]
+  },
+  %{
+    group: :cors_plug,
+    type: :group,
+    description: "",
+    children: [
+      %{
+        key: :max_age,
+        type: :integer,
+        description: "",
+        suggestions: [86_400]
+      },
+      %{
+        key: :methods,
+        type: {:list, :string},
+        description: "",
+        suggestions: [["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"]]
+      },
+      %{
+        key: :expose,
+        type: :string,
+        description: "",
+        suggestions: [
+          [
+            "Link",
+            "X-RateLimit-Reset",
+            "X-RateLimit-Limit",
+            "X-RateLimit-Remaining",
+            "X-Request-Id",
+            "Idempotency-Key"
+          ]
+        ]
+      },
+      %{
+        key: :credentials,
+        type: :boolean,
+        description: "",
+        suggestions: [true, false]
+      },
+      %{
+        key: :headers,
+        type: {:list, :string},
+        description: "",
+        suggestions: [["Authorization", "Content-Type", "Idempotency-Key"]]
+      }
+    ]
+  },
+  %{
+    group: :pleroma,
+    key: :web_cache_ttl,
+    type: :group,
+    description:
+      "The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration.",
+    children: [
+      %{
+        key: :activity_pub,
+        type: :integer,
+        description:
+          "activity pub routes (except question activities). Defaults to `nil` (no expiration).",
+        suggestions: [30_000, nil]
+      },
+      %{
+        key: :activity_pub_question,
+        type: :integer,
+        description:
+          "activity pub routes (question activities). Defaults to `30_000` (30 seconds).",
+        suggestions: [30_000]
+      }
+    ]
+  }
+]
index d79c342be3c092d1337ad037fa228ef6b1324a97..fd608c4598720f62012a91d133a60e8fecc9b90e 100644 (file)
@@ -60,9 +60,13 @@ Authentication is required and the user must be an admin.
 
 - Method: `POST`
 - Params:
-  - `nickname`
-  - `email`
-  - `password`
+  `users`: [
+    {
+      `nickname`,
+      `email`,
+      `password`
+    }
+  ]
 - Response: User’s nickname
 
 ## `/api/pleroma/admin/users/follow`
index 02f90f3e89dd294e30a8adeb91af6f64fe05bb13..9b32baf3a4a70e92f9596fba2c92224fd363d589 100644 (file)
@@ -91,6 +91,20 @@ Additional parameters can be added to the JSON body/Form data:
 - `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour.
 - `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`.
 
+## GET `/api/v1/statuses`
+
+An endpoint to get multiple statuses by IDs.
+
+Required parameters:
+
+- `ids`: array of activity ids
+
+Usage example: `GET /api/v1/statuses/?ids[]=1&ids[]=2`.
+
+Returns: array of Status.
+
+The maximum number of statuses is limited to 100 per request.
+
 ## PATCH `/api/v1/update_credentials`
 
 Additional parameters can be added to the JSON body/Form data:
index 7d343e97ad3c1076981e484865732e1f1aa12705..30fac77dad7d20b1af3c70b7d83d1fe2cb6c82d4 100644 (file)
@@ -252,7 +252,7 @@ See [Admin-API](Admin-API.md)
 * Params:
     * `email`: email of that needs to be verified
 * Authentication: not required
-* Response: 204 No Content 
+* Response: 204 No Content
 
 ## `/api/v1/pleroma/mascot`
 ### Gets user mascot image
@@ -321,11 +321,21 @@ See [Admin-API](Admin-API.md)
 }
 ```
 
+## `/api/pleroma/change_email`
+### Change account email
+* Method `POST`
+* Authentication: required
+* Params:
+    * `password`: user's password
+    * `email`: new email
+* Response: JSON. Returns `{"status": "success"}` if the change was successful, `{"error": "[error message]"}` otherwise
+* Note: Currently, Mastodon has no API for changing email. If they add it in future it might be incompatible with Pleroma.
+
 # Pleroma Conversations
 
 Pleroma Conversations have the same general structure that Mastodon Conversations have. The behavior differs in the following ways when using these endpoints:
 
-1. Pleroma Conversations never add or remove recipients, unless explicitly changed by the user. 
+1. Pleroma Conversations never add or remove recipients, unless explicitly changed by the user.
 2. Pleroma Conversations statuses can be requested by Conversation id.
 3. Pleroma Conversations can be replied to.
 
index 7a8819c911164478c4a401ad70fdf70028af953b..066547bb1a75ed567ce33ffce783e09dac678d87 100644 (file)
-# Configuration
-
-This file describe the configuration, it is recommended to edit the relevant *.secret.exs file instead of the others founds in the ``config`` directory.
-If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.
-
-## Pleroma.Upload
-* `uploader`: Select which `Pleroma.Uploaders` to use
-* `filters`: List of `Pleroma.Upload.Filter` to use.
-* `link_name`: When enabled Pleroma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe`
-* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host.
-* `proxy_remote`: If you're using a remote uploader, Pleroma will proxy media requests instead of redirecting to it.
-* `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation.
-
-Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
-
-## Pleroma.Uploaders.Local
-* `uploads`: Which directory to store the user-uploads in, relative to pleroma’s working directory
-
-## Pleroma.Uploaders.S3
-* `bucket`: S3 bucket name
-* `bucket_namespace`: S3 bucket namespace
-* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")
-* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
-For example, when using CDN to S3 virtual host format, set "".
-At this time, write CNAME to CDN in public_endpoint.
-
-## Pleroma.Upload.Filter.Mogrify
-
-* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
-
-## Pleroma.Upload.Filter.Dedupe
-
-No specific configuration.
-
-## Pleroma.Upload.Filter.AnonymizeFilename
-
-This filter replaces the filename (not the path) of an upload. For complete obfuscation, add
-`Pleroma.Upload.Filter.Dedupe` before AnonymizeFilename.
-
-* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`.
-
-## Pleroma.Emails.Mailer
-* `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.
-* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.
-* `enabled`: Allows enable/disable send  emails. Default: `false`.
-
-An example for Sendgrid adapter:
-
-```elixir
-config :pleroma, Pleroma.Emails.Mailer,
-  adapter: Swoosh.Adapters.Sendgrid,
-  api_key: "YOUR_API_KEY"
-```
-
-An example for SMTP adapter:
-
-```elixir
-config :pleroma, Pleroma.Emails.Mailer,
-  adapter: Swoosh.Adapters.SMTP,
-  relay: "smtp.gmail.com",
-  username: "YOUR_USERNAME@gmail.com",
-  password: "YOUR_SMTP_PASSWORD",
-  port: 465,
-  ssl: true,
-  tls: :always,
-  auth: :always
-```
-
-## :uri_schemes
-* `valid_schemes`: List of the scheme part that is considered valid to be an URL
-
-## :instance
-* `name`: The instance’s name
-* `email`: Email used to reach an Administrator/Moderator of the instance
-* `notify_email`: Email used for notifications.
-* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``
-* `limit`: Posts character limit (CW/Subject included in the counter)
-* `remote_limit`: Hard character limit beyond which remote posts will be dropped.
-* `upload_limit`: File size limit of uploads (except for avatar, background, banner)
-* `avatar_upload_limit`: File size limit of user’s profile avatars
-* `background_upload_limit`: File size limit of user’s profile backgrounds
-* `banner_upload_limit`: File size limit of user’s profile banners
-* `poll_limits`: A map with poll limits for **local** polls
-  * `max_options`: Maximum number of options
-  * `max_option_chars`: Maximum number of characters per option
-  * `min_expiration`: Minimum expiration time (in seconds)
-  * `max_expiration`: Maximum expiration time (in seconds)
-* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
-* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
-* `account_activation_required`: Require users to confirm their emails before signing in.
-* `federating`: Enable federation with other instances
-* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
-* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
-* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance
-* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
-  * `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default)
-  * `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production
-  * `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See ``:mrf_simple`` section)
-  * `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive)
-  * `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (see ``:mrf_subchain`` section)
-  * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section)
-  * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
-  * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
-  * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
-  * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section)
-  * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (see `:mrf_vocabulary` section)
-* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
-* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
-* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
-* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML)
-* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
-* `mrf_transparency_exclusions`: Exclude specific instance names from MRF transparency.  The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
-* `scope_copy`: Copy the scope (private/unlisted/public) in replies to posts by default.
-* `subject_line_behavior`: Allows changing the default behaviour of subject lines in replies. Valid values:
-  * "email": Copy and preprend re:, as in email.
-  * "masto": Copy verbatim, as in Mastodon.
-  * "noop": Don't copy the subject.
-* `always_show_subject_input`: When set to false, auto-hide the subject field when it's empty.
-* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
-    older software for theses nicknames.
-* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
-* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
-* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses
-* `welcome_message`: A message that will be send to a newly registered users as a direct message.
-* `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
-* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`)
-* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.
-* `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``.
-* `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database.
-* `user_bio_length`: A user bio maximum length (default: `5000`)
-* `user_name_length`: A user name maximum length (default: `100`)
-* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
-* `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
-* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
-* `max_account_fields`: The maximum number of custom fields in the user profile (default: `10`)
-* `max_remote_account_fields`: The maximum number of custom fields in the remote user profile (default: `20`)
-* `account_field_name_length`: An account field name maximum length (default: `512`)
-* `account_field_value_length`: An account field value maximum length (default: `512`)
-* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
-
-
-
-## :logger
-* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack
-
-An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
-```elixir
-config :logger,
-  backends: [{ExSyslogger, :ex_syslogger}]
-
-config :logger, :ex_syslogger,
-  level: :warn
-```
-
-Another example, keeping console output and adding the pid to syslog output:
-```elixir
-config :logger,
-  backends: [:console, {ExSyslogger, :ex_syslogger}]
-
-config :logger, :ex_syslogger,
-  level: :warn,
-  option: [:pid, :ndelay]
-```
-
-See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_syslogger’s documentation](https://hexdocs.pm/ex_syslogger/)
-
-An example of logging info to local syslog, but warn to a Slack channel:
-```elixir
-config :logger,
-  backends: [ {ExSyslogger, :ex_syslogger}, Quack.Logger ],
-  level: :info
-
-config :logger, :ex_syslogger,
-  level: :info,
-  ident: "pleroma",
-  format: "$metadata[$level] $message"
-
-config :quack,
-  level: :warn,
-  meta: [:all],
-  webhook_url: "https://hooks.slack.com/services/YOUR-API-KEY-HERE"
-```
-
-See the [Quack Github](https://github.com/azohra/quack) for more details
-
-## :frontend_configurations
-
-This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` and `masto_fe` are configured.
-
-Frontends can access these settings at `/api/pleroma/frontend_configurations`
-
-To add your own configuration for PleromaFE, use it like this:
-
-```elixir
-config :pleroma, :frontend_configurations,
-  pleroma_fe: %{
-    theme: "pleroma-dark",
-    # ... see /priv/static/static/config.json for the available keys.
-},
-  masto_fe: %{
-    showInstanceSpecificPanel: true
-  }
-```
-
-These settings **need to be complete**, they will override the defaults.
-
-NOTE: for versions < 1.0, you need to set [`:fe`](#fe) to false, as shown a few lines below.
-
-## :fe
-__THIS IS DEPRECATED__
-
-If you are using this method, please change it to the [`frontend_configurations`](#frontend_configurations) method.
-Please **set this option to false** in your config like this:
-
-```elixir
-config :pleroma, :fe, false
-```
-
-This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:instance`` is set to false.
-
-* `theme`: Which theme to use, they are defined in ``styles.json``
-* `logo`: URL of the logo, defaults to Pleroma’s logo
-* `logo_mask`: Whether to use only the logo's shape as a mask (true) or as a regular image (false)
-* `logo_margin`: What margin to use around the logo
-* `background`: URL of the background, unless viewing a user profile with a background that is set
-* `redirect_root_no_login`: relative URL which indicates where to redirect when a user isn’t logged in.
-* `redirect_root_login`: relative URL which indicates where to redirect when a user is logged in.
-* `show_instance_panel`: Whenether to show the instance’s specific panel.
-* `scope_options_enabled`: Enable setting an notice visibility and subject/CW when posting
-* `formatting_options_enabled`: Enable setting a formatting different than plain-text (ie. HTML, Markdown) when posting, relates to ``:instance, allowed_post_formats``
-* `collapse_message_with_subjects`: When a message has a subject(aka Content Warning), collapse it by default
-* `hide_post_stats`: Hide notices statistics(repeats, favorites, …)
-* `hide_user_stats`: Hide profile statistics(posts, posts per day, followers, followings, …)
-
-## :assets
-
-This section configures assets to be used with various frontends. Currently the only option
-relates to mascots on the mastodon frontend
-
-* `mascots`: KeywordList of mascots, each element __MUST__ contain both a `url` and a
-  `mime_type` key.
-* `default_mascot`: An element from `mascots` - This will be used as the default mascot
-  on MastoFE (default: `:pleroma_fox_tan`)
-
-## :mrf_simple
-* `media_removal`: List of instances to remove medias from
-* `media_nsfw`: List of instances to put medias as NSFW(sensitive) from
-* `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline
-* `reject`: List of instances to reject any activities from
-* `accept`: List of instances to accept any activities from
-* `report_removal`: List of instances to reject reports from
-* `avatar_removal`: List of instances to strip avatars from
-* `banner_removal`: List of instances to strip banners from
-
-## :mrf_subchain
-This policy processes messages through an alternate pipeline when a given message matches certain criteria.
-All criteria are configured as a map of regular expressions to lists of policy modules.
-
-* `match_actor`: Matches a series of regular expressions against the actor field.
-
-Example:
-
-```
-config :pleroma, :mrf_subchain,
-  match_actor: %{
-    ~r/https:\/\/example.com/s => [Pleroma.Web.ActivityPub.MRF.DropPolicy]
-  }
-```
-
-## :mrf_rejectnonpublic
-* `allow_followersonly`: whether to allow followers-only posts
-* `allow_direct`: whether to allow direct messages
-
-## :mrf_hellthread
-* `delist_threshold`: Number of mentioned users after which the message gets delisted (the message can still be seen, but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.
-* `reject_threshold`: Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.
-
-## :mrf_keyword
-* `reject`: A list of patterns which result in message being rejected, each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
-* `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
-* `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
-
-## :mrf_mention
-* `actors`: A list of actors, for which to drop any posts mentioning.
-
-## :mrf_vocabulary
-* `accept`: A list of ActivityStreams terms to accept.  If empty, all supported messages are accepted.
-* `reject`: A list of ActivityStreams terms to reject.  If empty, no messages are rejected.
-
-## :media_proxy
-* `enabled`: Enables proxying of remote media to the instance’s proxy
-* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
-* `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`.
-* `whitelist`: List of domains to bypass the mediaproxy
-
-## :gopher
-* `enabled`: Enables the gopher interface
-* `ip`: IP address to bind to
-* `port`: Port to bind to
-* `dstport`: Port advertised in urls (optional, defaults to `port`)
-
-## Pleroma.Web.Endpoint
-`Phoenix` endpoint configuration, all configuration options can be viewed [here](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#module-dynamic-configuration), only common options are listed here
-* `http` - a list containing http protocol configuration, all configuration options can be viewed [here](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html#module-options), only common options are listed here. For deployment using docker, you need to set this to `[ip: {0,0,0,0}, port: 4000]` to make pleroma accessible from other containers (such as your nginx server).
-  - `ip` - a tuple consisting of 4 integers
-  - `port`
-* `url` - a list containing the configuration for generating urls, accepts
-  - `host` - the host without the scheme and a post (e.g `example.com`, not `https://example.com:2020`)
-  - `scheme` - e.g `http`, `https`
-  - `port`
-  - `path`
-* `extra_cookie_attrs` - a list of `Key=Value` strings to be added as non-standard cookie attributes. Defaults to `["SameSite=Lax"]`. See the [SameSite article](https://www.owasp.org/index.php/SameSite) on OWASP for more info.
-
-
-
-**Important note**: if you modify anything inside these lists, default `config.exs` values will be overwritten, which may result in breakage, to make sure this does not happen please copy the default value for the list from `config.exs` and modify/add only what you need
-
-Example:
-```elixir
-config :pleroma, Pleroma.Web.Endpoint,
-  url: [host: "example.com", port: 2020, scheme: "https"],
-  http: [
-    # start copied from config.exs
-    dispatch: [
-      {:_,
-       [
-         {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
-         {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
-          {Phoenix.Transports.WebSocket,
-           {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
-         {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
-       ]}
-    # end copied from config.exs
-    ],
-    port: 8080,
-    ip: {127, 0, 0, 1}
-  ]
-```
-
-This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
-
-## :activitypub
-* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
-* ``outgoing_blocks``: Whether to federate blocks to other instances
-* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
-* ``sign_object_fetches``: Sign object fetches with HTTP signatures
-
-## :http_security
-* ``enabled``: Whether the managed content security policy is enabled
-* ``sts``: Whether to additionally send a `Strict-Transport-Security` header
-* ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent
-* ``ct_max_age``: The maximum age for the `Expect-CT` header if sent
-* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`
-* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
-
-## :mrf_user_allowlist
-
-The keys in this section are the domain names that the policy should apply to.
-Each key should be assigned a list of users that should be allowed through by
-their ActivityPub ID.
-
-An example:
-
-```elixir
-config :pleroma, :mrf_user_allowlist,
-  "example.org": ["https://example.org/users/admin"]
-```
-
-## :web_push_encryption, :vapid_details
-
-Web Push Notifications configuration. You can use the mix task `mix web_push.gen.keypair` to generate it.
-
-* ``subject``: a mailto link for the administrative contact. It’s best if this email is not a personal email address, but rather a group email so that if a person leaves an organization, is unavailable for an extended period, or otherwise can’t respond, someone else on the list can.
-* ``public_key``: VAPID public key
-* ``private_key``: VAPID private key
-
-## Pleroma.Captcha
-* `enabled`: Whether the captcha should be shown on registration
-* `method`: The method/service to use for captcha
-* `seconds_valid`: The time in seconds for which the captcha is valid
-
-### Pleroma.Captcha.Kocaptcha
-Kocaptcha is a very simple captcha service with a single API endpoint,
-the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint
-`https://captcha.kotobank.ch` is hosted by the developer.
-
-* `endpoint`: the kocaptcha endpoint to use
-
-## :admin_token
-
-Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter. Example:
-
-```elixir
-config :pleroma, :admin_token, "somerandomtoken"
-```
-
-You can then do
-
-```sh
-curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerandomtoken"
-```
-
-## :pleroma_job_queue
-
-[Pleroma Job Queue](https://git.pleroma.social/pleroma/pleroma_job_queue) configuration: a list of queues with maximum concurrent jobs.
-
-Pleroma has the following queues:
-
-* `federator_outgoing` - Outgoing federation
-* `federator_incoming` - Incoming federation
-* `mailer` - Email sender, see [`Pleroma.Emails.Mailer`](#pleroma-emails-mailer)
-* `transmogrifier` - Transmogrifier
-* `web_push` - Web push notifications
-* `scheduled_activities` - Scheduled activities, see [`Pleroma.ScheduledActivities`](#pleromascheduledactivity)
-
-Example:
-
-```elixir
-config :pleroma_job_queue, :queues,
-  federator_incoming: 50,
-  federator_outgoing: 50
-```
-
-This config contains two queues: `federator_incoming` and `federator_outgoing`. Both have the `max_jobs` set to `50`.
-
-## Pleroma.Web.Federator.RetryQueue
-
-* `enabled`: If set to `true`, failed federation jobs will be retried
-* `max_jobs`: The maximum amount of parallel federation jobs running at the same time.
-* `initial_timeout`: The initial timeout in seconds
-* `max_retries`: The maximum number of times a federation job is retried
-
-## Pleroma.Web.Metadata
-* `providers`: a list of metadata providers to enable. Providers available:
-  * Pleroma.Web.Metadata.Providers.OpenGraph
-  * Pleroma.Web.Metadata.Providers.TwitterCard
-  * Pleroma.Web.Metadata.Providers.RelMe - add links from user bio with rel=me into the `<header>` as `<link rel=me>`
-* `unfurl_nsfw`: If set to `true` nsfw attachments will be shown in previews
-
-## :rich_media
-* `enabled`: if enabled the instance will parse metadata from attached links to generate link previews
-* `ignore_hosts`: list of hosts which will be ignored by the metadata parser. For example `["accounts.google.com", "xss.website"]`, defaults to `[]`.
-* `ignore_tld`: list TLDs (top-level domains) which will ignore for parse metadata. default is ["local", "localdomain", "lan"]
-* `parsers`: list of Rich Media parsers
-
-## :fetch_initial_posts
-* `enabled`: if enabled, when a new user is federated with, fetch some of their latest posts
-* `pages`: the amount of pages to fetch
-
-## :hackney_pools
-
-Advanced. Tweaks Hackney (http client) connections pools.
-
-There's three pools used:
-
-* `:federation` for the federation jobs.
-  You may want this pool max_connections to be at least equal to the number of federator jobs + retry queue jobs.
-* `:media` for rich media, media proxy
-* `:upload` for uploaded media (if using a remote uploader and `proxy_remote: true`)
-
-For each pool, the options are:
-
-* `max_connections` - how much connections a pool can hold
-* `timeout` - retention duration for connections
-
-## :auto_linker
-
-Configuration for the `auto_linker` library:
-
-* `class: "auto-linker"` - specify the class to be added to the generated link. false to clear
-* `rel: "noopener noreferrer"` - override the rel attribute. false to clear
-* `new_window: true` - set to false to remove `target='_blank'` attribute
-* `scheme: false` - Set to true to link urls with schema `http://google.com`
-* `truncate: false` - Set to a number to truncate urls longer then the number. Truncated urls will end in `..`
-* `strip_prefix: true` - Strip the scheme prefix
-* `extra: false` - link urls with rarely used schemes (magnet, ipfs, irc, etc.)
-
-Example:
-
-```elixir
-config :auto_linker,
-  opts: [
-    scheme: true,
-    extra: true,
-    class: false,
-    strip_prefix: false,
-    new_window: false,
-    rel: false
-  ]
-```
-
-## Pleroma.ScheduledActivity
-
-* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)
-* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
-* `enabled`: whether scheduled activities are sent to the job queue to be executed
-
-## Pleroma.ActivityExpiration
-
-# `enabled`: whether expired activities will be sent to the job queue to be deleted
-
-## Pleroma.Web.Auth.Authenticator
-
-* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
-* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
-
-## :ldap
-
-Use LDAP for user authentication.  When a user logs in to the Pleroma
-instance, the name and password will be verified by trying to authenticate
-(bind) to an LDAP server.  If a user exists in the LDAP directory but there
-is no account with the same name yet on the Pleroma instance then a new
-Pleroma account will be created with the same name as the LDAP user name.
-
-* `enabled`: enables LDAP authentication
-* `host`: LDAP server hostname
-* `port`: LDAP port, e.g. 389 or 636
-* `ssl`: true to use SSL, usually implies the port 636
-* `sslopts`: additional SSL options
-* `tls`: true to start TLS, usually implies the port 389
-* `tlsopts`: additional TLS options
-* `base`: LDAP base, e.g. "dc=example,dc=com"
-* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
-
-## BBS / SSH access
-
-To enable simple command line interface accessible over ssh, add a setting like this to your configuration file:
-
-```exs
-app_dir = File.cwd!
-priv_dir = Path.join([app_dir, "priv/ssh_keys"])
-
-config :esshd,
-  enabled: true,
-  priv_dir: priv_dir,
-  handler: "Pleroma.BBS.Handler",
-  port: 10_022,
-  password_authenticator: "Pleroma.BBS.Authenticator"
-```
-
-Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -m PEM -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT`
-
-## :auth
-
-* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
-* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
-
-Authentication / authorization settings.
-
-* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
-* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
-* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by `OAUTH_CONSUMER_STRATEGIES` environment variable. Each entry in this space-delimited string should be of format `<strategy>` or `<strategy>:<dependency>` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_<strategy>`).
-
-## :email_notifications
-
-Email notifications settings.
-
-  - digest - emails of "what you've missed" for users who have been
-    inactive for a while.
-    - active: globally enable or disable digest emails
-    - schedule: When to send digest email, in [crontab format](https://en.wikipedia.org/wiki/Cron).
-      "0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"
-    - interval: Minimum interval between digest emails to one user
-    - inactivity_threshold: Minimum user inactivity threshold
-
-## Pleroma.Emails.UserEmail
-
-- `:logo` - a path to a custom logo. Set it to `nil` to use the default Pleroma logo.
-- `:styling` - a map with color settings for email templates.
-
-## OAuth consumer mode
-
-OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
-Implementation is based on Ueberauth; see the list of [available strategies](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies).
-
-Note: each strategy is shipped as a separate dependency; in order to get the strategies, run `OAUTH_CONSUMER_STRATEGIES="..." mix deps.get`,
-e.g. `OAUTH_CONSUMER_STRATEGIES="twitter facebook google microsoft" mix deps.get`.
-The server should also be started with `OAUTH_CONSUMER_STRATEGIES="..." mix phx.server` in case you enable any strategies.
-
-Note: each strategy requires separate setup (on external provider side and Pleroma side). Below are the guidelines on setting up most popular strategies.
-
-Note: make sure that `"SameSite=Lax"` is set in `extra_cookie_attrs` when you have this feature enabled. OAuth consumer mode will not work with `"SameSite=Strict"`
-
-* For Twitter, [register an app](https://developer.twitter.com/en/apps), configure callback URL to https://<your_host>/oauth/twitter/callback
-
-* For Facebook, [register an app](https://developers.facebook.com/apps), configure callback URL to https://<your_host>/oauth/facebook/callback, enable Facebook Login service at https://developers.facebook.com/apps/<app_id>/fb-login/settings/
-
-* For Google, [register an app](https://console.developers.google.com), configure callback URL to https://<your_host>/oauth/google/callback
-
-* For Microsoft, [register an app](https://portal.azure.com), configure callback URL to https://<your_host>/oauth/microsoft/callback
-
-Once the app is configured on external OAuth provider side, add app's credentials and strategy-specific settings (if any — e.g. see Microsoft below) to `config/prod.secret.exs`,
-per strategy's documentation (e.g. [ueberauth_twitter](https://github.com/ueberauth/ueberauth_twitter)). Example config basing on environment variables:
-
-```elixir
-# Twitter
-config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
-  consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
-  consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
-
-# Facebook
-config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
-  client_id: System.get_env("FACEBOOK_APP_ID"),
-  client_secret: System.get_env("FACEBOOK_APP_SECRET"),
-  redirect_uri: System.get_env("FACEBOOK_REDIRECT_URI")
-
-# Google
-config :ueberauth, Ueberauth.Strategy.Google.OAuth,
-  client_id: System.get_env("GOOGLE_CLIENT_ID"),
-  client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
-  redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")
-
-# Microsoft
-config :ueberauth, Ueberauth.Strategy.Microsoft.OAuth,
-  client_id: System.get_env("MICROSOFT_CLIENT_ID"),
-  client_secret: System.get_env("MICROSOFT_CLIENT_SECRET")
-
-config :ueberauth, Ueberauth,
-  providers: [
-    microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}
-  ]
-
-# Keycloak
-# Note: make sure to add `keycloak:ueberauth_keycloak_strategy` entry to `OAUTH_CONSUMER_STRATEGIES` environment variable
-keycloak_url = "https://publicly-reachable-keycloak-instance.org:8080"
-
-config :ueberauth, Ueberauth.Strategy.Keycloak.OAuth,
-  client_id: System.get_env("KEYCLOAK_CLIENT_ID"),
-  client_secret: System.get_env("KEYCLOAK_CLIENT_SECRET"),
-  site: keycloak_url,
-  authorize_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/auth",
-  token_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/token",
-  userinfo_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/userinfo",
-  token_method: :post
-
-config :ueberauth, Ueberauth,
-  providers: [
-    keycloak: {Ueberauth.Strategy.Keycloak, [uid_field: :email]}
-  ]
-```
-
-## OAuth 2.0 provider - :oauth2
-
-Configure OAuth 2 provider capabilities:
-
-* `token_expires_in` - The lifetime in seconds of the access token.
-* `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token.
-* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`.
-* `clean_expired_tokens_interval` - Interval to run the job to clean expired tokens. Defaults to `86_400_000` (24 hours).
-
-## :emoji
-* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`
-* `pack_extensions`: A list of file extensions for emojis, when no emoji.txt for a pack is present. Example `[".png", ".gif"]`
-* `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]`
-* `default_manifest`: Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays).
-
-## Database options
-
-### RUM indexing for full text search
-* `rum_enabled`: If RUM indexes should be used. Defaults to `false`.
-
-RUM indexes are an alternative indexing scheme that is not included in PostgreSQL by default. While they may eventually be mainlined, for now they have to be installed as a PostgreSQL extension from https://github.com/postgrespro/rum.
-
-Their advantage over the standard GIN indexes is that they allow efficient ordering of search results by timestamp, which makes search queries a lot faster on larger servers, by one or two orders of magnitude. They take up around 3 times as much space as GIN indexes.
-
-To enable them, both the `rum_enabled` flag has to be set and the following special migration has to be run:
-
-`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`
-
-This will probably take a long time.
-
-## :rate_limit
-
-This is an advanced feature and disabled by default.
-
-A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
-
-* The first element: `scale` (Integer). The time scale in milliseconds.
-* The second element: `limit` (Integer). How many requests to limit in the time scale provided.
-
-It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
-
-See [`Pleroma.Plugs.RateLimiter`](Pleroma.Plugs.RateLimiter.html) documentation for examples.
-
-Supported rate limiters:
-
-* `:search` for the search requests (account & status search etc.)
-* `:app_account_creation` for registering user accounts from the same IP address
-* `:relations_actions` for actions on relations with all users (follow, unfollow)
-* `:relation_id_action` for actions on relation with a specific user (follow, unfollow)
-* `:statuses_actions` for create / delete / fav / unfav / reblog / unreblog actions on any statuses
-* `:status_id_action` for fav / unfav or reblog / unreblog actions on the same status by the same user
+# Configuration\r
+\r
+This file describe the configuration, it is recommended to edit the relevant *.secret.exs file instead of the others founds in the ``config`` directory.\r
+If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\r
+\r
+## Pleroma.Upload\r
+* `uploader`: Select which `Pleroma.Uploaders` to use\r
+* `filters`: List of `Pleroma.Upload.Filter` to use.\r
+* `link_name`: When enabled Pleroma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe`\r
+* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host.\r
+* `proxy_remote`: If you're using a remote uploader, Pleroma will proxy media requests instead of redirecting to it.\r
+* `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation.\r
+\r
+Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.\r
+\r
+## Pleroma.Uploaders.Local\r
+* `uploads`: Which directory to store the user-uploads in, relative to pleroma’s working directory\r
+\r
+## Pleroma.Uploaders.S3\r
+* `bucket`: S3 bucket name\r
+* `bucket_namespace`: S3 bucket namespace\r
+* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")\r
+* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.\r
+For example, when using CDN to S3 virtual host format, set "".\r
+At this time, write CNAME to CDN in public_endpoint.\r
+\r
+## Pleroma.Upload.Filter.Mogrify\r
+\r
+* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.\r
+\r
+## Pleroma.Upload.Filter.Dedupe\r
+\r
+No specific configuration.\r
+\r
+## Pleroma.Upload.Filter.AnonymizeFilename\r
+\r
+This filter replaces the filename (not the path) of an upload. For complete obfuscation, add\r
+`Pleroma.Upload.Filter.Dedupe` before AnonymizeFilename.\r
+\r
+* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`.\r
+\r
+## Pleroma.Emails.Mailer\r
+* `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.\r
+* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.\r
+* `enabled`: Allows enable/disable send  emails. Default: `false`.\r
+\r
+An example for Sendgrid adapter:\r
+\r
+```elixir\r
+config :pleroma, Pleroma.Emails.Mailer,\r
+  adapter: Swoosh.Adapters.Sendgrid,\r
+  api_key: "YOUR_API_KEY"\r
+```\r
+\r
+An example for SMTP adapter:\r
+\r
+```elixir\r
+config :pleroma, Pleroma.Emails.Mailer,\r
+  adapter: Swoosh.Adapters.SMTP,\r
+  relay: "smtp.gmail.com",\r
+  username: "YOUR_USERNAME@gmail.com",\r
+  password: "YOUR_SMTP_PASSWORD",\r
+  port: 465,\r
+  ssl: true,\r
+  tls: :always,\r
+  auth: :always\r
+```\r
+\r
+## :uri_schemes\r
+* `valid_schemes`: List of the scheme part that is considered valid to be an URL\r
+\r
+## :instance\r
+* `name`: The instance’s name\r
+* `email`: Email used to reach an Administrator/Moderator of the instance\r
+* `notify_email`: Email used for notifications.\r
+* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``\r
+* `limit`: Posts character limit (CW/Subject included in the counter)\r
+* `remote_limit`: Hard character limit beyond which remote posts will be dropped.\r
+* `upload_limit`: File size limit of uploads (except for avatar, background, banner)\r
+* `avatar_upload_limit`: File size limit of user’s profile avatars\r
+* `background_upload_limit`: File size limit of user’s profile backgrounds\r
+* `banner_upload_limit`: File size limit of user’s profile banners\r
+* `poll_limits`: A map with poll limits for **local** polls\r
+  * `max_options`: Maximum number of options\r
+  * `max_option_chars`: Maximum number of characters per option\r
+  * `min_expiration`: Minimum expiration time (in seconds)\r
+  * `max_expiration`: Maximum expiration time (in seconds)\r
+* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.\r
+* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).\r
+* `account_activation_required`: Require users to confirm their emails before signing in.\r
+* `federating`: Enable federation with other instances\r
+* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.\r
+* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.\r
+* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance\r
+* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:\r
+  * `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default)\r
+  * `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production\r
+  * `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See ``:mrf_simple`` section)\r
+  * `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive)\r
+  * `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (see ``:mrf_subchain`` section)\r
+  * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section)\r
+  * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.\r
+  * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.\r
+  * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.\r
+  * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section)\r
+  * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (see `:mrf_vocabulary` section)\r
+* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.\r
+* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.\r
+* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``\r
+* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML)\r
+* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).\r
+* `mrf_transparency_exclusions`: Exclude specific instance names from MRF transparency.  The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.\r
+* `scope_copy`: Copy the scope (private/unlisted/public) in replies to posts by default.\r
+* `subject_line_behavior`: Allows changing the default behaviour of subject lines in replies. Valid values:\r
+  * "email": Copy and preprend re:, as in email.\r
+  * "masto": Copy verbatim, as in Mastodon.\r
+  * "noop": Don't copy the subject.\r
+* `always_show_subject_input`: When set to false, auto-hide the subject field when it's empty.\r
+* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with\r
+    older software for theses nicknames.\r
+* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.\r
+* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.\r
+* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses\r
+* `welcome_message`: A message that will be send to a newly registered users as a direct message.\r
+* `welcome_user_nickname`: The nickname of the local user that sends the welcome message.\r
+* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`)\r
+* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.\r
+* `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``.\r
+* `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database.\r
+* `user_bio_length`: A user bio maximum length (default: `5000`)\r
+* `user_name_length`: A user name maximum length (default: `100`)\r
+* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.\r
+* `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.\r
+* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.\r
+* `max_account_fields`: The maximum number of custom fields in the user profile (default: `10`)\r
+* `max_remote_account_fields`: The maximum number of custom fields in the remote user profile (default: `20`)\r
+* `account_field_name_length`: An account field name maximum length (default: `512`)\r
+* `account_field_value_length`: An account field value maximum length (default: `512`)\r
+* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.\r
+\r
+\r
+\r
+## :logger\r
+* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack\r
+\r
+An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:\r
+```elixir\r
+config :logger,\r
+  backends: [{ExSyslogger, :ex_syslogger}]\r
+\r
+config :logger, :ex_syslogger,\r
+  level: :warn\r
+```\r
+\r
+Another example, keeping console output and adding the pid to syslog output:\r
+```elixir\r
+config :logger,\r
+  backends: [:console, {ExSyslogger, :ex_syslogger}]\r
+\r
+config :logger, :ex_syslogger,\r
+  level: :warn,\r
+  option: [:pid, :ndelay]\r
+```\r
+\r
+See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_syslogger’s documentation](https://hexdocs.pm/ex_syslogger/)\r
+\r
+An example of logging info to local syslog, but warn to a Slack channel:\r
+```elixir\r
+config :logger,\r
+  backends: [ {ExSyslogger, :ex_syslogger}, Quack.Logger ],\r
+  level: :info\r
+\r
+config :logger, :ex_syslogger,\r
+  level: :info,\r
+  ident: "pleroma",\r
+  format: "$metadata[$level] $message"\r
+\r
+config :quack,\r
+  level: :warn,\r
+  meta: [:all],\r
+  webhook_url: "https://hooks.slack.com/services/YOUR-API-KEY-HERE"\r
+```\r
+\r
+See the [Quack Github](https://github.com/azohra/quack) for more details\r
+\r
+## :frontend_configurations\r
+\r
+This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` and `masto_fe` are configured.\r
+\r
+Frontends can access these settings at `/api/pleroma/frontend_configurations`\r
+\r
+To add your own configuration for PleromaFE, use it like this:\r
+\r
+```elixir\r
+config :pleroma, :frontend_configurations,\r
+  pleroma_fe: %{\r
+    theme: "pleroma-dark",\r
+    # ... see /priv/static/static/config.json for the available keys.\r
+},\r
+  masto_fe: %{\r
+    showInstanceSpecificPanel: true\r
+  }\r
+```\r
+\r
+These settings **need to be complete**, they will override the defaults.\r
+\r
+NOTE: for versions < 1.0, you need to set [`:fe`](#fe) to false, as shown a few lines below.\r
+\r
+## :fe\r
+__THIS IS DEPRECATED__\r
+\r
+If you are using this method, please change it to the [`frontend_configurations`](#frontend_configurations) method.\r
+Please **set this option to false** in your config like this:\r
+\r
+```elixir\r
+config :pleroma, :fe, false\r
+```\r
+\r
+This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:instance`` is set to false.\r
+\r
+* `theme`: Which theme to use, they are defined in ``styles.json``\r
+* `logo`: URL of the logo, defaults to Pleroma’s logo\r
+* `logo_mask`: Whether to use only the logo's shape as a mask (true) or as a regular image (false)\r
+* `logo_margin`: What margin to use around the logo\r
+* `background`: URL of the background, unless viewing a user profile with a background that is set\r
+* `redirect_root_no_login`: relative URL which indicates where to redirect when a user isn’t logged in.\r
+* `redirect_root_login`: relative URL which indicates where to redirect when a user is logged in.\r
+* `show_instance_panel`: Whenether to show the instance’s specific panel.\r
+* `scope_options_enabled`: Enable setting an notice visibility and subject/CW when posting\r
+* `formatting_options_enabled`: Enable setting a formatting different than plain-text (ie. HTML, Markdown) when posting, relates to ``:instance, allowed_post_formats``\r
+* `collapse_message_with_subjects`: When a message has a subject(aka Content Warning), collapse it by default\r
+* `hide_post_stats`: Hide notices statistics(repeats, favorites, …)\r
+* `hide_user_stats`: Hide profile statistics(posts, posts per day, followers, followings, …)\r
+\r
+## :assets\r
+\r
+This section configures assets to be used with various frontends. Currently the only option\r
+relates to mascots on the mastodon frontend\r
+\r
+* `mascots`: KeywordList of mascots, each element __MUST__ contain both a `url` and a\r
+  `mime_type` key.\r
+* `default_mascot`: An element from `mascots` - This will be used as the default mascot\r
+  on MastoFE (default: `:pleroma_fox_tan`)\r
+\r
+## :mrf_simple\r
+* `media_removal`: List of instances to remove medias from\r
+* `media_nsfw`: List of instances to put medias as NSFW(sensitive) from\r
+* `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline\r
+* `reject`: List of instances to reject any activities from\r
+* `accept`: List of instances to accept any activities from\r
+* `report_removal`: List of instances to reject reports from\r
+* `avatar_removal`: List of instances to strip avatars from\r
+* `banner_removal`: List of instances to strip banners from\r
+\r
+## :mrf_subchain\r
+This policy processes messages through an alternate pipeline when a given message matches certain criteria.\r
+All criteria are configured as a map of regular expressions to lists of policy modules.\r
+\r
+* `match_actor`: Matches a series of regular expressions against the actor field.\r
+\r
+Example:\r
+\r
+```\r
+config :pleroma, :mrf_subchain,\r
+  match_actor: %{\r
+    ~r/https:\/\/example.com/s => [Pleroma.Web.ActivityPub.MRF.DropPolicy]\r
+  }\r
+```\r
+\r
+## :mrf_rejectnonpublic\r
+* `allow_followersonly`: whether to allow followers-only posts\r
+* `allow_direct`: whether to allow direct messages\r
+\r
+## :mrf_hellthread\r
+* `delist_threshold`: Number of mentioned users after which the message gets delisted (the message can still be seen, but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.\r
+* `reject_threshold`: Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.\r
+\r
+## :mrf_keyword\r
+* `reject`: A list of patterns which result in message being rejected, each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)\r
+* `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)\r
+* `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)\r
+\r
+## :mrf_mention\r
+* `actors`: A list of actors, for which to drop any posts mentioning.\r
+\r
+## :mrf_vocabulary\r
+* `accept`: A list of ActivityStreams terms to accept.  If empty, all supported messages are accepted.\r
+* `reject`: A list of ActivityStreams terms to reject.  If empty, no messages are rejected.\r
+\r
+## :media_proxy\r
+* `enabled`: Enables proxying of remote media to the instance’s proxy\r
+* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.\r
+* `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`.\r
+* `whitelist`: List of domains to bypass the mediaproxy\r
+\r
+## :gopher\r
+* `enabled`: Enables the gopher interface\r
+* `ip`: IP address to bind to\r
+* `port`: Port to bind to\r
+* `dstport`: Port advertised in urls (optional, defaults to `port`)\r
+\r
+## Pleroma.Web.Endpoint\r
+`Phoenix` endpoint configuration, all configuration options can be viewed [here](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#module-dynamic-configuration), only common options are listed here\r
+* `http` - a list containing http protocol configuration, all configuration options can be viewed [here](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html#module-options), only common options are listed here. For deployment using docker, you need to set this to `[ip: {0,0,0,0}, port: 4000]` to make pleroma accessible from other containers (such as your nginx server).\r
+  - `ip` - a tuple consisting of 4 integers\r
+  - `port`\r
+* `url` - a list containing the configuration for generating urls, accepts\r
+  - `host` - the host without the scheme and a post (e.g `example.com`, not `https://example.com:2020`)\r
+  - `scheme` - e.g `http`, `https`\r
+  - `port`\r
+  - `path`\r
+* `extra_cookie_attrs` - a list of `Key=Value` strings to be added as non-standard cookie attributes. Defaults to `["SameSite=Lax"]`. See the [SameSite article](https://www.owasp.org/index.php/SameSite) on OWASP for more info.\r
+\r
+\r
+\r
+**Important note**: if you modify anything inside these lists, default `config.exs` values will be overwritten, which may result in breakage, to make sure this does not happen please copy the default value for the list from `config.exs` and modify/add only what you need\r
+\r
+Example:\r
+```elixir\r
+config :pleroma, Pleroma.Web.Endpoint,\r
+  url: [host: "example.com", port: 2020, scheme: "https"],\r
+  http: [\r
+    # start copied from config.exs\r
+    dispatch: [\r
+      {:_,\r
+       [\r
+         {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},\r
+         {"/websocket", Phoenix.Endpoint.CowboyWebSocket,\r
+          {Phoenix.Transports.WebSocket,\r
+           {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},\r
+         {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}\r
+       ]}\r
+    # end copied from config.exs\r
+    ],\r
+    port: 8080,\r
+    ip: {127, 0, 0, 1}\r
+  ]\r
+```\r
+\r
+This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`\r
+\r
+## :activitypub\r
+* ``unfollow_blocked``: Whether blocks result in people getting unfollowed\r
+* ``outgoing_blocks``: Whether to federate blocks to other instances\r
+* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question\r
+* ``sign_object_fetches``: Sign object fetches with HTTP signatures\r
+\r
+## :http_security\r
+* ``enabled``: Whether the managed content security policy is enabled\r
+* ``sts``: Whether to additionally send a `Strict-Transport-Security` header\r
+* ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent\r
+* ``ct_max_age``: The maximum age for the `Expect-CT` header if sent\r
+* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`\r
+* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.\r
+\r
+## :mrf_user_allowlist\r
+\r
+The keys in this section are the domain names that the policy should apply to.\r
+Each key should be assigned a list of users that should be allowed through by\r
+their ActivityPub ID.\r
+\r
+An example:\r
+\r
+```elixir\r
+config :pleroma, :mrf_user_allowlist,\r
+  "example.org": ["https://example.org/users/admin"]\r
+```\r
+\r
+## :web_push_encryption, :vapid_details\r
+\r
+Web Push Notifications configuration. You can use the mix task `mix web_push.gen.keypair` to generate it.\r
+\r
+* ``subject``: a mailto link for the administrative contact. It’s best if this email is not a personal email address, but rather a group email so that if a person leaves an organization, is unavailable for an extended period, or otherwise can’t respond, someone else on the list can.\r
+* ``public_key``: VAPID public key\r
+* ``private_key``: VAPID private key\r
+\r
+## Pleroma.Captcha\r
+* `enabled`: Whether the captcha should be shown on registration\r
+* `method`: The method/service to use for captcha\r
+* `seconds_valid`: The time in seconds for which the captcha is valid\r
+\r
+### Pleroma.Captcha.Kocaptcha\r
+Kocaptcha is a very simple captcha service with a single API endpoint,\r
+the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint\r
+`https://captcha.kotobank.ch` is hosted by the developer.\r
+\r
+* `endpoint`: the kocaptcha endpoint to use\r
+\r
+## :admin_token\r
+\r
+Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter. Example:\r
+\r
+```elixir\r
+config :pleroma, :admin_token, "somerandomtoken"\r
+```\r
+\r
+You can then do\r
+\r
+```sh\r
+curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerandomtoken"\r
+```\r
+\r
+## :pleroma_job_queue\r
+\r
+[Pleroma Job Queue](https://git.pleroma.social/pleroma/pleroma_job_queue) configuration: a list of queues with maximum concurrent jobs.\r
+\r
+Pleroma has the following queues:\r
+\r
+* `federator_outgoing` - Outgoing federation\r
+* `federator_incoming` - Incoming federation\r
+* `mailer` - Email sender, see [`Pleroma.Emails.Mailer`](#pleroma-emails-mailer)\r
+* `transmogrifier` - Transmogrifier\r
+* `web_push` - Web push notifications\r
+* `scheduled_activities` - Scheduled activities, see [`Pleroma.ScheduledActivities`](#pleromascheduledactivity)\r
+\r
+Example:\r
+\r
+```elixir\r
+config :pleroma_job_queue, :queues,\r
+  federator_incoming: 50,\r
+  federator_outgoing: 50\r
+```\r
+\r
+This config contains two queues: `federator_incoming` and `federator_outgoing`. Both have the `max_jobs` set to `50`.\r
+\r
+## Pleroma.Web.Federator.RetryQueue\r
+\r
+* `enabled`: If set to `true`, failed federation jobs will be retried\r
+* `max_jobs`: The maximum amount of parallel federation jobs running at the same time.\r
+* `initial_timeout`: The initial timeout in seconds\r
+* `max_retries`: The maximum number of times a federation job is retried\r
+\r
+## Pleroma.Web.Metadata\r
+* `providers`: a list of metadata providers to enable. Providers available:\r
+  * Pleroma.Web.Metadata.Providers.OpenGraph\r
+  * Pleroma.Web.Metadata.Providers.TwitterCard\r
+  * Pleroma.Web.Metadata.Providers.RelMe - add links from user bio with rel=me into the `<header>` as `<link rel=me>`\r
+* `unfurl_nsfw`: If set to `true` nsfw attachments will be shown in previews\r
+\r
+## :rich_media\r
+* `enabled`: if enabled the instance will parse metadata from attached links to generate link previews\r
+* `ignore_hosts`: list of hosts which will be ignored by the metadata parser. For example `["accounts.google.com", "xss.website"]`, defaults to `[]`.\r
+* `ignore_tld`: list TLDs (top-level domains) which will ignore for parse metadata. default is ["local", "localdomain", "lan"]\r
+* `parsers`: list of Rich Media parsers\r
+\r
+## :fetch_initial_posts\r
+* `enabled`: if enabled, when a new user is federated with, fetch some of their latest posts\r
+* `pages`: the amount of pages to fetch\r
+\r
+## :hackney_pools\r
+\r
+Advanced. Tweaks Hackney (http client) connections pools.\r
+\r
+There's three pools used:\r
+\r
+* `:federation` for the federation jobs.\r
+  You may want this pool max_connections to be at least equal to the number of federator jobs + retry queue jobs.\r
+* `:media` for rich media, media proxy\r
+* `:upload` for uploaded media (if using a remote uploader and `proxy_remote: true`)\r
+\r
+For each pool, the options are:\r
+\r
+* `max_connections` - how much connections a pool can hold\r
+* `timeout` - retention duration for connections\r
+\r
+## :auto_linker\r
+\r
+Configuration for the `auto_linker` library:\r
+\r
+* `class: "auto-linker"` - specify the class to be added to the generated link. false to clear\r
+* `rel: "noopener noreferrer"` - override the rel attribute. false to clear\r
+* `new_window: true` - set to false to remove `target='_blank'` attribute\r
+* `scheme: false` - Set to true to link urls with schema `http://google.com`\r
+* `truncate: false` - Set to a number to truncate urls longer then the number. Truncated urls will end in `..`\r
+* `strip_prefix: true` - Strip the scheme prefix\r
+* `extra: false` - link urls with rarely used schemes (magnet, ipfs, irc, etc.)\r
+\r
+Example:\r
+\r
+```elixir\r
+config :auto_linker,\r
+  opts: [\r
+    scheme: true,\r
+    extra: true,\r
+    class: false,\r
+    strip_prefix: false,\r
+    new_window: false,\r
+    rel: false\r
+  ]\r
+```\r
+\r
+## Pleroma.ScheduledActivity\r
+\r
+* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)\r
+* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)\r
+* `enabled`: whether scheduled activities are sent to the job queue to be executed\r
+\r
+## Pleroma.ActivityExpiration\r
+\r
+# `enabled`: whether expired activities will be sent to the job queue to be deleted\r
+\r
+## Pleroma.Web.Auth.Authenticator\r
+\r
+* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator\r
+* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication\r
+\r
+## :ldap\r
+\r
+Use LDAP for user authentication.  When a user logs in to the Pleroma\r
+instance, the name and password will be verified by trying to authenticate\r
+(bind) to an LDAP server.  If a user exists in the LDAP directory but there\r
+is no account with the same name yet on the Pleroma instance then a new\r
+Pleroma account will be created with the same name as the LDAP user name.\r
+\r
+* `enabled`: enables LDAP authentication\r
+* `host`: LDAP server hostname\r
+* `port`: LDAP port, e.g. 389 or 636\r
+* `ssl`: true to use SSL, usually implies the port 636\r
+* `sslopts`: additional SSL options\r
+* `tls`: true to start TLS, usually implies the port 389\r
+* `tlsopts`: additional TLS options\r
+* `base`: LDAP base, e.g. "dc=example,dc=com"\r
+* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"\r
+\r
+## BBS / SSH access\r
+\r
+To enable simple command line interface accessible over ssh, add a setting like this to your configuration file:\r
+\r
+```exs\r
+app_dir = File.cwd!\r
+priv_dir = Path.join([app_dir, "priv/ssh_keys"])\r
+\r
+config :esshd,\r
+  enabled: true,\r
+  priv_dir: priv_dir,\r
+  handler: "Pleroma.BBS.Handler",\r
+  port: 10_022,\r
+  password_authenticator: "Pleroma.BBS.Authenticator"\r
+```\r
+\r
+Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -m PEM -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT`\r
+\r
+## :auth\r
+\r
+* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator\r
+* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication\r
+\r
+Authentication / authorization settings.\r
+\r
+* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.\r
+* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.\r
+* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by `OAUTH_CONSUMER_STRATEGIES` environment variable. Each entry in this space-delimited string should be of format `<strategy>` or `<strategy>:<dependency>` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_<strategy>`).\r
+\r
+## :email_notifications\r
+\r
+Email notifications settings.\r
+\r
+  - digest - emails of "what you've missed" for users who have been\r
+    inactive for a while.\r
+    - active: globally enable or disable digest emails\r
+    - schedule: When to send digest email, in [crontab format](https://en.wikipedia.org/wiki/Cron).\r
+      "0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"\r
+    - interval: Minimum interval between digest emails to one user\r
+    - inactivity_threshold: Minimum user inactivity threshold\r
+\r
+## Pleroma.Emails.UserEmail\r
+\r
+- `:logo` - a path to a custom logo. Set it to `nil` to use the default Pleroma logo.\r
+- `:styling` - a map with color settings for email templates.\r
+\r
+## OAuth consumer mode\r
+\r
+OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).\r
+Implementation is based on Ueberauth; see the list of [available strategies](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies).\r
+\r
+Note: each strategy is shipped as a separate dependency; in order to get the strategies, run `OAUTH_CONSUMER_STRATEGIES="..." mix deps.get`,\r
+e.g. `OAUTH_CONSUMER_STRATEGIES="twitter facebook google microsoft" mix deps.get`.\r
+The server should also be started with `OAUTH_CONSUMER_STRATEGIES="..." mix phx.server` in case you enable any strategies.\r
+\r
+Note: each strategy requires separate setup (on external provider side and Pleroma side). Below are the guidelines on setting up most popular strategies.\r
+\r
+Note: make sure that `"SameSite=Lax"` is set in `extra_cookie_attrs` when you have this feature enabled. OAuth consumer mode will not work with `"SameSite=Strict"`\r
+\r
+* For Twitter, [register an app](https://developer.twitter.com/en/apps), configure callback URL to https://<your_host>/oauth/twitter/callback\r
+\r
+* For Facebook, [register an app](https://developers.facebook.com/apps), configure callback URL to https://<your_host>/oauth/facebook/callback, enable Facebook Login service at https://developers.facebook.com/apps/<app_id>/fb-login/settings/\r
+\r
+* For Google, [register an app](https://console.developers.google.com), configure callback URL to https://<your_host>/oauth/google/callback\r
+\r
+* For Microsoft, [register an app](https://portal.azure.com), configure callback URL to https://<your_host>/oauth/microsoft/callback\r
+\r
+Once the app is configured on external OAuth provider side, add app's credentials and strategy-specific settings (if any — e.g. see Microsoft below) to `config/prod.secret.exs`,\r
+per strategy's documentation (e.g. [ueberauth_twitter](https://github.com/ueberauth/ueberauth_twitter)). Example config basing on environment variables:\r
+\r
+```elixir\r
+# Twitter\r
+config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,\r
+  consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),\r
+  consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")\r
+\r
+# Facebook\r
+config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,\r
+  client_id: System.get_env("FACEBOOK_APP_ID"),\r
+  client_secret: System.get_env("FACEBOOK_APP_SECRET"),\r
+  redirect_uri: System.get_env("FACEBOOK_REDIRECT_URI")\r
+\r
+# Google\r
+config :ueberauth, Ueberauth.Strategy.Google.OAuth,\r
+  client_id: System.get_env("GOOGLE_CLIENT_ID"),\r
+  client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),\r
+  redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")\r
+\r
+# Microsoft\r
+config :ueberauth, Ueberauth.Strategy.Microsoft.OAuth,\r
+  client_id: System.get_env("MICROSOFT_CLIENT_ID"),\r
+  client_secret: System.get_env("MICROSOFT_CLIENT_SECRET")\r
+\r
+config :ueberauth, Ueberauth,\r
+  providers: [\r
+    microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}\r
+  ]\r
+\r
+# Keycloak\r
+# Note: make sure to add `keycloak:ueberauth_keycloak_strategy` entry to `OAUTH_CONSUMER_STRATEGIES` environment variable\r
+keycloak_url = "https://publicly-reachable-keycloak-instance.org:8080"\r
+\r
+config :ueberauth, Ueberauth.Strategy.Keycloak.OAuth,\r
+  client_id: System.get_env("KEYCLOAK_CLIENT_ID"),\r
+  client_secret: System.get_env("KEYCLOAK_CLIENT_SECRET"),\r
+  site: keycloak_url,\r
+  authorize_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/auth",\r
+  token_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/token",\r
+  userinfo_url: "#{keycloak_url}/auth/realms/master/protocol/openid-connect/userinfo",\r
+  token_method: :post\r
+\r
+config :ueberauth, Ueberauth,\r
+  providers: [\r
+    keycloak: {Ueberauth.Strategy.Keycloak, [uid_field: :email]}\r
+  ]\r
+```\r
+\r
+## OAuth 2.0 provider - :oauth2\r
+\r
+Configure OAuth 2 provider capabilities:\r
+\r
+* `token_expires_in` - The lifetime in seconds of the access token.\r
+* `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token.\r
+* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`.\r
+* `clean_expired_tokens_interval` - Interval to run the job to clean expired tokens. Defaults to `86_400_000` (24 hours).\r
+\r
+## :emoji\r
+* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`\r
+* `pack_extensions`: A list of file extensions for emojis, when no emoji.txt for a pack is present. Example `[".png", ".gif"]`\r
+* `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]`\r
+* `default_manifest`: Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays).\r
+\r
+## Database options\r
+\r
+### RUM indexing for full text search\r
+* `rum_enabled`: If RUM indexes should be used. Defaults to `false`.\r
+\r
+RUM indexes are an alternative indexing scheme that is not included in PostgreSQL by default. While they may eventually be mainlined, for now they have to be installed as a PostgreSQL extension from https://github.com/postgrespro/rum.\r
+\r
+Their advantage over the standard GIN indexes is that they allow efficient ordering of search results by timestamp, which makes search queries a lot faster on larger servers, by one or two orders of magnitude. They take up around 3 times as much space as GIN indexes.\r
+\r
+To enable them, both the `rum_enabled` flag has to be set and the following special migration has to be run:\r
+\r
+`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`\r
+\r
+This will probably take a long time.\r
+\r
+## :rate_limit\r
+\r
+This is an advanced feature and disabled by default.\r
+\r
+A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:\r
+\r
+* The first element: `scale` (Integer). The time scale in milliseconds.\r
+* The second element: `limit` (Integer). How many requests to limit in the time scale provided.\r
+\r
+It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.\r
+\r
+See [`Pleroma.Plugs.RateLimiter`](Pleroma.Plugs.RateLimiter.html) documentation for examples.\r
+\r
+Supported rate limiters:\r
+\r
+* `:search` for the search requests (account & status search etc.)\r
+* `:app_account_creation` for registering user accounts from the same IP address\r
+* `:relations_actions` for actions on relations with all users (follow, unfollow)\r
+* `:relation_id_action` for actions on relation with a specific user (follow, unfollow)\r
+* `:statuses_actions` for create / delete / fav / unfav / reblog / unreblog actions on any statuses\r
+* `:status_id_action` for fav / unfav or reblog / unreblog actions on the same status by the same user\r
+\r
+## :web_cache_ttl\r
+\r
+The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration.\r
+\r
+Available caches:\r
+\r
+* `:activity_pub` - activity pub routes (except question activities). Defaults to `nil` (no expiration).\r
+* `:activity_pub_question` - activity pub routes (question activities). Defaults to `30_000` (30 seconds).\r
index 4cc63472764a2f6dee54fb0dd7c2f80240c477fa..84dccf7f33282eded0b1c6712d5c05d1f63f201a 100644 (file)
@@ -27,7 +27,7 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
     })
   end
 
-  def run(["render_timeline", nickname]) do
+  def run(["render_timeline", nickname | _] = args) do
     start_pleroma()
     user = Pleroma.User.get_by_nickname(nickname)
 
@@ -37,33 +37,37 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
       |> Map.put("blocking_user", user)
       |> Map.put("muting_user", user)
       |> Map.put("user", user)
-      |> Map.put("limit", 80)
+      |> Map.put("limit", 4096)
       |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
       |> Enum.reverse()
 
     inputs = %{
-      "One activity" => Enum.take_random(activities, 1),
-      "Ten activities" => Enum.take_random(activities, 10),
-      "Twenty activities" => Enum.take_random(activities, 20),
-      "Forty activities" => Enum.take_random(activities, 40),
-      "Eighty activities" => Enum.take_random(activities, 80)
+      "1 activity" => Enum.take_random(activities, 1),
+      "10 activities" => Enum.take_random(activities, 10),
+      "20 activities" => Enum.take_random(activities, 20),
+      "40 activities" => Enum.take_random(activities, 40),
+      "80 activities" => Enum.take_random(activities, 80)
     }
 
+    inputs =
+      if Enum.at(args, 2) == "extended" do
+        Map.merge(inputs, %{
+          "200 activities" => Enum.take_random(activities, 200),
+          "500 activities" => Enum.take_random(activities, 500),
+          "2000 activities" => Enum.take_random(activities, 2000),
+          "4096 activities" => Enum.take_random(activities, 4096)
+        })
+      else
+        inputs
+      end
+
     Benchee.run(
       %{
-        "Parallel rendering" => fn activities ->
-          Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
-            activities: activities,
-            for: user,
-            as: :activity
-          })
-        end,
         "Standart rendering" => fn activities ->
           Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
             activities: activities,
             for: user,
-            as: :activity,
-            parallel: false
+            as: :activity
           })
         end
       },
diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex
new file mode 100644 (file)
index 0000000..0d26636
--- /dev/null
@@ -0,0 +1,42 @@
+defmodule Mix.Tasks.Pleroma.Docs do
+  use Mix.Task
+  import Mix.Pleroma
+
+  @shortdoc "Generates docs from descriptions.exs"
+  @moduledoc """
+  Generates docs from `descriptions.exs`.
+
+  Supports two formats: `markdown` and `json`.
+
+  ## Generate Markdown docs
+
+  `mix pleroma.docs`
+
+  ## Generate JSON docs
+
+  `mix pleroma.docs json`
+  """
+
+  def run(["json"]) do
+    do_run(Pleroma.Docs.JSON)
+  end
+
+  def run(_) do
+    do_run(Pleroma.Docs.Markdown)
+  end
+
+  defp do_run(implementation) do
+    start_pleroma()
+
+    with {descriptions, _paths} <- Mix.Config.eval!("config/description.exs"),
+         {:ok, file_path} <-
+           Pleroma.Docs.Generator.process(
+             implementation,
+             descriptions[:pleroma][:config_description]
+           ) do
+      type = if implementation == Pleroma.Docs.Markdown, do: "Markdown", else: "JSON"
+
+      Mix.shell().info([:green, "#{type} docs successfully generated to #{file_path}."])
+    end
+  end
+end
index a7844c36bc909f9985c2ece2bb3c1dc100db34f9..ec558168ae05e43ad5e85f76c18b72623752c87d 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Activity do
   use Ecto.Schema
 
   alias Pleroma.Activity
+  alias Pleroma.Activity.Queries
   alias Pleroma.ActivityExpiration
   alias Pleroma.Bookmark
   alias Pleroma.Notification
@@ -65,8 +66,8 @@ defmodule Pleroma.Activity do
     timestamps()
   end
 
-  def with_joined_object(query) do
-    join(query, :inner, [activity], o in Object,
+  def with_joined_object(query, join_type \\ :inner) do
+    join(query, join_type, [activity], o in Object,
       on:
         fragment(
           "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
@@ -78,10 +79,10 @@ defmodule Pleroma.Activity do
     )
   end
 
-  def with_preloaded_object(query) do
+  def with_preloaded_object(query, join_type \\ :inner) do
     query
     |> has_named_binding?(:object)
-    |> if(do: query, else: with_joined_object(query))
+    |> if(do: query, else: with_joined_object(query, join_type))
     |> preload([activity, object: object], object: object)
   end
 
@@ -107,12 +108,9 @@ defmodule Pleroma.Activity do
   def with_set_thread_muted_field(query, _), do: query
 
   def get_by_ap_id(ap_id) do
-    Repo.one(
-      from(
-        activity in Activity,
-        where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id))
-      )
-    )
+    ap_id
+    |> Queries.by_ap_id()
+    |> Repo.one()
   end
 
   def get_bookmark(%Activity{} = activity, %User{} = user) do
@@ -133,21 +131,10 @@ defmodule Pleroma.Activity do
   end
 
   def get_by_ap_id_with_object(ap_id) do
-    Repo.one(
-      from(
-        activity in Activity,
-        where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)),
-        left_join: o in Object,
-        on:
-          fragment(
-            "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
-            o.data,
-            activity.data,
-            activity.data
-          ),
-        preload: [object: o]
-      )
-    )
+    ap_id
+    |> Queries.by_ap_id()
+    |> with_preloaded_object(:left)
+    |> Repo.one()
   end
 
   def get_by_id(id) do
@@ -158,66 +145,34 @@ defmodule Pleroma.Activity do
   end
 
   def get_by_id_with_object(id) do
-    from(activity in Activity,
-      where: activity.id == ^id,
-      inner_join: o in Object,
-      on:
-        fragment(
-          "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
-          o.data,
-          activity.data,
-          activity.data
-        ),
-      preload: [object: o]
-    )
+    Activity
+    |> where(id: ^id)
+    |> with_preloaded_object()
     |> Repo.one()
   end
 
-  def by_object_ap_id(ap_id) do
-    from(
-      activity in Activity,
-      where:
-        fragment(
-          "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
-          activity.data,
-          activity.data,
-          ^to_string(ap_id)
-        )
-    )
-  end
-
-  def create_by_object_ap_id(ap_ids) when is_list(ap_ids) do
-    from(
-      activity in Activity,
-      where:
-        fragment(
-          "coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
-          activity.data,
-          activity.data,
-          ^ap_ids
-        ),
-      where: fragment("(?)->>'type' = 'Create'", activity.data)
-    )
+  def all_by_ids_with_object(ids) do
+    Activity
+    |> where([a], a.id in ^ids)
+    |> with_preloaded_object()
+    |> Repo.all()
   end
 
-  def create_by_object_ap_id(ap_id) when is_binary(ap_id) do
-    from(
-      activity in Activity,
-      where:
-        fragment(
-          "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
-          activity.data,
-          activity.data,
-          ^to_string(ap_id)
-        ),
-      where: fragment("(?)->>'type' = 'Create'", activity.data)
-    )
+  @doc """
+  Accepts `ap_id` or list of `ap_id`.
+  Returns a query.
+  """
+  @spec create_by_object_ap_id(String.t() | [String.t()]) :: Ecto.Queryable.t()
+  def create_by_object_ap_id(ap_id) do
+    ap_id
+    |> Queries.by_object_id()
+    |> Queries.by_type("Create")
   end
 
-  def create_by_object_ap_id(_), do: nil
-
   def get_all_create_by_object_ap_id(ap_id) do
-    Repo.all(create_by_object_ap_id(ap_id))
+    ap_id
+    |> create_by_object_ap_id()
+    |> Repo.all()
   end
 
   def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
@@ -228,54 +183,17 @@ defmodule Pleroma.Activity do
 
   def get_create_by_object_ap_id(_), do: nil
 
-  def create_by_object_ap_id_with_object(ap_ids) when is_list(ap_ids) do
-    from(
-      activity in Activity,
-      where:
-        fragment(
-          "coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
-          activity.data,
-          activity.data,
-          ^ap_ids
-        ),
-      where: fragment("(?)->>'type' = 'Create'", activity.data),
-      inner_join: o in Object,
-      on:
-        fragment(
-          "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
-          o.data,
-          activity.data,
-          activity.data
-        ),
-      preload: [object: o]
-    )
-  end
-
-  def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
-    from(
-      activity in Activity,
-      where:
-        fragment(
-          "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
-          activity.data,
-          activity.data,
-          ^to_string(ap_id)
-        ),
-      where: fragment("(?)->>'type' = 'Create'", activity.data),
-      inner_join: o in Object,
-      on:
-        fragment(
-          "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
-          o.data,
-          activity.data,
-          activity.data
-        ),
-      preload: [object: o]
-    )
+  @doc """
+  Accepts `ap_id` or list of `ap_id`.
+  Returns a query.
+  """
+  @spec create_by_object_ap_id_with_object(String.t() | [String.t()]) :: Ecto.Queryable.t()
+  def create_by_object_ap_id_with_object(ap_id) do
+    ap_id
+    |> create_by_object_ap_id()
+    |> with_preloaded_object()
   end
 
-  def create_by_object_ap_id_with_object(_), do: nil
-
   def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
     ap_id
     |> create_by_object_ap_id_with_object()
@@ -299,7 +217,8 @@ defmodule Pleroma.Activity do
   def normalize(_), do: nil
 
   def delete_by_ap_id(id) when is_binary(id) do
-    by_object_ap_id(id)
+    id
+    |> Queries.by_object_id()
     |> select([u], u)
     |> Repo.delete_all()
     |> elem(1)
@@ -308,10 +227,19 @@ defmodule Pleroma.Activity do
       %{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id
       _ -> nil
     end)
+    |> purge_web_resp_cache()
   end
 
   def delete_by_ap_id(_), do: nil
 
+  defp purge_web_resp_cache(%Activity{} = activity) do
+    %{path: path} = URI.parse(activity.data["id"])
+    Cachex.del(:web_resp_cache, path)
+    activity
+  end
+
+  defp purge_web_resp_cache(nil), do: nil
+
   for {ap_type, type} <- @mastodon_notification_types do
     def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),
       do: unquote(type)
@@ -334,31 +262,10 @@ defmodule Pleroma.Activity do
   end
 
   def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
-    from(
-      a in Activity,
-      where:
-        fragment(
-          "? ->> 'type' = 'Follow'",
-          a.data
-        ),
-      where:
-        fragment(
-          "? ->> 'state' = 'pending'",
-          a.data
-        ),
-      where:
-        fragment(
-          "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
-          a.data,
-          a.data,
-          ^ap_id
-        )
-    )
-  end
-
-  @spec query_by_actor(actor()) :: Ecto.Query.t()
-  def query_by_actor(actor) do
-    from(a in Activity, where: a.actor == ^actor)
+    ap_id
+    |> Queries.by_object_id()
+    |> Queries.by_type("Follow")
+    |> where([a], fragment("? ->> 'state' = 'pending'", a.data))
   end
 
   def restrict_deactivated_users(query) do
index aa5b29566fdd9744cf2e977602e31e92a230164e..13fa33831f30fa968229deb15d426424936411df 100644 (file)
@@ -13,6 +13,14 @@ defmodule Pleroma.Activity.Queries do
 
   alias Pleroma.Activity
 
+  @spec by_ap_id(query, String.t()) :: query
+  def by_ap_id(query \\ Activity, ap_id) do
+    from(
+      activity in query,
+      where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id))
+    )
+  end
+
   @spec by_actor(query, String.t()) :: query
   def by_actor(query \\ Activity, actor) do
     from(
@@ -21,8 +29,23 @@ defmodule Pleroma.Activity.Queries do
     )
   end
 
-  @spec by_object_id(query, String.t()) :: query
-  def by_object_id(query \\ Activity, object_id) do
+  @spec by_object_id(query, String.t() | [String.t()]) :: query
+  def by_object_id(query \\ Activity, object_id)
+
+  def by_object_id(query, object_ids) when is_list(object_ids) do
+    from(
+      activity in query,
+      where:
+        fragment(
+          "coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
+          activity.data,
+          activity.data,
+          ^object_ids
+        )
+    )
+  end
+
+  def by_object_id(query, object_id) when is_binary(object_id) do
     from(activity in query,
       where:
         fragment(
@@ -41,9 +64,4 @@ defmodule Pleroma.Activity.Queries do
       where: fragment("(?)->>'type' = ?", activity.data, ^activity_type)
     )
   end
-
-  @spec limit(query, pos_integer()) :: query
-  def limit(query \\ Activity, limit) do
-    from(activity in query, limit: ^limit)
-  end
 end
index 483ac1f39e8558ed9ec512c28fa3fa7e9aa57c64..1d46925f80b990ec9bab9dee5546c16c8eaf041c 100644 (file)
@@ -116,7 +116,8 @@ defmodule Pleroma.Application do
       build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
       build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),
       build_cachex("scrubber", limit: 2500),
-      build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500)
+      build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
+      build_cachex("web_resp", limit: 2500)
     ]
   end
 
diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex
new file mode 100644 (file)
index 0000000..aa578ee
--- /dev/null
@@ -0,0 +1,73 @@
+defmodule Pleroma.Docs.Generator do
+  @callback process(keyword()) :: {:ok, String.t()}
+
+  @spec process(module(), keyword()) :: {:ok, String.t()}
+  def process(implementation, descriptions) do
+    implementation.process(descriptions)
+  end
+
+  @spec uploaders_list() :: [module()]
+  def uploaders_list do
+    {:ok, modules} = :application.get_key(:pleroma, :modules)
+
+    Enum.filter(modules, fn module ->
+      name_as_list = Module.split(module)
+
+      List.starts_with?(name_as_list, ["Pleroma", "Uploaders"]) and
+        List.last(name_as_list) != "Uploader"
+    end)
+  end
+
+  @spec filters_list() :: [module()]
+  def filters_list do
+    {:ok, modules} = :application.get_key(:pleroma, :modules)
+
+    Enum.filter(modules, fn module ->
+      name_as_list = Module.split(module)
+
+      List.starts_with?(name_as_list, ["Pleroma", "Upload", "Filter"])
+    end)
+  end
+
+  @spec mrf_list() :: [module()]
+  def mrf_list do
+    {:ok, modules} = :application.get_key(:pleroma, :modules)
+
+    Enum.filter(modules, fn module ->
+      name_as_list = Module.split(module)
+
+      List.starts_with?(name_as_list, ["Pleroma", "Web", "ActivityPub", "MRF"]) and
+        length(name_as_list) > 4
+    end)
+  end
+
+  @spec richmedia_parsers() :: [module()]
+  def richmedia_parsers do
+    {:ok, modules} = :application.get_key(:pleroma, :modules)
+
+    Enum.filter(modules, fn module ->
+      name_as_list = Module.split(module)
+
+      List.starts_with?(name_as_list, ["Pleroma", "Web", "RichMedia", "Parsers"]) and
+        length(name_as_list) == 5
+    end)
+  end
+end
+
+defimpl Jason.Encoder, for: Tuple do
+  def encode(tuple, opts) do
+    Jason.Encode.list(Tuple.to_list(tuple), opts)
+  end
+end
+
+defimpl Jason.Encoder, for: [Regex, Function] do
+  def encode(term, opts) do
+    Jason.Encode.string(inspect(term), opts)
+  end
+end
+
+defimpl String.Chars, for: Regex do
+  def to_string(term) do
+    inspect(term)
+  end
+end
diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex
new file mode 100644 (file)
index 0000000..18ba01d
--- /dev/null
@@ -0,0 +1,20 @@
+defmodule Pleroma.Docs.JSON do
+  @behaviour Pleroma.Docs.Generator
+
+  @spec process(keyword()) :: {:ok, String.t()}
+  def process(descriptions) do
+    config_path = "docs/generate_config.json"
+
+    with {:ok, file} <- File.open(config_path, [:write]),
+         json <- generate_json(descriptions),
+         :ok <- IO.write(file, json),
+         :ok <- File.close(file) do
+      {:ok, config_path}
+    end
+  end
+
+  @spec generate_json([keyword()]) :: String.t()
+  def generate_json(descriptions) do
+    Jason.encode!(descriptions)
+  end
+end
diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex
new file mode 100644 (file)
index 0000000..8386dc2
--- /dev/null
@@ -0,0 +1,78 @@
+defmodule Pleroma.Docs.Markdown do
+  @behaviour Pleroma.Docs.Generator
+
+  @spec process(keyword()) :: {:ok, String.t()}
+  def process(descriptions) do
+    config_path = "docs/generated_config.md"
+    {:ok, file} = File.open(config_path, [:utf8, :write])
+    IO.write(file, "# Generated configuration\n")
+    IO.write(file, "Date of generation: #{Date.utc_today()}\n\n")
+
+    IO.write(
+      file,
+      "This file describe the configuration, it is recommended to edit the relevant `*.secret.exs` file instead of the others founds in the ``config`` directory.\n\n" <>
+        "If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\n\n"
+    )
+
+    for group <- descriptions do
+      if is_nil(group[:key]) do
+        IO.write(file, "## #{inspect(group[:group])}\n")
+      else
+        IO.write(file, "## #{inspect(group[:key])}\n")
+      end
+
+      IO.write(file, "#{group[:description]}\n")
+
+      for child <- group[:children] do
+        print_child_header(file, child)
+
+        print_suggestions(file, child[:suggestions])
+
+        if child[:children] do
+          for subchild <- child[:children] do
+            print_child_header(file, subchild)
+
+            print_suggestions(file, subchild[:suggestions])
+          end
+        end
+      end
+
+      IO.write(file, "\n")
+    end
+
+    :ok = File.close(file)
+    {:ok, config_path}
+  end
+
+  defp print_suggestion(file, suggestion) when is_list(suggestion) do
+    IO.write(file, "  `#{inspect(suggestion)}`\n")
+  end
+
+  defp print_suggestion(file, suggestion) when is_function(suggestion) do
+    IO.write(file, "  `#{inspect(suggestion.())}`\n")
+  end
+
+  defp print_suggestion(file, suggestion, as_list \\ false) do
+    list_mark = if as_list, do: "- ", else: ""
+    IO.write(file, "  #{list_mark}`#{inspect(suggestion)}`\n")
+  end
+
+  defp print_suggestions(_file, nil), do: nil
+
+  defp print_suggestions(file, suggestions) do
+    IO.write(file, "Suggestions:\n")
+
+    if length(suggestions) > 1 do
+      for suggestion <- suggestions do
+        print_suggestion(file, suggestion, true)
+      end
+    else
+      print_suggestion(file, List.first(suggestions))
+    end
+  end
+
+  defp print_child_header(file, child) do
+    IO.write(file, "- `#{inspect(child[:key])}` -`#{inspect(child[:type])}`  \n")
+    IO.write(file, "#{child[:description]}  \n")
+  end
+end
similarity index 98%
rename from lib/healthcheck.ex
rename to lib/pleroma/healthcheck.ex
index f97d14432b27d1858e0df990d71b22b5b10b0519..977b78c268d8ab59f2af297069a6eb44966725bc 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Healthcheck do
   alias Pleroma.Healthcheck
   alias Pleroma.Repo
 
+  @derive Jason.Encoder
   defstruct pool_size: 0,
             active: 0,
             idle: 0,
index d58eb7f7deeb8e9734cd339fe98faa8c992b95ac..5033798aeacbf11fc2f5c81d2c9d4a638c72a710 100644 (file)
@@ -130,14 +130,16 @@ defmodule Pleroma.Object do
   def delete(%Object{data: %{"id" => id}} = object) do
     with {:ok, _obj} = swap_object_with_tombstone(object),
          deleted_activity = Activity.delete_by_ap_id(id),
-         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
+         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
+         {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
       {:ok, object, deleted_activity}
     end
   end
 
   def prune(%Object{data: %{"id" => id}} = object) do
     with {:ok, object} <- Repo.delete(object),
-         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
+         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
+         {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
       {:ok, object}
     end
   end
diff --git a/lib/pleroma/plugs/cache.ex b/lib/pleroma/plugs/cache.ex
new file mode 100644 (file)
index 0000000..a81a861
--- /dev/null
@@ -0,0 +1,122 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.Cache do
+  @moduledoc """
+  Caches successful GET responses.
+
+  To enable the cache add the plug to a router pipeline or controller:
+
+      plug(Pleroma.Plugs.Cache)
+
+  ## Configuration
+
+  To configure the plug you need to pass settings as the second argument to the `plug/2` macro:
+
+      plug(Pleroma.Plugs.Cache, [ttl: nil, query_params: true])
+
+  Available options:
+
+  - `ttl`:  An expiration time (time-to-live). This value should be in milliseconds or `nil` to disable expiration. Defaults to `nil`.
+  - `query_params`: Take URL query string into account (`true`), ignore it (`false`) or limit to specific params only (list). Defaults to `true`.
+
+  Additionally, you can overwrite the TTL inside a controller action by assigning `cache_ttl` to the connection struct:
+
+      def index(conn, _params) do
+        ttl = 60_000 # one minute
+
+        conn
+        |> assign(:cache_ttl, ttl)
+        |> render("index.html")
+      end
+
+  """
+
+  import Phoenix.Controller, only: [current_path: 1, json: 2]
+  import Plug.Conn
+
+  @behaviour Plug
+
+  @defaults %{ttl: nil, query_params: true}
+
+  @impl true
+  def init([]), do: @defaults
+
+  def init(opts) do
+    opts = Map.new(opts)
+    Map.merge(@defaults, opts)
+  end
+
+  @impl true
+  def call(%{method: "GET"} = conn, opts) do
+    key = cache_key(conn, opts)
+
+    case Cachex.get(:web_resp_cache, key) do
+      {:ok, nil} ->
+        cache_resp(conn, opts)
+
+      {:ok, record} ->
+        send_cached(conn, record)
+
+      {atom, message} when atom in [:ignore, :error] ->
+        render_error(conn, message)
+    end
+  end
+
+  def call(conn, _), do: conn
+
+  # full path including query params
+  defp cache_key(conn, %{query_params: true}), do: current_path(conn)
+
+  # request path without query params
+  defp cache_key(conn, %{query_params: false}), do: conn.request_path
+
+  # request path with specific query params
+  defp cache_key(conn, %{query_params: query_params}) when is_list(query_params) do
+    query_string =
+      conn.params
+      |> Map.take(query_params)
+      |> URI.encode_query()
+
+    conn.request_path <> "?" <> query_string
+  end
+
+  defp cache_resp(conn, opts) do
+    register_before_send(conn, fn
+      %{status: 200, resp_body: body} = conn ->
+        ttl = Map.get(conn.assigns, :cache_ttl, opts.ttl)
+        key = cache_key(conn, opts)
+        content_type = content_type(conn)
+        record = {content_type, body}
+
+        Cachex.put(:web_resp_cache, key, record, ttl: ttl)
+
+        put_resp_header(conn, "x-cache", "MISS from Pleroma")
+
+      conn ->
+        conn
+    end)
+  end
+
+  defp content_type(conn) do
+    conn
+    |> Plug.Conn.get_resp_header("content-type")
+    |> hd()
+  end
+
+  defp send_cached(conn, {content_type, body}) do
+    conn
+    |> put_resp_content_type(content_type, nil)
+    |> put_resp_header("x-cache", "HIT from Pleroma")
+    |> send_resp(:ok, body)
+    |> halt()
+  end
+
+  defp render_error(conn, message) do
+    conn
+    |> put_status(:internal_server_error)
+    |> json(%{error: message})
+    |> halt()
+  end
+end
index 3aa245f2aa43f60389fc8a8f43023de09a39268e..64b69e686775493972a87c68fce87f635652375a 100644 (file)
@@ -174,11 +174,25 @@ defmodule Pleroma.User do
     |> Repo.aggregate(:count, :id)
   end
 
+  defp truncate_if_exists(params, key, max_length) do
+    if Map.has_key?(params, key) and is_binary(params[key]) do
+      {value, _chopped} = String.split_at(params[key], max_length)
+      Map.put(params, key, value)
+    else
+      params
+    end
+  end
+
   def remote_user_creation(params) do
     bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
     name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
 
-    params = Map.put(params, :info, params[:info] || %{})
+    params =
+      params
+      |> Map.put(:info, params[:info] || %{})
+      |> truncate_if_exists(:name, name_limit)
+      |> truncate_if_exists(:bio, bio_limit)
+
     info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
 
     changes =
@@ -1219,7 +1233,7 @@ defmodule Pleroma.User do
 
   def delete_user_activities(%User{ap_id: ap_id} = user) do
     ap_id
-    |> Activity.query_by_actor()
+    |> Activity.Queries.by_actor()
     |> RepoStreamer.chunk_stream(50)
     |> Stream.each(fn activities ->
       Enum.each(activities, &delete_activity(&1))
@@ -1624,4 +1638,13 @@ defmodule Pleroma.User do
   def is_internal_user?(%User{nickname: nil}), do: true
   def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
   def is_internal_user?(_), do: false
+
+  def change_email(user, email) do
+    user
+    |> cast(%{email: email}, [:email])
+    |> validate_required([:email])
+    |> unique_constraint(:email)
+    |> validate_format(:email, @email_regex)
+    |> update_and_set_cache()
+  end
 end
index 779bfbc188a911c324d4ad006a616565c3ba2ad1..151e025de8bf98f1e9da6af1844a505a74cb3019 100644 (file)
@@ -242,6 +242,13 @@ defmodule Pleroma.User.Info do
   end
 
   def remote_user_creation(info, params) do
+    params =
+      if Map.has_key?(params, :fields) do
+        Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
+      else
+        params
+      end
+
     info
     |> cast(params, [
       :ap_enabled,
@@ -326,6 +333,16 @@ defmodule Pleroma.User.Info do
 
   defp valid_field?(_), do: false
 
+  defp truncate_field(%{"name" => name, "value" => value}) do
+    {name, _chopped} =
+      String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
+
+    {value, _chopped} =
+      String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
+
+    %{"name" => name, "value" => value}
+  end
+
   @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
   def confirmation_changeset(info, opts) do
     need_confirmation? = Keyword.get(opts, :need_confirmation)
index 7047b8254f1d84143537bef37ad915ce321a0752..4dc01a85bee8682629bd0c10a45cc1788bcb206d 100644 (file)
@@ -23,6 +23,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   action_fallback(:errors)
 
+  plug(Pleroma.Plugs.Cache, [query_params: false] when action in [:activity, :object])
+
   plug(
     Pleroma.Plugs.OAuthScopesPlug,
     %{scopes: ["read:accounts"]} when action in [:followers, :following]
@@ -58,8 +60,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
          %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
          {_, true} <- {:public?, Visibility.is_public?(object)} do
       conn
+      |> set_cache_ttl_for(object)
       |> put_resp_content_type("application/activity+json")
-      |> json(ObjectView.render("object.json", %{object: object}))
+      |> put_view(ObjectView)
+      |> render("object.json", object: object)
     else
       {:public?, false} ->
         {:error, :not_found}
@@ -101,14 +105,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
          %Activity{} = activity <- Activity.normalize(ap_id),
          {_, true} <- {:public?, Visibility.is_public?(activity)} do
       conn
+      |> set_cache_ttl_for(activity)
       |> put_resp_content_type("application/activity+json")
-      |> json(ObjectView.render("object.json", %{object: activity}))
+      |> put_view(ObjectView)
+      |> render("object.json", object: activity)
     else
-      {:public?, false} ->
-        {:error, :not_found}
+      {:public?, false} -> {:error, :not_found}
+      nil -> {:error, :not_found}
     end
   end
 
+  defp set_cache_ttl_for(conn, %Activity{object: object}) do
+    set_cache_ttl_for(conn, object)
+  end
+
+  defp set_cache_ttl_for(conn, entity) do
+    ttl =
+      case entity do
+        %Object{data: %{"type" => "Question"}} ->
+          Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
+
+        %Object{} ->
+          Pleroma.Config.get([:web_cache_ttl, :activity_pub])
+
+        _ ->
+          nil
+      end
+
+    assign(conn, :cache_ttl, ttl)
+  end
+
   # GET /relay/following
   def following(%{assigns: %{relay: true}} = conn, _params) do
     conn
@@ -256,22 +282,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def whoami(_conn, _params), do: {:error, :not_found}
 
-  def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
-    if nickname == user.nickname do
-      conn
-      |> put_resp_content_type("application/activity+json")
-      |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
-    else
-      err =
-        dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
-          nickname: nickname,
-          as_nickname: user.nickname
-        )
+  def read_inbox(
+        %{assigns: %{user: %{nickname: nickname} = user}} = conn,
+        %{"nickname" => nickname} = params
+      ) do
+    conn
+    |> put_resp_content_type("application/activity+json")
+    |> put_view(UserView)
+    |> render("inbox.json", user: user, max_id: params["max_id"])
+  end
 
-      conn
-      |> put_status(:forbidden)
-      |> json(err)
-    end
+  def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
+    err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
+
+    conn
+    |> put_status(:forbidden)
+    |> json(err)
+  end
+
+  def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
+        "nickname" => nickname
+      }) do
+    err =
+      dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
+        nickname: nickname,
+        as_nickname: as_nickname
+      )
+
+    conn
+    |> put_status(:forbidden)
+    |> json(err)
   end
 
   def handle_user_activity(user, %{"type" => "Create"} = params) do
index 468961bd0e204dbe5b294d6272ba26a142d82897..350b83abb3d245f5a08be850af46db9ca55188fd 100644 (file)
@@ -185,12 +185,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
             |> Map.put("context", replied_object.data["context"] || object["conversation"])
           else
             e ->
-              Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+              Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
               object
           end
 
         e ->
-          Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+          Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
           object
       end
     else
index c9c0c376335f5bc43b627f8a35b4589faac7d428..47917f5d3190a57b19fde77831aa69cccdb063df 100644 (file)
@@ -85,15 +85,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   defp extract_list(_), do: []
 
   def maybe_splice_recipient(ap_id, params) do
-    need_splice =
+    need_splice? =
       !recipient_in_collection(ap_id, params["to"]) &&
         !recipient_in_collection(ap_id, params["cc"])
 
-    cc_list = extract_list(params["cc"])
-
-    if need_splice do
-      params
-      |> Map.put("cc", [ap_id | cc_list])
+    if need_splice? do
+      cc_list = extract_list(params["cc"])
+      Map.put(params, "cc", [ap_id | cc_list])
     else
       params
     end
@@ -139,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       "object" => object
     }
 
-    Notification.get_notified_from_activity(%Activity{data: fake_create_activity}, false)
+    get_notified_from_object(fake_create_activity)
   end
 
   def get_notified_from_object(object) do
@@ -188,9 +186,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   Adds an id and a published data if they aren't there,
   also adds it to an included object
   """
-  def lazy_put_activity_defaults(map, fake \\ false) do
+  def lazy_put_activity_defaults(map, fake? \\ false) do
     map =
-      unless fake do
+      if not fake? do
         %{data: %{"id" => context}, id: context_id} = create_context(map["context"])
 
         map
@@ -207,7 +205,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
       end
 
     if is_map(map["object"]) do
-      object = lazy_put_object_defaults(map["object"], map, fake)
+      object = lazy_put_object_defaults(map["object"], map, fake?)
       %{map | "object" => object}
     else
       map
@@ -217,9 +215,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   @doc """
   Adds an id and published date if they aren't there.
   """
-  def lazy_put_object_defaults(map, activity \\ %{}, fake)
+  def lazy_put_object_defaults(map, activity \\ %{}, fake?)
 
-  def lazy_put_object_defaults(map, activity, true = _fake) do
+  def lazy_put_object_defaults(map, activity, true = _fake?) do
     map
     |> Map.put_new_lazy("published", &make_date/0)
     |> Map.put_new("id", "pleroma:fake_object_id")
@@ -228,7 +226,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     |> Map.put_new("context_id", activity["context_id"])
   end
 
-  def lazy_put_object_defaults(map, activity, _fake) do
+  def lazy_put_object_defaults(map, activity, _fake?) do
     map
     |> Map.put_new_lazy("id", &generate_object_id/0)
     |> Map.put_new_lazy("published", &make_date/0)
@@ -242,9 +240,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   def insert_full_object(%{"object" => %{"type" => type} = object_data} = map)
       when is_map(object_data) and type in @supported_object_types do
     with {:ok, object} <- Object.create(object_data) do
-      map =
-        map
-        |> Map.put("object", object.data["id"])
+      map = Map.put(map, "object", object.data["id"])
 
       {:ok, map, object}
     end
@@ -263,7 +259,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     |> Activity.Queries.by_actor()
     |> Activity.Queries.by_object_id(id)
     |> Activity.Queries.by_type("Like")
-    |> Activity.Queries.limit(1)
+    |> limit(1)
     |> Repo.one()
   end
 
@@ -380,12 +376,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
         %Activity{data: %{"actor" => actor, "object" => object}} = activity,
         state
       ) do
-    with new_data <-
-           activity.data
-           |> Map.put("state", state),
-         changeset <- Changeset.change(activity, data: new_data),
-         {:ok, activity} <- Repo.update(changeset),
-         _ <- User.set_follow_state_cache(actor, object, state) do
+    new_data = Map.put(activity.data, "state", state)
+    changeset = Changeset.change(activity, data: new_data)
+
+    with {:ok, activity} <- Repo.update(changeset) do
+      User.set_follow_state_cache(actor, object, state)
       {:ok, activity}
     end
   end
@@ -410,28 +405,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   end
 
   def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
-    query =
-      from(
-        activity in Activity,
-        where:
-          fragment(
-            "? ->> 'type' = 'Follow'",
-            activity.data
-          ),
-        where: activity.actor == ^follower_id,
-        # this is to use the index
-        where:
-          fragment(
-            "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
-            activity.data,
-            activity.data,
-            ^followed_id
-          ),
-        order_by: [fragment("? desc nulls last", activity.id)],
-        limit: 1
-      )
-
-    Repo.one(query)
+    "Follow"
+    |> Activity.Queries.by_type()
+    |> where(actor: ^follower_id)
+    # this is to use the index
+    |> Activity.Queries.by_object_id(followed_id)
+    |> order_by([activity], fragment("? desc nulls last", activity.id))
+    |> limit(1)
+    |> Repo.one()
   end
 
   #### Announce-related helpers
@@ -439,23 +420,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   @doc """
   Retruns an existing announce activity if the notice has already been announced
   """
-  def get_existing_announce(actor, %{data: %{"id" => id}}) do
-    query =
-      from(
-        activity in Activity,
-        where: activity.actor == ^actor,
-        # this is to use the index
-        where:
-          fragment(
-            "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
-            activity.data,
-            activity.data,
-            ^id
-          ),
-        where: fragment("(?)->>'type' = 'Announce'", activity.data)
-      )
-
-    Repo.one(query)
+  def get_existing_announce(actor, %{data: %{"id" => ap_id}}) do
+    "Announce"
+    |> Activity.Queries.by_type()
+    |> where(actor: ^actor)
+    # this is to use the index
+    |> Activity.Queries.by_object_id(ap_id)
+    |> Repo.one()
   end
 
   @doc """
@@ -538,11 +509,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
         object
       ) do
     announcements =
-      if is_list(object.data["announcements"]), do: object.data["announcements"], else: []
+      if is_list(object.data["announcements"]) do
+        Enum.uniq([actor | object.data["announcements"]])
+      else
+        [actor]
+      end
 
-    with announcements <- [actor | announcements] |> Enum.uniq() do
-      update_element_in_object("announcement", announcements, object)
-    end
+    update_element_in_object("announcement", announcements, object)
   end
 
   def add_announce_to_object(_, object), do: {:ok, object}
@@ -570,28 +543,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
 
   #### Block-related helpers
   def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do
-    query =
-      from(
-        activity in Activity,
-        where:
-          fragment(
-            "? ->> 'type' = 'Block'",
-            activity.data
-          ),
-        where: activity.actor == ^blocker_id,
-        # this is to use the index
-        where:
-          fragment(
-            "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
-            activity.data,
-            activity.data,
-            ^blocked_id
-          ),
-        order_by: [fragment("? desc nulls last", activity.id)],
-        limit: 1
-      )
-
-    Repo.one(query)
+    "Block"
+    |> Activity.Queries.by_type()
+    |> where(actor: ^blocker_id)
+    # this is to use the index
+    |> Activity.Queries.by_object_id(blocked_id)
+    |> order_by([activity], fragment("? desc nulls last", activity.id))
+    |> limit(1)
+    |> Repo.one()
   end
 
   def make_block_data(blocker, blocked, activity_id) do
@@ -695,11 +654,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   #### Report-related helpers
 
   def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
-    with new_data <- Map.put(activity.data, "state", state),
-         changeset <- Changeset.change(activity, data: new_data),
-         {:ok, activity} <- Repo.update(changeset) do
-      {:ok, activity}
-    end
+    new_data = Map.put(activity.data, "state", state)
+
+    activity
+    |> Changeset.change(data: new_data)
+    |> Repo.update()
   end
 
   def update_report_state(_, _), do: {:error, "Unsupported state"}
@@ -766,21 +725,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   end
 
   def get_existing_votes(actor, %{data: %{"id" => id}}) do
-    query =
-      from(
-        [activity, object: object] in Activity.with_preloaded_object(Activity),
-        where: fragment("(?)->>'type' = 'Create'", activity.data),
-        where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
-        where:
-          fragment(
-            "(?)->>'inReplyTo' = ?",
-            object.data,
-            ^to_string(id)
-          ),
-        where: fragment("(?)->>'type' = 'Answer'", object.data)
-      )
-
-    Repo.all(query)
+    actor
+    |> Activity.Queries.by_actor()
+    |> Activity.Queries.by_type("Create")
+    |> Activity.with_preloaded_object()
+    |> where([a, object: o], fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(id)))
+    |> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data))
+    |> Repo.all()
   end
 
   defp maybe_put(map, _key, nil), do: map
index a10cc779bbf4918e1188fd822b132f6f58dbd256..1917a55805b9a728f0048ccf5150f54b498746be 100644 (file)
@@ -90,6 +90,8 @@ defmodule Pleroma.Web.AdminAPI.Config do
     for v <- entity, into: [], do: do_convert(v)
   end
 
+  defp do_convert(%Regex{} = entity), do: inspect(entity)
+
   defp do_convert(entity) when is_map(entity) do
     for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)}
   end
@@ -122,7 +124,7 @@ defmodule Pleroma.Web.AdminAPI.Config do
 
   def transform(entity), do: :erlang.term_to_binary(entity)
 
-  defp do_transform(%Regex{} = entity) when is_map(entity), do: entity
+  defp do_transform(%Regex{} = entity), do: entity
 
   defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
     {dispatch_settings, []} = do_eval(entity)
@@ -154,8 +156,15 @@ defmodule Pleroma.Web.AdminAPI.Config do
   defp do_transform(entity), do: entity
 
   defp do_transform_string("~r/" <> pattern) do
-    pattern = String.trim_trailing(pattern, "/")
-    ~r/#{pattern}/
+    modificator = String.split(pattern, "/") |> List.last()
+    pattern = String.trim_trailing(pattern, "/" <> modificator)
+
+    case modificator do
+      "" -> ~r/#{pattern}/
+      "i" -> ~r/#{pattern}/i
+      "u" -> ~r/#{pattern}/u
+      "s" -> ~r/#{pattern}/s
+    end
   end
 
   defp do_transform_string(":" <> atom), do: String.to_atom(atom)
index eeac9f503ccf09d75fc216b9ee2c9beab7622b15..b53a01955d3cf0c1b60a7072385ec3a8b4c0b3f9 100644 (file)
@@ -34,79 +34,38 @@ defmodule Pleroma.Web.ControllerHelper do
 
   defp param_to_integer(_, default), do: default
 
-  def add_link_headers(
-        conn,
-        method,
-        activities,
-        param \\ nil,
-        params \\ %{},
-        func3 \\ nil,
-        func4 \\ nil
-      ) do
-    params =
-      conn.params
-      |> Map.drop(["since_id", "max_id", "min_id"])
-      |> Map.merge(params)
+  def add_link_headers(conn, activities, extra_params \\ %{}) do
+    case List.last(activities) do
+      %{id: max_id} ->
+        params =
+          conn.params
+          |> Map.drop(Map.keys(conn.path_params))
+          |> Map.drop(["since_id", "max_id", "min_id"])
+          |> Map.merge(extra_params)
 
-    last = List.last(activities)
+        limit =
+          params
+          |> Map.get("limit", "20")
+          |> String.to_integer()
 
-    func3 = func3 || (&mastodon_api_url/3)
-    func4 = func4 || (&mastodon_api_url/4)
+        min_id =
+          if length(activities) <= limit do
+            activities
+            |> List.first()
+            |> Map.get(:id)
+          else
+            activities
+            |> Enum.at(limit * -1)
+            |> Map.get(:id)
+          end
 
-    if last do
-      max_id = last.id
+        next_url = current_url(conn, Map.merge(params, %{max_id: max_id}))
+        prev_url = current_url(conn, Map.merge(params, %{min_id: min_id}))
 
-      limit =
-        params
-        |> Map.get("limit", "20")
-        |> String.to_integer()
+        put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
 
-      min_id =
-        if length(activities) <= limit do
-          activities
-          |> List.first()
-          |> Map.get(:id)
-        else
-          activities
-          |> Enum.at(limit * -1)
-          |> Map.get(:id)
-        end
-
-      {next_url, prev_url} =
-        if param do
-          {
-            func4.(
-              Pleroma.Web.Endpoint,
-              method,
-              param,
-              Map.merge(params, %{max_id: max_id})
-            ),
-            func4.(
-              Pleroma.Web.Endpoint,
-              method,
-              param,
-              Map.merge(params, %{min_id: min_id})
-            )
-          }
-        else
-          {
-            func3.(
-              Pleroma.Web.Endpoint,
-              method,
-              Map.merge(params, %{max_id: max_id})
-            ),
-            func3.(
-              Pleroma.Web.Endpoint,
-              method,
-              Map.merge(params, %{min_id: min_id})
-            )
-          }
-        end
-
-      conn
-      |> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
-    else
-      conn
+      _ ->
+        conn
     end
   end
 end
index 704664f5fa65fe8727a3ed6dd5b04c64309cf626..c5632bb5e9e08484ee937a6ea022326b66395e44 100644 (file)
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   use Pleroma.Web, :controller
 
   import Pleroma.Web.ControllerHelper,
-    only: [json_response: 3, add_link_headers: 5, add_link_headers: 4, add_link_headers: 3]
+    only: [json_response: 3, add_link_headers: 2, add_link_headers: 3]
 
   alias Ecto.Changeset
   alias Pleroma.Activity
@@ -101,7 +101,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   plug(
     OAuthScopesPlug,
     %{@unauthenticated_access | scopes: ["read:statuses"]}
-    when action in [:user_statuses, :get_status, :get_context, :status_card, :get_poll]
+    when action in [
+           :user_statuses,
+           :get_statuses,
+           :get_status,
+           :get_context,
+           :status_card,
+           :get_poll
+         ]
   )
 
   plug(
@@ -526,7 +533,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> Enum.reverse()
 
     conn
-    |> add_link_headers(:home_timeline, activities)
+    |> add_link_headers(activities)
     |> put_view(StatusView)
     |> render("index.json", %{activities: activities, for: user, as: :activity})
   end
@@ -545,7 +552,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> Enum.reverse()
 
     conn
-    |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
+    |> add_link_headers(activities, %{"local" => local_only})
     |> put_view(StatusView)
     |> render("index.json", %{activities: activities, for: user, as: :activity})
   end
@@ -559,7 +566,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       activities = ActivityPub.fetch_user_activities(user, reading_user, params)
 
       conn
-      |> add_link_headers(:user_statuses, activities, params["id"])
+      |> add_link_headers(activities)
       |> put_view(StatusView)
       |> render("index.json", %{
         activities: activities,
@@ -583,11 +590,25 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> Pagination.fetch_paginated(params)
 
     conn
-    |> add_link_headers(:dm_timeline, activities)
+    |> add_link_headers(activities)
     |> put_view(StatusView)
     |> render("index.json", %{activities: activities, for: user, as: :activity})
   end
 
+  def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
+    limit = 100
+
+    activities =
+      ids
+      |> Enum.take(limit)
+      |> Activity.all_by_ids_with_object()
+      |> Enum.filter(&Visibility.visible_for_user?(&1, user))
+
+    conn
+    |> put_view(StatusView)
+    |> render("index.json", activities: activities, for: user, as: :activity)
+  end
+
   def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     with %Activity{} = activity <- Activity.get_by_id_with_object(id),
          true <- Visibility.visible_for_user?(activity, user) do
@@ -684,7 +705,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
     with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
       conn
-      |> add_link_headers(:scheduled_statuses, scheduled_activities)
+      |> add_link_headers(scheduled_activities)
       |> put_view(ScheduledActivityView)
       |> render("index.json", %{scheduled_activities: scheduled_activities})
     end
@@ -867,7 +888,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     notifications = MastodonAPI.get_notifications(user, params)
 
     conn
-    |> add_link_headers(:notifications, notifications)
+    |> add_link_headers(notifications)
     |> put_view(NotificationView)
     |> render("index.json", %{notifications: notifications, for: user})
   end
@@ -989,6 +1010,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
 
   def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+         {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
          %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
       q = from(u in User, where: u.ap_id in ^likes)
 
@@ -1000,12 +1022,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> put_view(AccountView)
       |> render("accounts.json", %{for: user, users: users, as: :user})
     else
+      {:visible, false} -> {:error, :not_found}
       _ -> json(conn, [])
     end
   end
 
   def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+         {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
          %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
       q = from(u in User, where: u.ap_id in ^announces)
 
@@ -1017,6 +1041,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> put_view(AccountView)
       |> render("accounts.json", %{for: user, users: users, as: :user})
     else
+      {:visible, false} -> {:error, :not_found}
       _ -> json(conn, [])
     end
   end
@@ -1055,7 +1080,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> Enum.reverse()
 
     conn
-    |> add_link_headers(:hashtag_timeline, activities, params["tag"], %{"local" => local_only})
+    |> add_link_headers(activities, %{"local" => local_only})
     |> put_view(StatusView)
     |> render("index.json", %{activities: activities, for: user, as: :activity})
   end
@@ -1071,7 +1096,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         end
 
       conn
-      |> add_link_headers(:followers, followers, user)
+      |> add_link_headers(followers)
       |> put_view(AccountView)
       |> render("accounts.json", %{for: for_user, users: followers, as: :user})
     end
@@ -1088,7 +1113,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         end
 
       conn
-      |> add_link_headers(:following, followers, user)
+      |> add_link_headers(followers)
       |> put_view(AccountView)
       |> render("accounts.json", %{for: for_user, users: followers, as: :user})
     end
@@ -1313,7 +1338,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> Enum.reverse()
 
     conn
-    |> add_link_headers(:favourites, activities)
+    |> add_link_headers(activities)
     |> put_view(StatusView)
     |> render("index.json", %{activities: activities, for: user, as: :activity})
   end
@@ -1340,7 +1365,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         |> Enum.reverse()
 
       conn
-      |> add_link_headers(:favourites, activities)
+      |> add_link_headers(activities)
       |> put_view(StatusView)
       |> render("index.json", %{activities: activities, for: for_user, as: :activity})
     else
@@ -1361,7 +1386,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
 
     conn
-    |> add_link_headers(:bookmarks, bookmarks)
+    |> add_link_headers(bookmarks)
     |> put_view(StatusView)
     |> render("index.json", %{activities: activities, for: user, as: :activity})
   end
@@ -1803,7 +1828,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       end)
 
     conn
-    |> add_link_headers(:conversations, participations)
+    |> add_link_headers(participations)
     |> json(conversations)
   end
 
index e71083b918ffca8d2c21b5dcc81565f4605376e9..ef796cddd2b0645d46293a2b1f4ea70537bb89fb 100644 (file)
@@ -73,14 +73,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
   def render("index.json", opts) do
     replied_to_activities = get_replied_to_activities(opts.activities)
-    parallel = unless is_nil(opts[:parallel]), do: opts[:parallel], else: true
 
     opts.activities
     |> safe_render_many(
       StatusView,
       "status.json",
-      Map.put(opts, :replied_to_activities, replied_to_activities),
-      parallel
+      Map.put(opts, :replied_to_activities, replied_to_activities)
     )
   end
 
@@ -499,7 +497,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     object_tags = for tag when is_binary(tag) <- object_tags, do: tag
 
     Enum.reduce(object_tags, [], fn tag, tags ->
-      tags ++ [%{name: tag, url: "/tag/#{tag}"}]
+      tags ++ [%{name: tag, url: "/tag/#{URI.encode(tag)}"}]
     end)
   end
 
index 17c568a9d12224b8a621c64f2be405626ef45b1e..f3dc4616cd7a05b8bd193ff4b28639c7380b5d66 100644 (file)
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
   use Pleroma.Web, :controller
 
-  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7]
+  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
 
   alias Pleroma.Conversation.Participation
   alias Pleroma.Notification
@@ -40,31 +40,22 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
         %{assigns: %{user: user}} = conn,
         %{"id" => participation_id} = params
       ) do
-    params =
-      params
-      |> Map.put("blocking_user", user)
-      |> Map.put("muting_user", user)
-      |> Map.put("user", user)
-
-    participation =
-      participation_id
-      |> Participation.get(preload: [:conversation])
+    participation = Participation.get(participation_id, preload: [:conversation])
 
     if user.id == participation.user_id do
+      params =
+        params
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("user", user)
+
       activities =
         participation.conversation.ap_id
         |> ActivityPub.fetch_activities_for_context(params)
         |> Enum.reverse()
 
       conn
-      |> add_link_headers(
-        :conversation_statuses,
-        activities,
-        participation_id,
-        params,
-        nil,
-        &pleroma_api_url/4
-      )
+      |> add_link_headers(activities)
       |> put_view(StatusView)
       |> render("index.json", %{activities: activities, for: user, as: :activity})
     end
index 593da01fd555666fa28d0c7aaa1925ba4cd57a47..0b41eb2e0b68bc68f62e8fd69c75c6bf8186efdc 100644 (file)
@@ -192,6 +192,7 @@ defmodule Pleroma.Web.Router do
   scope "/api/pleroma", Pleroma.Web.TwitterAPI do
     pipe_through(:authenticated_api)
 
+    post("/change_email", UtilController, :change_email)
     post("/change_password", UtilController, :change_password)
     post("/delete_account", UtilController, :delete_account)
     put("/notification_settings", UtilController, :update_notificaton_settings)
@@ -360,48 +361,46 @@ defmodule Pleroma.Web.Router do
   scope "/api/v1", Pleroma.Web.MastodonAPI do
     pipe_through(:api)
 
-    post("/accounts", MastodonAPIController, :account_register)
-
     get("/instance", MastodonAPIController, :masto_instance)
     get("/instance/peers", MastodonAPIController, :peers)
+
     post("/apps", MastodonAPIController, :create_app)
     get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials)
-    get("/custom_emojis", MastodonAPIController, :custom_emojis)
-
-    get("/statuses/:id/card", MastodonAPIController, :status_card)
 
-    get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by)
-    get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by)
+    get("/custom_emojis", MastodonAPIController, :custom_emojis)
 
     get("/trends", MastodonAPIController, :empty_array)
 
     get("/accounts/search", SearchController, :account_search)
 
-    post(
-      "/pleroma/accounts/confirmation_resend",
-      MastodonAPIController,
-      :account_confirmation_resend
-    )
-
     get("/timelines/public", MastodonAPIController, :public_timeline)
     get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
-
-    get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
-
-    get("/search", SearchController, :search)
+    get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
 
     get("/polls/:id", MastodonAPIController, :get_poll)
 
+    post("/accounts", MastodonAPIController, :account_register)
+    get("/accounts/:id", MastodonAPIController, :user)
     get("/accounts/:id/followers", MastodonAPIController, :followers)
     get("/accounts/:id/following", MastodonAPIController, :following)
+    get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
 
-    get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
-
-    get("/accounts/:id", MastodonAPIController, :user)
+    get("/search", SearchController, :search)
 
-    get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
+    get("/statuses", MastodonAPIController, :get_statuses)
     get("/statuses/:id", MastodonAPIController, :get_status)
     get("/statuses/:id/context", MastodonAPIController, :get_context)
+    get("/statuses/:id/card", MastodonAPIController, :status_card)
+    get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by)
+    get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by)
+
+    get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
+
+    post(
+      "/pleroma/accounts/confirmation_resend",
+      MastodonAPIController,
+      :account_confirmation_resend
+    )
   end
 
   scope "/api/v2", Pleroma.Web.MastodonAPI do
index 82ed0c287c952c5b84e3a50b5d086c4d0b34f540..644a0ae6b5964c3aa12ccb797520bbdc4e21f562 100644 (file)
@@ -31,6 +31,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
     OAuthScopesPlug,
     %{scopes: ["write:accounts"]}
     when action in [
+           :change_email,
            :change_password,
            :delete_account,
            :update_notificaton_settings,
@@ -334,6 +335,25 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
     end
   end
 
+  def change_email(%{assigns: %{user: user}} = conn, params) do
+    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
+      {:ok, user} ->
+        with {:ok, _user} <- User.change_email(user, params["email"]) do
+          json(conn, %{status: "success"})
+        else
+          {:error, changeset} ->
+            {_, {error, _}} = Enum.at(changeset.errors, 0)
+            json(conn, %{error: "Email #{error}."})
+
+          _ ->
+            json(conn, %{error: "Unable to change email."})
+        end
+
+      {:error, msg} ->
+        json(conn, %{error: msg})
+    end
+  end
+
   def delete_account(%{assigns: %{user: user}} = conn, params) do
     case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
       {:ok, user} ->
index bfb6c728784055ab925799f1ef7f84de7aa0ee76..6873465544b19fab1c9d31f88689708a52cbf87f 100644 (file)
@@ -66,23 +66,9 @@ defmodule Pleroma.Web do
       end
 
       @doc """
-      Same as `render_many/4` but wrapped in rescue block and parallelized (unless disabled by passing false as a fifth argument).
+      Same as `render_many/4` but wrapped in rescue block.
       """
-      def safe_render_many(collection, view, template, assigns \\ %{}, parallel \\ true)
-
-      def safe_render_many(collection, view, template, assigns, true) do
-        Enum.map(collection, fn resource ->
-          Task.async(fn ->
-            as = Map.get(assigns, :as) || view.__resource__
-            assigns = Map.put(assigns, as, resource)
-            safe_render(view, template, assigns)
-          end)
-        end)
-        |> Enum.map(&Task.await(&1, :infinity))
-        |> Enum.filter(& &1)
-      end
-
-      def safe_render_many(collection, view, template, assigns, false) do
+      def safe_render_many(collection, view, template, assigns \\ %{}) do
         Enum.map(collection, fn resource ->
           as = Map.get(assigns, :as) || view.__resource__
           assigns = Map.put(assigns, as, resource)
diff --git a/mix.exs b/mix.exs
index 3170d6f2d7ca8413660297439c3fbc092569a458..dfa53035886ac410227665d82a1af1fa38be4048 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -125,7 +125,7 @@ defmodule Pleroma.Mixfile do
       {:crypt,
        git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
       {:cors_plug, "~> 1.5"},
-      {:ex_doc, "~> 0.20.2", only: :dev, runtime: false},
+      {:ex_doc, "~> 0.21", only: :dev, runtime: false},
       {:web_push_encryption, "~> 0.2.1"},
       {:swoosh, "~> 0.23.2"},
       {:phoenix_swoosh, "~> 0.2"},
index 2639e96e95ad118bc30360793cc7fad4eb939f1c..5762dae4f34c3e8d3cd435b26355b0f506b18f81 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -20,7 +20,7 @@
   "db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
   "decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
   "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
-  "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
+  "earmark": {:hex, :earmark, "1.3.6", "ce1d0675e10a5bb46b007549362bd3f5f08908843957687d8484fe7f37466b19", [:mix], [], "hexpm"},
   "ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
   "ecto_sql": {:hex, :ecto_sql, "3.1.3", "2c536139190492d9de33c5fefac7323c5eaaa82e1b9bf93482a14649042f7cd9", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
   "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
@@ -29,7 +29,7 @@
   "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
   "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
   "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm"},
-  "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
+  "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
   "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
   "ex_rated": {:hex, :ex_rated, "1.3.3", "30ecbdabe91f7eaa9d37fa4e81c85ba420f371babeb9d1910adbcd79ec798d27", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"},
   "ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]},
@@ -46,8 +46,8 @@
   "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
   "joken": {:hex, :joken, "2.0.1", "ec9ab31bf660f343380da033b3316855197c8d4c6ef597fa3fcb451b326beb14", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"},
   "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
-  "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
-  "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
+  "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
+  "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
   "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
   "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
   "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
@@ -56,7 +56,7 @@
   "mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
   "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
   "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
-  "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
+  "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
   "phoenix": {:hex, :phoenix, "1.4.9", "746d098e10741c334d88143d3c94cab1756435f94387a63441792e66ec0ee974", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
index 785c4b3cf2bff8897fe655f960a7b900ac8f8b45..275cab81e6123a839ed044e5a5cc2642f2360040 100644 (file)
@@ -173,4 +173,51 @@ defmodule Pleroma.ActivityTest do
     |> where([a], a.activity_id == ^activity.id)
     |> Repo.one!()
   end
+
+  test "all_by_ids_with_object/1" do
+    %{id: id1} = insert(:note_activity)
+    %{id: id2} = insert(:note_activity)
+
+    activities =
+      [id1, id2]
+      |> Activity.all_by_ids_with_object()
+      |> Enum.sort(&(&1.id < &2.id))
+
+    assert [%{id: ^id1, object: %Object{}}, %{id: ^id2, object: %Object{}}] = activities
+  end
+
+  test "get_by_id_with_object/1" do
+    %{id: id} = insert(:note_activity)
+
+    assert %Activity{id: ^id, object: %Object{}} = Activity.get_by_id_with_object(id)
+  end
+
+  test "get_by_ap_id_with_object/1" do
+    %{data: %{"id" => ap_id}} = insert(:note_activity)
+
+    assert %Activity{data: %{"id" => ^ap_id}, object: %Object{}} =
+             Activity.get_by_ap_id_with_object(ap_id)
+  end
+
+  test "get_by_id/1" do
+    %{id: id} = insert(:note_activity)
+
+    assert %Activity{id: ^id} = Activity.get_by_id(id)
+  end
+
+  test "all_by_actor_and_id/2" do
+    user = insert(:user)
+
+    {:ok, %{id: id1}} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofe"})
+    {:ok, %{id: id2}} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofefe"})
+
+    assert [] == Activity.all_by_actor_and_id(user, [])
+
+    activities =
+      user.ap_id
+      |> Activity.all_by_actor_and_id([id1, id2])
+      |> Enum.sort(&(&1.id < &2.id))
+
+    assert [%Activity{id: ^id1}, %Activity{id: ^id2}] = activities
+  end
 end
index 3975cdcd694b3a39bc14ebce532b89faaa021b55..63bf73412f9bf1b03c299ca1e0fc48c7c6ca31d3 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Integration.MastodonWebsocketTest do
   use Pleroma.DataCase
 
+  import ExUnit.CaptureLog
   import Pleroma.Factory
 
   alias Pleroma.Integration.WebsocketClient
@@ -39,13 +40,17 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
   end
 
   test "refuses invalid requests" do
-    assert {:error, {400, _}} = start_socket()
-    assert {:error, {404, _}} = start_socket("?stream=ncjdk")
+    capture_log(fn ->
+      assert {:error, {400, _}} = start_socket()
+      assert {:error, {404, _}} = start_socket("?stream=ncjdk")
+    end)
   end
 
   test "requires authentication and a valid token for protected streams" do
-    assert {:error, {403, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa")
-    assert {:error, {403, _}} = start_socket("?stream=user")
+    capture_log(fn ->
+      assert {:error, {403, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa")
+      assert {:error, {403, _}} = start_socket("?stream=user")
+    end)
   end
 
   test "allows public streams without authentication" do
@@ -100,19 +105,27 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
 
     test "accepts the 'user' stream", %{token: token} = _state do
       assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
-      assert {:error, {403, "Forbidden"}} = start_socket("?stream=user")
+
+      assert capture_log(fn ->
+               assert {:error, {403, "Forbidden"}} = start_socket("?stream=user")
+             end) =~ ":badarg"
     end
 
     test "accepts the 'user:notification' stream", %{token: token} = _state do
       assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
-      assert {:error, {403, "Forbidden"}} = start_socket("?stream=user:notification")
+
+      assert capture_log(fn ->
+               assert {:error, {403, "Forbidden"}} = start_socket("?stream=user:notification")
+             end) =~ ":badarg"
     end
 
     test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do
       assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}])
 
-      assert {:error, {403, "Forbidden"}} =
-               start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
+      assert capture_log(fn ->
+               assert {:error, {403, "Forbidden"}} =
+                        start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
+             end) =~ ":badarg"
     end
   end
 end
index d138ee0912e203a21169418a3375187d96555f2b..ba96aeea4d67ba793e5181fc9b1844bdf7daaa8e 100644 (file)
@@ -53,9 +53,12 @@ defmodule Pleroma.ObjectTest do
 
       assert object == cached_object
 
+      Cachex.put(:web_resp_cache, URI.parse(object.data["id"]).path, "cofe")
+
       Object.delete(cached_object)
 
       {:ok, nil} = Cachex.get(:object_cache, "object:#{object.data["id"]}")
+      {:ok, nil} = Cachex.get(:web_resp_cache, URI.parse(object.data["id"]).path)
 
       cached_object = Object.get_cached_by_ap_id(object.data["id"])
 
diff --git a/test/plugs/cache_test.exs b/test/plugs/cache_test.exs
new file mode 100644 (file)
index 0000000..e6e7f40
--- /dev/null
@@ -0,0 +1,186 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.CacheTest do
+  use ExUnit.Case, async: true
+  use Plug.Test
+
+  alias Pleroma.Plugs.Cache
+
+  @miss_resp {200,
+              [
+                {"cache-control", "max-age=0, private, must-revalidate"},
+                {"content-type", "cofe/hot; charset=utf-8"},
+                {"x-cache", "MISS from Pleroma"}
+              ], "cofe"}
+
+  @hit_resp {200,
+             [
+               {"cache-control", "max-age=0, private, must-revalidate"},
+               {"content-type", "cofe/hot; charset=utf-8"},
+               {"x-cache", "HIT from Pleroma"}
+             ], "cofe"}
+
+  @ttl 5
+
+  setup do
+    Cachex.clear(:web_resp_cache)
+    :ok
+  end
+
+  test "caches a response" do
+    assert @miss_resp ==
+             conn(:get, "/")
+             |> Cache.call(%{query_params: false, ttl: nil})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+
+    assert_raise(Plug.Conn.AlreadySentError, fn ->
+      conn(:get, "/")
+      |> Cache.call(%{query_params: false, ttl: nil})
+      |> put_resp_content_type("cofe/hot")
+      |> send_resp(:ok, "cofe")
+      |> sent_resp()
+    end)
+
+    assert @hit_resp ==
+             conn(:get, "/")
+             |> Cache.call(%{query_params: false, ttl: nil})
+             |> sent_resp()
+  end
+
+  test "ttl is set" do
+    assert @miss_resp ==
+             conn(:get, "/")
+             |> Cache.call(%{query_params: false, ttl: @ttl})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+
+    assert @hit_resp ==
+             conn(:get, "/")
+             |> Cache.call(%{query_params: false, ttl: @ttl})
+             |> sent_resp()
+
+    :timer.sleep(@ttl + 1)
+
+    assert @miss_resp ==
+             conn(:get, "/")
+             |> Cache.call(%{query_params: false, ttl: @ttl})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+  end
+
+  test "set ttl via conn.assigns" do
+    assert @miss_resp ==
+             conn(:get, "/")
+             |> Cache.call(%{query_params: false, ttl: nil})
+             |> put_resp_content_type("cofe/hot")
+             |> assign(:cache_ttl, @ttl)
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+
+    assert @hit_resp ==
+             conn(:get, "/")
+             |> Cache.call(%{query_params: false, ttl: nil})
+             |> sent_resp()
+
+    :timer.sleep(@ttl + 1)
+
+    assert @miss_resp ==
+             conn(:get, "/")
+             |> Cache.call(%{query_params: false, ttl: nil})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+  end
+
+  test "ignore query string when `query_params` is false" do
+    assert @miss_resp ==
+             conn(:get, "/?cofe")
+             |> Cache.call(%{query_params: false, ttl: nil})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+
+    assert @hit_resp ==
+             conn(:get, "/?cofefe")
+             |> Cache.call(%{query_params: false, ttl: nil})
+             |> sent_resp()
+  end
+
+  test "take query string into account when `query_params` is true" do
+    assert @miss_resp ==
+             conn(:get, "/?cofe")
+             |> Cache.call(%{query_params: true, ttl: nil})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+
+    assert @miss_resp ==
+             conn(:get, "/?cofefe")
+             |> Cache.call(%{query_params: true, ttl: nil})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+  end
+
+  test "take specific query params into account when `query_params` is list" do
+    assert @miss_resp ==
+             conn(:get, "/?a=1&b=2&c=3&foo=bar")
+             |> fetch_query_params()
+             |> Cache.call(%{query_params: ["a", "b", "c"], ttl: nil})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+
+    assert @hit_resp ==
+             conn(:get, "/?bar=foo&c=3&b=2&a=1")
+             |> fetch_query_params()
+             |> Cache.call(%{query_params: ["a", "b", "c"], ttl: nil})
+             |> sent_resp()
+
+    assert @miss_resp ==
+             conn(:get, "/?bar=foo&c=3&b=2&a=2")
+             |> fetch_query_params()
+             |> Cache.call(%{query_params: ["a", "b", "c"], ttl: nil})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+  end
+
+  test "ignore not GET requests" do
+    expected =
+      {200,
+       [
+         {"cache-control", "max-age=0, private, must-revalidate"},
+         {"content-type", "cofe/hot; charset=utf-8"}
+       ], "cofe"}
+
+    assert expected ==
+             conn(:post, "/")
+             |> Cache.call(%{query_params: true, ttl: nil})
+             |> put_resp_content_type("cofe/hot")
+             |> send_resp(:ok, "cofe")
+             |> sent_resp()
+  end
+
+  test "ignore non-successful responses" do
+    expected =
+      {418,
+       [
+         {"cache-control", "max-age=0, private, must-revalidate"},
+         {"content-type", "tea/iced; charset=utf-8"}
+       ], "🥤"}
+
+    assert expected ==
+             conn(:get, "/cofe")
+             |> Cache.call(%{query_params: true, ttl: nil})
+             |> put_resp_content_type("tea/iced")
+             |> send_resp(:im_a_teapot, "🥤")
+             |> sent_resp()
+  end
+end
index a25b72f4ed2cdfb4cd011331434a8b6f73e4719c..4d79f858a924bcdc10d3f4a63dfb1d1aaaa6171f 100644 (file)
@@ -570,22 +570,6 @@ defmodule Pleroma.UserTest do
         refute cs.valid?
       end)
     end
-
-    test "it restricts some sizes" do
-      bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
-      name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
-
-      [bio: bio_limit, name: name_limit]
-      |> Enum.each(fn {field, size} ->
-        string = String.pad_leading(".", size)
-        cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
-        assert cs.valid?
-
-        string = String.pad_leading(".", size + 1)
-        cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
-        refute cs.valid?
-      end)
-    end
   end
 
   describe "followers and friends" do
@@ -1081,7 +1065,7 @@ defmodule Pleroma.UserTest do
 
       user_activities =
         user.ap_id
-        |> Activity.query_by_actor()
+        |> Activity.Queries.by_actor()
         |> Repo.all()
         |> Enum.map(fn act -> act.data["type"] end)
 
@@ -1117,11 +1101,60 @@ defmodule Pleroma.UserTest do
     assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin")
   end
 
-  test "insert or update a user from given data" do
-    user = insert(:user, %{nickname: "nick@name.de"})
-    data = %{ap_id: user.ap_id <> "xxx", name: user.name, nickname: user.nickname}
+  describe "insert or update a user from given data" do
+    test "with normal data" do
+      user = insert(:user, %{nickname: "nick@name.de"})
+      data = %{ap_id: user.ap_id <> "xxx", name: user.name, nickname: user.nickname}
+
+      assert {:ok, %User{}} = User.insert_or_update_user(data)
+    end
+
+    test "with overly long fields" do
+      current_max_length = Pleroma.Config.get([:instance, :account_field_value_length], 255)
+      user = insert(:user, nickname: "nickname@supergood.domain")
+
+      data = %{
+        ap_id: user.ap_id,
+        name: user.name,
+        nickname: user.nickname,
+        info: %{
+          fields: [
+            %{"name" => "myfield", "value" => String.duplicate("h", current_max_length + 1)}
+          ]
+        }
+      }
+
+      assert {:ok, %User{}} = User.insert_or_update_user(data)
+    end
+
+    test "with an overly long bio" do
+      current_max_length = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+      user = insert(:user, nickname: "nickname@supergood.domain")
+
+      data = %{
+        ap_id: user.ap_id,
+        name: user.name,
+        nickname: user.nickname,
+        bio: String.duplicate("h", current_max_length + 1),
+        info: %{}
+      }
+
+      assert {:ok, %User{}} = User.insert_or_update_user(data)
+    end
+
+    test "with an overly long display name" do
+      current_max_length = Pleroma.Config.get([:instance, :user_name_length], 100)
+      user = insert(:user, nickname: "nickname@supergood.domain")
+
+      data = %{
+        ap_id: user.ap_id,
+        name: String.duplicate("h", current_max_length + 1),
+        nickname: user.nickname,
+        info: %{}
+      }
 
-    assert {:ok, %User{}} = User.insert_or_update_user(data)
+      assert {:ok, %User{}} = User.insert_or_update_user(data)
+    end
   end
 
   describe "per-user rich-text filtering" do
@@ -1614,4 +1647,31 @@ defmodule Pleroma.UserTest do
       assert User.user_info(other_user).following_count == 152
     end
   end
+
+  describe "change_email/2" do
+    setup do
+      [user: insert(:user)]
+    end
+
+    test "blank email returns error", %{user: user} do
+      assert {:error, %{errors: [email: {"can't be blank", _}]}} = User.change_email(user, "")
+      assert {:error, %{errors: [email: {"can't be blank", _}]}} = User.change_email(user, nil)
+    end
+
+    test "non unique email returns error", %{user: user} do
+      %{email: email} = insert(:user)
+
+      assert {:error, %{errors: [email: {"has already been taken", _}]}} =
+               User.change_email(user, email)
+    end
+
+    test "invalid email returns error", %{user: user} do
+      assert {:error, %{errors: [email: {"has invalid format", _}]}} =
+               User.change_email(user, "cofe")
+    end
+
+    test "changes email", %{user: user} do
+      assert {:ok, %User{email: "cofe@cofe.party"}} = User.change_email(user, "cofe@cofe.party")
+    end
+  end
 end
index 5192e734f7246be87efc138469ea9810c6bc1247..9698c70997012502c1006a6c47558eb782a48e9c 100644 (file)
@@ -175,6 +175,49 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
       assert json_response(conn, 404)
     end
+
+    test "it caches a response", %{conn: conn} do
+      note = insert(:note)
+      uuid = String.split(note.data["id"], "/") |> List.last()
+
+      conn1 =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/objects/#{uuid}")
+
+      assert json_response(conn1, :ok)
+      assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
+
+      conn2 =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/objects/#{uuid}")
+
+      assert json_response(conn1, :ok) == json_response(conn2, :ok)
+      assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
+    end
+
+    test "cached purged after object deletion", %{conn: conn} do
+      note = insert(:note)
+      uuid = String.split(note.data["id"], "/") |> List.last()
+
+      conn1 =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/objects/#{uuid}")
+
+      assert json_response(conn1, :ok)
+      assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
+
+      Object.delete(note)
+
+      conn2 =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/objects/#{uuid}")
+
+      assert "Not found" == json_response(conn2, :not_found)
+    end
   end
 
   describe "/object/:uuid/likes" do
@@ -264,6 +307,51 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
       assert json_response(conn, 404)
     end
+
+    test "it caches a response", %{conn: conn} do
+      activity = insert(:note_activity)
+      uuid = String.split(activity.data["id"], "/") |> List.last()
+
+      conn1 =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/activities/#{uuid}")
+
+      assert json_response(conn1, :ok)
+      assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
+
+      conn2 =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/activities/#{uuid}")
+
+      assert json_response(conn1, :ok) == json_response(conn2, :ok)
+      assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
+    end
+
+    test "cached purged after activity deletion", %{conn: conn} do
+      user = insert(:user)
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "cofe"})
+
+      uuid = String.split(activity.data["id"], "/") |> List.last()
+
+      conn1 =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/activities/#{uuid}")
+
+      assert json_response(conn1, :ok)
+      assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
+
+      Activity.delete_by_ap_id(activity.object.data["id"])
+
+      conn2 =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/activities/#{uuid}")
+
+      assert "Not found" == json_response(conn2, :not_found)
+    end
   end
 
   describe "/inbox" do
@@ -365,6 +453,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       assert json_response(conn, 403)
     end
 
+    test "it doesn't crash without an authenticated user", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/users/#{user.nickname}/inbox")
+
+      assert json_response(conn, 403)
+    end
+
     test "it returns a note activity in a collection", %{conn: conn} do
       note_activity = insert(:direct_note_activity)
       note_object = Object.normalize(note_activity)
index 36a39c84c9208a0f38e4eb9d86a2940fac6b4fe4..381757e1b99fdfd7a5788fc167b0077920d5ac90 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.ActivityPub.PublisherTest do
   use Pleroma.DataCase
 
+  import ExUnit.CaptureLog
   import Pleroma.Factory
   import Tesla.Mock
   import Mock
@@ -188,7 +189,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
       actor = insert(:user)
       inbox = "http://connrefused.site/users/nick1/inbox"
 
-      assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+      assert capture_log(fn ->
+               assert {:error, _} =
+                        Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+             end) =~ "connrefused"
 
       assert called(Instances.set_unreachable(inbox))
     end
@@ -212,14 +216,16 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
       actor = insert(:user)
       inbox = "http://connrefused.site/users/nick1/inbox"
 
-      assert {:error, _} =
-               Publisher.publish_one(%{
-                 inbox: inbox,
-                 json: "{}",
-                 actor: actor,
-                 id: 1,
-                 unreachable_since: NaiveDateTime.utc_now()
-               })
+      assert capture_log(fn ->
+               assert {:error, _} =
+                        Publisher.publish_one(%{
+                          inbox: inbox,
+                          json: "{}",
+                          actor: actor,
+                          id: 1,
+                          unreachable_since: NaiveDateTime.utc_now()
+                        })
+             end) =~ "connrefused"
 
       refute called(Instances.set_unreachable(inbox))
     end
index 4f7d592a66694e1e374a63ca0a9802aeb1801164..9db4255d884d8e880aee27b21a0b42d150367099 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Relay
 
+  import ExUnit.CaptureLog
   import Pleroma.Factory
   import Mock
 
@@ -20,7 +21,9 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
 
   describe "follow/1" do
     test "returns errors when user not found" do
-      assert Relay.follow("test-ap-id") == {:error, "Could not fetch by AP id"}
+      assert capture_log(fn ->
+               assert Relay.follow("test-ap-id") == {:error, "Could not fetch by AP id"}
+             end) =~ "Could not fetch by AP id"
     end
 
     test "returns activity" do
@@ -37,7 +40,9 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
 
   describe "unfollow/1" do
     test "returns errors when user not found" do
-      assert Relay.unfollow("test-ap-id") == {:error, "Could not fetch by AP id"}
+      assert capture_log(fn ->
+               assert Relay.unfollow("test-ap-id") == {:error, "Could not fetch by AP id"}
+             end) =~ "Could not fetch by AP id"
     end
 
     test "returns activity" do
@@ -78,7 +83,9 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
           }
         )
 
-      assert Relay.publish(activity) == {:error, nil}
+      assert capture_log(fn ->
+               assert Relay.publish(activity) == {:error, nil}
+             end) =~ "[error] error: nil"
     end
 
     test_with_mock "returns announce activity and publish to federate",
index 0661d5d7cf94ea5aa830746be612ac3fb95e084e..bebecce8db941d5f0cf355e17f3ed93d36557031 100644 (file)
@@ -102,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       assert capture_log(fn ->
                {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
-             end) =~ "[error] Couldn't fetch \"\"https://404.site/whatever\"\", error: nil"
+             end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil"
     end
 
     test "it works for incoming notices" do
index 4e2c274315d678782e4112d6d8d8c00282b72333..3b6d75a4cc34b0814aecaf7394a4515077a874f6 100644 (file)
@@ -1779,7 +1779,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                 %{"tuple" => [":seconds_valid", 60]},
                 %{"tuple" => [":path", ""]},
                 %{"tuple" => [":key1", nil]},
-                %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}
+                %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
+                %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
+                %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
+                %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
+                %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]}
               ]
             }
           ]
@@ -1796,7 +1800,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                      %{"tuple" => [":seconds_valid", 60]},
                      %{"tuple" => [":path", ""]},
                      %{"tuple" => [":key1", nil]},
-                     %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}
+                     %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
+                     %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
+                     %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
+                     %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
+                     %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]}
                    ]
                  }
                ]
index 3190dc1c8a31a2af85a5540cfc1102920b9116bd..204446b79aa69f3c38bd5ee8044127e18fbd52d0 100644 (file)
@@ -103,6 +103,30 @@ defmodule Pleroma.Web.AdminAPI.ConfigTest do
       assert Config.from_binary(binary) == ~r/comp[lL][aA][iI][nN]er/
     end
 
+    test "link sigil" do
+      binary = Config.transform("~r/https:\/\/example.com/")
+      assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/)
+      assert Config.from_binary(binary) == ~r/https:\/\/example.com/
+    end
+
+    test "link sigil with u modifier" do
+      binary = Config.transform("~r/https:\/\/example.com/u")
+      assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/u)
+      assert Config.from_binary(binary) == ~r/https:\/\/example.com/u
+    end
+
+    test "link sigil with i modifier" do
+      binary = Config.transform("~r/https:\/\/example.com/i")
+      assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/i)
+      assert Config.from_binary(binary) == ~r/https:\/\/example.com/i
+    end
+
+    test "link sigil with s modifier" do
+      binary = Config.transform("~r/https:\/\/example.com/s")
+      assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/s)
+      assert Config.from_binary(binary) == ~r/https:\/\/example.com/s
+    end
+
     test "2 child tuple" do
       binary = Config.transform(%{"tuple" => ["v1", ":v2"]})
       assert binary == :erlang.term_to_binary({"v1", :v2})
index e18f8f0d1df842035d08ab14f8738204c07adab2..061c3a8ad1be6497b376d28df6b3a334837f1481 100644 (file)
@@ -744,6 +744,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert id == to_string(activity.id)
   end
 
+  test "get statuses by IDs", %{conn: conn} do
+    %{id: id1} = insert(:note_activity)
+    %{id: id2} = insert(:note_activity)
+
+    query_string = "ids[]=#{id1}&ids[]=#{id2}"
+    conn = get(conn, "/api/v1/statuses/?#{query_string}")
+
+    assert [%{"id" => ^id1}, %{"id" => ^id2}] = json_response(conn, :ok)
+  end
+
   describe "deleting a status" do
     test "when you created it", %{conn: conn} do
       activity = insert(:note_activity)
@@ -3688,7 +3698,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         build_conn()
         |> assign(:user, user)
 
-      [conn: conn, activity: activity]
+      [conn: conn, activity: activity, user: user]
     end
 
     test "returns users who have favorited the status", %{conn: conn, activity: activity} do
@@ -3748,6 +3758,32 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       [%{"id" => id}] = response
       assert id == other_user.id
     end
+
+    test "requires authentification for private posts", %{conn: conn, user: user} do
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "@#{other_user.nickname} wanna get some #cofe together?",
+          "visibility" => "direct"
+        })
+
+      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+
+      conn
+      |> assign(:user, nil)
+      |> get("/api/v1/statuses/#{activity.id}/favourited_by")
+      |> json_response(404)
+
+      response =
+        build_conn()
+        |> assign(:user, other_user)
+        |> get("/api/v1/statuses/#{activity.id}/favourited_by")
+        |> json_response(200)
+
+      [%{"id" => id}] = response
+      assert id == other_user.id
+    end
   end
 
   describe "GET /api/v1/statuses/:id/reblogged_by" do
@@ -3759,7 +3795,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         build_conn()
         |> assign(:user, user)
 
-      [conn: conn, activity: activity]
+      [conn: conn, activity: activity, user: user]
     end
 
     test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
@@ -3819,6 +3855,29 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       [%{"id" => id}] = response
       assert id == other_user.id
     end
+
+    test "requires authentification for private posts", %{conn: conn, user: user} do
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "@#{other_user.nickname} wanna get some #cofe together?",
+          "visibility" => "direct"
+        })
+
+      conn
+      |> assign(:user, nil)
+      |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
+      |> json_response(404)
+
+      response =
+        build_conn()
+        |> assign(:user, other_user)
+        |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
+        |> json_response(200)
+
+      assert [] == response
+    end
   end
 
   describe "POST /auth/password, with valid parameters" do
@@ -3953,13 +4012,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       Config.put([:suggestions, :enabled], true)
       Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}")
 
-      res =
-        conn
-        |> assign(:user, user)
-        |> get("/api/v1/suggestions")
-        |> json_response(500)
+      assert capture_log(fn ->
+               res =
+                 conn
+                 |> assign(:user, user)
+                 |> get("/api/v1/suggestions")
+                 |> json_response(500)
 
-      assert res == "Something went wrong"
+               assert res == "Something went wrong"
+             end) =~ "Could not retrieve suggestions"
     end
 
     test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do
index 685e482708ab631fed713a92bbf9dce8fde3d2c8..c990e0abc2e958842b907a3a7a59b28071fb138f 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
+  import ExUnit.CaptureLog
   import Pleroma.Factory
   import Mock
 
@@ -340,12 +341,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
     test "show follow page with error when user cannot fecth by `acct` link", %{conn: conn} do
       user = insert(:user)
 
-      response =
-        conn
-        |> assign(:user, user)
-        |> get("/ostatus_subscribe?acct=https://mastodon.social/users/not_found")
+      assert capture_log(fn ->
+               response =
+                 conn
+                 |> assign(:user, user)
+                 |> get("/ostatus_subscribe?acct=https://mastodon.social/users/not_found")
 
-      assert html_response(response, 200) =~ "Error fetching user"
+               assert html_response(response, 200) =~ "Error fetching user"
+             end) =~ "Object has been deleted"
     end
   end
 
@@ -664,4 +667,111 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       assert called(Pleroma.Captcha.new())
     end
   end
+
+  defp with_credentials(conn, username, password) do
+    header_content = "Basic " <> Base.encode64("#{username}:#{password}")
+    put_req_header(conn, "authorization", header_content)
+  end
+
+  defp valid_user(_context) do
+    user = insert(:user)
+    [user: user]
+  end
+
+  describe "POST /api/pleroma/change_email" do
+    setup [:valid_user]
+
+    test "without credentials", %{conn: conn} do
+      conn = post(conn, "/api/pleroma/change_email")
+      assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
+    end
+
+    test "with credentials and invalid password", %{conn: conn, user: current_user} do
+      conn =
+        conn
+        |> with_credentials(current_user.nickname, "test")
+        |> post("/api/pleroma/change_email", %{
+          "password" => "hi",
+          "email" => "test@test.com"
+        })
+
+      assert json_response(conn, 200) == %{"error" => "Invalid password."}
+    end
+
+    test "with credentials, valid password and invalid email", %{
+      conn: conn,
+      user: current_user
+    } do
+      conn =
+        conn
+        |> with_credentials(current_user.nickname, "test")
+        |> post("/api/pleroma/change_email", %{
+          "password" => "test",
+          "email" => "foobar"
+        })
+
+      assert json_response(conn, 200) == %{"error" => "Email has invalid format."}
+    end
+
+    test "with credentials, valid password and no email", %{
+      conn: conn,
+      user: current_user
+    } do
+      conn =
+        conn
+        |> with_credentials(current_user.nickname, "test")
+        |> post("/api/pleroma/change_email", %{
+          "password" => "test"
+        })
+
+      assert json_response(conn, 200) == %{"error" => "Email can't be blank."}
+    end
+
+    test "with credentials, valid password and blank email", %{
+      conn: conn,
+      user: current_user
+    } do
+      conn =
+        conn
+        |> with_credentials(current_user.nickname, "test")
+        |> post("/api/pleroma/change_email", %{
+          "password" => "test",
+          "email" => ""
+        })
+
+      assert json_response(conn, 200) == %{"error" => "Email can't be blank."}
+    end
+
+    test "with credentials, valid password and non unique email", %{
+      conn: conn,
+      user: current_user
+    } do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> with_credentials(current_user.nickname, "test")
+        |> post("/api/pleroma/change_email", %{
+          "password" => "test",
+          "email" => user.email
+        })
+
+      assert json_response(conn, 200) == %{"error" => "Email has already been taken."}
+    end
+
+    test "with credentials, valid password and valid email", %{
+      conn: conn,
+      user: current_user
+    } do
+      conn =
+        conn
+        |> with_credentials(current_user.nickname, "test")
+        |> post("/api/pleroma/change_email", %{
+          "password" => "test",
+          "email" => "cofe@foobar.com"
+        })
+
+      assert json_response(conn, 200) == %{"status" => "success"}
+    end
+  end
 end
index e23086b2aeb7edf88402a360e220bf81beb5a7e3..bd3ccaaf700754f18ba837cc43edd870aa91c705 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
   use Pleroma.Web.ConnCase
 
+  import ExUnit.CaptureLog
   import Pleroma.Factory
   import Tesla.Mock
 
@@ -75,11 +76,13 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do
   test "Sends a 404 when invalid format" do
     user = insert(:user)
 
-    assert_raise Phoenix.NotAcceptableError, fn ->
-      build_conn()
-      |> put_req_header("accept", "text/html")
-      |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
-    end
+    assert capture_log(fn ->
+             assert_raise Phoenix.NotAcceptableError, fn ->
+               build_conn()
+               |> put_req_header("accept", "text/html")
+               |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost")
+             end
+           end) =~ "no supported media type in accept header"
   end
 
   test "Sends a 400 when resource param is missing" do