Merge remote-tracking branch 'origin/develop' into shigusegubu
This commit is contained in:
commit
63b82091d9
153 changed files with 3143 additions and 539 deletions
|
|
@ -79,12 +79,12 @@ build-1.14.5-otp-25:
|
|||
script:
|
||||
- mix compile --force
|
||||
|
||||
build-1.17.1-otp-26:
|
||||
build-1.18.3-otp-27:
|
||||
extends:
|
||||
- .build_changes_policy
|
||||
- .using-ci-base
|
||||
stage: build
|
||||
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.17.1-otp-26
|
||||
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.18.3-otp-27
|
||||
script:
|
||||
- mix compile --force
|
||||
|
||||
|
|
@ -142,12 +142,12 @@ unit-testing-1.14.5-otp-25:
|
|||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
|
||||
unit-testing-1.17.1-otp-26:
|
||||
unit-testing-1.18.3-otp-27:
|
||||
extends:
|
||||
- .build_changes_policy
|
||||
- .using-ci-base
|
||||
stage: test
|
||||
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.17.1-otp-26
|
||||
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.18.3-otp-27
|
||||
cache: *testing_cache_policy
|
||||
services: *testing_services
|
||||
script: *testing_script
|
||||
|
|
@ -208,7 +208,7 @@ docs-deploy:
|
|||
before_script:
|
||||
- apk add curl
|
||||
script:
|
||||
- curl --fail-with-body -X POST -F"token=$CI_JOB_TOKEN" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" https://git.pleroma.social/api/v4/projects/673/trigger/pipeline
|
||||
- curl --fail-with-body -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" https://git.pleroma.social/api/v4/projects/673/trigger/pipeline
|
||||
review_app:
|
||||
image: alpine:3.9
|
||||
stage: deploy
|
||||
|
|
@ -249,7 +249,7 @@ spec-deploy:
|
|||
before_script:
|
||||
- apk add curl
|
||||
script:
|
||||
- curl --fail-with-body -X POST -F"token=$CI_JOB_TOKEN" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline
|
||||
- curl --fail-with-body -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline
|
||||
|
||||
|
||||
stop_review_app:
|
||||
|
|
|
|||
1
changelog.d/activity_type_index.change
Normal file
1
changelog.d/activity_type_index.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add new activity actor/type index. Greatly speeds up retrieval of rare types (like "Listen")
|
||||
1
changelog.d/admin-api-docs-fix.skip
Normal file
1
changelog.d/admin-api-docs-fix.skip
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix 'Create a user' description in admin api docs
|
||||
0
changelog.d/admin-api-log-fix.skip
Normal file
0
changelog.d/admin-api-log-fix.skip
Normal file
1
changelog.d/assign-app-user-oom.fix
Normal file
1
changelog.d/assign-app-user-oom.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix AssignAppUser migration OOM
|
||||
1
changelog.d/deepl-json.fix
Normal file
1
changelog.d/deepl-json.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Use JSON for DeepL API requests
|
||||
1
changelog.d/dislike-activity.add
Normal file
1
changelog.d/dislike-activity.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Support Dislike activity, as sent by Mitra and Friendica, by changing it into a thumbs-down EmojiReact
|
||||
0
changelog.d/doc-typo.skip
Normal file
0
changelog.d/doc-typo.skip
Normal file
1
changelog.d/elixir-1-18.fix
Normal file
1
changelog.d/elixir-1-18.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Elixir 1.18: Fixed warnings and new deprecations
|
||||
1
changelog.d/emoji_likes.add
Normal file
1
changelog.d/emoji_likes.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Support Mitra-style emoji likes.
|
||||
1
changelog.d/expiring-blocks.add
Normal file
1
changelog.d/expiring-blocks.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add `duration` to the block endpoint, which makes block expire
|
||||
1
changelog.d/expose-markup-configuration.add
Normal file
1
changelog.d/expose-markup-configuration.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Expose markup configuration in InstanceView
|
||||
0
changelog.d/fixtests.skip
Normal file
0
changelog.d/fixtests.skip
Normal file
1
changelog.d/freebsd-rc.fix
Normal file
1
changelog.d/freebsd-rc.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Set PATH in the FreeBSD rc script to avoid failures starting the service
|
||||
1
changelog.d/gin-search.fix
Normal file
1
changelog.d/gin-search.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Improved performance of status search queries using the default GIN index
|
||||
1
changelog.d/language-detection.add
Normal file
1
changelog.d/language-detection.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Implement language detection with fastText
|
||||
0
changelog.d/openbsd-docs-update.skip
Normal file
0
changelog.d/openbsd-docs-update.skip
Normal file
1
changelog.d/openbsd-update-httpd-relayd.change
Normal file
1
changelog.d/openbsd-update-httpd-relayd.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Updated relayd/httpd config files to be on par with nginx
|
||||
1
changelog.d/openbsd-update-rc.fix
Normal file
1
changelog.d/openbsd-update-rc.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
replaced depracated flags and functions, renamed service to fit other service files
|
||||
1
changelog.d/preserve-public-cc.fix
Normal file
1
changelog.d/preserve-public-cc.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix federation issue where Public visibility information in cc field was lost when sent to remote servers, causing posts to appear with inconsistent visibility across instances
|
||||
0
changelog.d/private-functions.skip
Normal file
0
changelog.d/private-functions.skip
Normal file
1
changelog.d/relax-also-known-as.change
Normal file
1
changelog.d/relax-also-known-as.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Relax alsoKnownAs requirements to just URI, not necessarily HTTP(S)
|
||||
1
changelog.d/releases.fix
Normal file
1
changelog.d/releases.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix release builds
|
||||
0
changelog.d/remove-forgotten-OTPVersion-usage.skip
Normal file
0
changelog.d/remove-forgotten-OTPVersion-usage.skip
Normal file
1
changelog.d/scrobbles.change
Normal file
1
changelog.d/scrobbles.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Change scrobble external link param name to use snake case
|
||||
1
changelog.d/siteinfo-baseurls.add
Normal file
1
changelog.d/siteinfo-baseurls.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add `base_urls` to the /api/v1/instance pleroma metadata which provides information about the base URLs for media_proxy and uploads when configured
|
||||
1
changelog.d/toctou-mkdir.fix
Normal file
1
changelog.d/toctou-mkdir.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Backport [Elixir PR 14242](https://github.com/elixir-lang/elixir/pull/14242) fixing racy mkdir and lack of error handling of parent directory creation
|
||||
1
changelog.d/tos-setting.add
Normal file
1
changelog.d/tos-setting.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow Terms of Service panel behaviour to be configurable
|
||||
1
changelog.d/translate-posts.add
Normal file
1
changelog.d/translate-posts.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Support translation providers (DeepL, LibreTranslate)
|
||||
1
changelog.d/truncate-rich-media.change
Normal file
1
changelog.d/truncate-rich-media.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Truncate the length of Rich Media title and description fields
|
||||
0
changelog.d/typos.skip
Normal file
0
changelog.d/typos.skip
Normal file
1
changelog.d/webfinger.change
Normal file
1
changelog.d/webfinger.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Don't require an Accept header for WebFinger queries and default to JSON.
|
||||
8
ci/elixir-1.18.3-otp-27/Dockerfile
Normal file
8
ci/elixir-1.18.3-otp-27/Dockerfile
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
FROM elixir:1.18.3-otp-27
|
||||
|
||||
# Single RUN statement, otherwise intermediate images are created
|
||||
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
|
||||
RUN apt-get update &&\
|
||||
apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\
|
||||
mix local.hex --force &&\
|
||||
mix local.rebar --force
|
||||
1
ci/elixir-1.18.3-otp-27/build_and_push.sh
Executable file
1
ci/elixir-1.18.3-otp-27/build_and_push.sh
Executable file
|
|
@ -0,0 +1 @@
|
|||
docker buildx build --platform linux/amd64,linux/arm64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.18.3-otp-27 --push .
|
||||
|
|
@ -48,7 +48,7 @@ config :pleroma, ecto_repos: [Pleroma.Repo]
|
|||
|
||||
config :pleroma, Pleroma.Repo,
|
||||
telemetry_event: [Pleroma.Repo.Instrumenter],
|
||||
migration_lock: nil
|
||||
migration_lock: :pg_advisory_lock
|
||||
|
||||
config :pleroma, Pleroma.Captcha,
|
||||
enabled: true,
|
||||
|
|
@ -305,6 +305,7 @@ config :pleroma, :frontend_configurations,
|
|||
collapseMessageWithSubject: false,
|
||||
disableChat: false,
|
||||
greentext: false,
|
||||
embeddedToS: true,
|
||||
hideFilteredStatuses: false,
|
||||
hideMutedPosts: false,
|
||||
hidePostStats: false,
|
||||
|
|
|
|||
|
|
@ -1261,6 +1261,7 @@ config :pleroma, :config_description, [
|
|||
background: "/static/aurora_borealis.jpg",
|
||||
collapseMessageWithSubject: false,
|
||||
greentext: false,
|
||||
embeddedToS: true,
|
||||
hideFilteredStatuses: false,
|
||||
hideMutedPosts: false,
|
||||
hidePostStats: false,
|
||||
|
|
@ -1312,6 +1313,12 @@ config :pleroma, :config_description, [
|
|||
type: :boolean,
|
||||
description: "Enables green text on lines prefixed with the > character"
|
||||
},
|
||||
%{
|
||||
key: :embeddedToS,
|
||||
label: "Embedded ToS panel",
|
||||
type: :boolean,
|
||||
description: "Hide Terms of Service panel decorations on About and Registration pages"
|
||||
},
|
||||
%{
|
||||
key: :hideFilteredStatuses,
|
||||
label: "Hide Filtered Statuses",
|
||||
|
|
@ -3500,5 +3507,71 @@ config :pleroma, :config_description, [
|
|||
suggestion: [100_000]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Language.LanguageDetector,
|
||||
type: :group,
|
||||
description: "Language detection providers",
|
||||
children: [
|
||||
%{
|
||||
key: :provider,
|
||||
type: :module,
|
||||
suggestions: [
|
||||
Pleroma.Language.LanguageDetector.Fasttext
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.LanguageDetector.Fasttext},
|
||||
key: :model,
|
||||
label: "fastText language detection model",
|
||||
type: :string,
|
||||
suggestions: ["/usr/share/fasttext/lid.176.bin"]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Language.Translation,
|
||||
type: :group,
|
||||
description: "Translation providers",
|
||||
children: [
|
||||
%{
|
||||
key: :provider,
|
||||
type: :module,
|
||||
suggestions: [
|
||||
Pleroma.Language.Translation.Deepl,
|
||||
Pleroma.Language.Translation.Libretranslate
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.Translation.Deepl},
|
||||
key: :base_url,
|
||||
label: "DeepL base URL",
|
||||
type: :string,
|
||||
suggestions: ["https://api-free.deepl.com", "https://api.deepl.com"]
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.Translation.Deepl},
|
||||
key: :api_key,
|
||||
label: "DeepL API Key",
|
||||
type: :string,
|
||||
suggestions: ["YOUR_API_KEY"]
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.Translation.Libretranslate},
|
||||
key: :base_url,
|
||||
label: "LibreTranslate instance URL",
|
||||
type: :string,
|
||||
suggestions: ["https://libretranslate.com"]
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.Translation.Libretranslate},
|
||||
key: :api_key,
|
||||
label: "LibreTranslate API Key",
|
||||
type: :string,
|
||||
suggestions: ["YOUR_API_KEY"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ config :pleroma, Pleroma.User.Backup, config_impl: Pleroma.UnstubbedConfigMock
|
|||
config :pleroma, Pleroma.Uploaders.S3, ex_aws_impl: Pleroma.Uploaders.S3.ExAwsMock
|
||||
config :pleroma, Pleroma.Uploaders.S3, config_impl: Pleroma.UnstubbedConfigMock
|
||||
config :pleroma, Pleroma.Upload, config_impl: Pleroma.UnstubbedConfigMock
|
||||
config :pleroma, Pleroma.Language.LanguageDetector, config_impl: Pleroma.StaticStubbedConfigMock
|
||||
config :pleroma, Pleroma.ScheduledActivity, config_impl: Pleroma.UnstubbedConfigMock
|
||||
config :pleroma, Pleroma.Web.RichMedia.Helpers, config_impl: Pleroma.StaticStubbedConfigMock
|
||||
config :pleroma, Pleroma.Uploaders.IPFS, config_impl: Pleroma.UnstubbedConfigMock
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ The `/api/v1/pleroma/admin/*` path is backwards compatible with `/api/pleroma/ad
|
|||
- `nicknames`
|
||||
- Response: Array of user nicknames
|
||||
|
||||
## `POST /api/v1/pleroma/admin/users`
|
||||
|
||||
### Create a user
|
||||
|
||||
- Method: `POST`
|
||||
|
|
@ -81,7 +83,7 @@ The `/api/v1/pleroma/admin/*` path is backwards compatible with `/api/pleroma/ad
|
|||
`password`
|
||||
}
|
||||
]
|
||||
- Response: User’s nickname
|
||||
- Response: Array of user objects
|
||||
|
||||
## `POST /api/v1/pleroma/admin/users/follow`
|
||||
|
||||
|
|
|
|||
|
|
@ -671,6 +671,7 @@ Audio scrobbling in Pleroma is **deprecated**.
|
|||
"artist": "Some Artist",
|
||||
"album": "Some Album",
|
||||
"length": 180000,
|
||||
"external_link": "https://www.last.fm/music/Some+Artist/_/Some+Title",
|
||||
"created_at": "2019-09-28T12:40:45.000Z"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1 +1,7 @@
|
|||
This section contains notes and guidelines for developers.
|
||||
|
||||
- [Setting up a Pleroma development environment](setting_up_pleroma_dev.md)
|
||||
- [Setting up a Gitlab Runner](setting_up_a_gitlab_runner.md)
|
||||
- [Authentication & Authorization](authentication_authorization.md)
|
||||
- [ActivityPub Extensions](ap_extensions.md)
|
||||
- [Mox Testing Guide](mox_testing.md)
|
||||
|
|
|
|||
485
docs/development/mox_testing.md
Normal file
485
docs/development/mox_testing.md
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
# Using Mox for Testing in Pleroma
|
||||
|
||||
## Introduction
|
||||
|
||||
This guide explains how to use [Mox](https://hexdocs.pm/mox/Mox.html) for testing in Pleroma and how to migrate existing tests from Mock/meck to Mox. Mox is a library for defining concurrent mocks in Elixir that offers several key advantages:
|
||||
|
||||
- **Async-safe testing**: Mox supports concurrent testing with `async: true`
|
||||
- **Explicit contract through behaviors**: Enforces implementation of behavior callbacks
|
||||
- **No module redefinition**: Avoids runtime issues caused by redefining modules
|
||||
- **Expectations scoped to the current process**: Prevents test state from leaking between tests
|
||||
|
||||
## Why Migrate from Mock/meck to Mox?
|
||||
|
||||
### Problems with Mock/meck
|
||||
|
||||
1. **Not async-safe**: Tests using Mock/meck cannot safely run with `async: true`, which slows down the test suite
|
||||
2. **Global state**: Mocked functions are global, leading to potential cross-test contamination
|
||||
3. **No explicit contract**: No guarantee that mocked functions match the actual implementation
|
||||
4. **Module redefinition**: Can lead to hard-to-debug runtime issues
|
||||
|
||||
### Benefits of Mox
|
||||
|
||||
1. **Async-safe testing**: Tests can run concurrently with `async: true`, significantly speeding up the test suite
|
||||
2. **Process isolation**: Expectations are set per process, preventing leakage between tests
|
||||
3. **Explicit contracts via behaviors**: Ensures mocks implement all required functions
|
||||
4. **Compile-time checks**: Prevents mocking non-existent functions
|
||||
5. **No module redefinition**: Mocks are defined at compile time, not runtime
|
||||
|
||||
## Existing Mox Setup in Pleroma
|
||||
|
||||
Pleroma already has a basic Mox setup in the `Pleroma.DataCase` module, which handles some common mocking scenarios automatically. Here's what's included:
|
||||
|
||||
### Default Mox Configuration
|
||||
|
||||
The `setup` function in `DataCase` does the following:
|
||||
|
||||
1. Sets up Mox for either async or non-async tests
|
||||
2. Verifies all mock expectations on test exit
|
||||
3. Stubs common dependencies with their real implementations
|
||||
|
||||
```elixir
|
||||
# From test/support/data_case.ex
|
||||
setup tags do
|
||||
setup_multi_process_mode(tags)
|
||||
setup_streamer(tags)
|
||||
stub_pipeline()
|
||||
|
||||
Mox.verify_on_exit!()
|
||||
|
||||
:ok
|
||||
end
|
||||
```
|
||||
|
||||
### Async vs. Non-Async Test Setup
|
||||
|
||||
Pleroma configures Mox differently depending on whether your test is async or not:
|
||||
|
||||
```elixir
|
||||
def setup_multi_process_mode(tags) do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
|
||||
|
||||
if tags[:async] do
|
||||
# For async tests, use process-specific mocks and stub CachexMock with NullCache
|
||||
Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache)
|
||||
Mox.set_mox_private()
|
||||
else
|
||||
# For non-async tests, use global mocks and stub CachexMock with CachexProxy
|
||||
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
|
||||
|
||||
Mox.set_mox_global()
|
||||
Mox.stub_with(Pleroma.CachexMock, Pleroma.CachexProxy)
|
||||
clear_cachex()
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
```
|
||||
|
||||
### Default Pipeline Stubs
|
||||
|
||||
Pleroma automatically stubs several core components with their real implementations:
|
||||
|
||||
```elixir
|
||||
def stub_pipeline do
|
||||
Mox.stub_with(Pleroma.Web.ActivityPub.SideEffectsMock, Pleroma.Web.ActivityPub.SideEffects)
|
||||
Mox.stub_with(Pleroma.Web.ActivityPub.ObjectValidatorMock, Pleroma.Web.ActivityPub.ObjectValidator)
|
||||
Mox.stub_with(Pleroma.Web.ActivityPub.MRFMock, Pleroma.Web.ActivityPub.MRF)
|
||||
Mox.stub_with(Pleroma.Web.ActivityPub.ActivityPubMock, Pleroma.Web.ActivityPub.ActivityPub)
|
||||
Mox.stub_with(Pleroma.Web.FederatorMock, Pleroma.Web.Federator)
|
||||
Mox.stub_with(Pleroma.ConfigMock, Pleroma.Config)
|
||||
Mox.stub_with(Pleroma.StaticStubbedConfigMock, Pleroma.Test.StaticConfig)
|
||||
Mox.stub_with(Pleroma.StubbedHTTPSignaturesMock, Pleroma.Test.HTTPSignaturesProxy)
|
||||
end
|
||||
```
|
||||
|
||||
This means that by default, these mocks will behave like their real implementations unless you explicitly override them with expectations in your tests.
|
||||
|
||||
### Understanding Config Mock Types
|
||||
|
||||
Pleroma has three different Config mock implementations, each with a specific purpose and different characteristics regarding async test safety:
|
||||
|
||||
#### 1. ConfigMock
|
||||
|
||||
- Defined in `test/support/mocks.ex` as `Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting)`
|
||||
- It's stubbed with the real `Pleroma.Config` by default in `DataCase`: `Mox.stub_with(Pleroma.ConfigMock, Pleroma.Config)`
|
||||
- This means it falls back to the normal configuration behavior unless explicitly overridden
|
||||
- Used for general mocking of configuration in tests where you want most config to behave normally
|
||||
- ⚠️ **NOT ASYNC-SAFE**: Since it's stubbed with the real `Pleroma.Config`, it modifies global application state
|
||||
- Can not be used in tests with `async: true`
|
||||
|
||||
#### 2. StaticStubbedConfigMock
|
||||
|
||||
- Defined in `test/support/mocks.ex` as `Mox.defmock(Pleroma.StaticStubbedConfigMock, for: Pleroma.Config.Getting)`
|
||||
- It's stubbed with `Pleroma.Test.StaticConfig` (defined in `test/test_helper.exs`)
|
||||
- `Pleroma.Test.StaticConfig` creates a completely static configuration snapshot at the start of the test run:
|
||||
```elixir
|
||||
defmodule Pleroma.Test.StaticConfig do
|
||||
@moduledoc """
|
||||
This module provides a Config that is completely static, built at startup time from the environment.
|
||||
It's safe to use in testing as it will not modify any state.
|
||||
"""
|
||||
|
||||
@behaviour Pleroma.Config.Getting
|
||||
@config Application.get_all_env(:pleroma)
|
||||
|
||||
def get(path, default \\ nil) do
|
||||
get_in(@config, path) || default
|
||||
end
|
||||
end
|
||||
```
|
||||
- Configuration is frozen at startup time and doesn't change during the test run
|
||||
- ✅ **ASYNC-SAFE**: Never modifies global state since it uses a frozen snapshot of the configuration
|
||||
|
||||
#### 3. UnstubbedConfigMock
|
||||
|
||||
- Defined in `test/support/mocks.ex` as `Mox.defmock(Pleroma.UnstubbedConfigMock, for: Pleroma.Config.Getting)`
|
||||
- Unlike the other two mocks, it's not automatically stubbed with any implementation in `DataCase`
|
||||
- Starts completely "unstubbed" and requires tests to explicitly set expectations or stub it
|
||||
- The most commonly used configuration mock in the test suite
|
||||
- Often aliased as `ConfigMock` in individual test files: `alias Pleroma.UnstubbedConfigMock, as: ConfigMock`
|
||||
- Set as the default config implementation in `config/test.exs`: `config :pleroma, :config_impl, Pleroma.UnstubbedConfigMock`
|
||||
- Offers maximum flexibility for tests that need precise control over configuration values
|
||||
- ✅ **ASYNC-SAFE**: Safe if used with `expect()` to set up test-specific expectations (since expectations are process-scoped)
|
||||
|
||||
#### Configuring Components to Use Specific Mocks
|
||||
|
||||
In `config/test.exs`, different components can be configured to use different configuration mocks:
|
||||
|
||||
```elixir
|
||||
# Components using UnstubbedConfigMock
|
||||
config :pleroma, Pleroma.Upload, config_impl: Pleroma.UnstubbedConfigMock
|
||||
config :pleroma, Pleroma.User.Backup, config_impl: Pleroma.UnstubbedConfigMock
|
||||
config :pleroma, Pleroma.Uploaders.S3, config_impl: Pleroma.UnstubbedConfigMock
|
||||
|
||||
# Components using StaticStubbedConfigMock (async-safe)
|
||||
config :pleroma, Pleroma.Language.LanguageDetector, config_impl: Pleroma.StaticStubbedConfigMock
|
||||
config :pleroma, Pleroma.Web.RichMedia.Helpers, config_impl: Pleroma.StaticStubbedConfigMock
|
||||
config :pleroma, Pleroma.Web.Plugs.HTTPSecurityPlug, config_impl: Pleroma.StaticStubbedConfigMock
|
||||
```
|
||||
|
||||
This allows different parts of the application to use the most appropriate configuration mocking strategy based on their specific needs.
|
||||
|
||||
#### When to Use Each Config Mock Type
|
||||
|
||||
- **ConfigMock**: ⚠️ For non-async tests only, when you want most configuration to behave normally with occasional overrides
|
||||
- **StaticStubbedConfigMock**: ✅ For async tests where modifying global state would be problematic and a static configuration is sufficient
|
||||
- **UnstubbedConfigMock**: ⚠️ Use carefully in async tests; set specific expectations rather than stubbing with implementations that modify global state
|
||||
|
||||
#### Summary of Async Safety
|
||||
|
||||
| Mock Type | Async-Safe? | Best Use Case |
|
||||
|-----------|-------------|--------------|
|
||||
| ConfigMock | ❌ No | Non-async tests that need minimal configuration overrides |
|
||||
| StaticStubbedConfigMock | ✅ Yes | Async tests that need configuration values without modification |
|
||||
| UnstubbedConfigMock | ⚠️ Depends | Any test with careful usage; set expectations rather than stubbing |
|
||||
|
||||
## Configuration in Async Tests
|
||||
|
||||
### Understanding `clear_config` Limitations
|
||||
|
||||
The `clear_config` helper is commonly used in Pleroma tests to modify configuration for specific tests. However, it's important to understand that **`clear_config` is not async-safe** and should not be used in tests with `async: true`.
|
||||
|
||||
Here's why:
|
||||
|
||||
```elixir
|
||||
# Implementation of clear_config in test/support/helpers.ex
|
||||
defmacro clear_config(config_path, temp_setting) do
|
||||
quote do
|
||||
clear_config(unquote(config_path)) do
|
||||
Config.put(unquote(config_path), unquote(temp_setting))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro clear_config(config_path, do: yield) do
|
||||
quote do
|
||||
initial_setting = Config.fetch(unquote(config_path))
|
||||
|
||||
unquote(yield)
|
||||
|
||||
on_exit(fn ->
|
||||
case initial_setting do
|
||||
:error ->
|
||||
Config.delete(unquote(config_path))
|
||||
|
||||
{:ok, value} ->
|
||||
Config.put(unquote(config_path), value)
|
||||
end
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The issue is that `clear_config`:
|
||||
1. Modifies the global application environment
|
||||
2. Uses `on_exit` to restore the original value after the test
|
||||
3. Can lead to race conditions when multiple async tests modify the same configuration
|
||||
|
||||
### Async-Safe Configuration Approaches
|
||||
|
||||
When writing async tests with Mox, use these approaches instead of `clear_config`:
|
||||
|
||||
1. **Dependency Injection with Module Attributes**:
|
||||
```elixir
|
||||
# In your module
|
||||
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
|
||||
|
||||
def some_function do
|
||||
value = @config_impl.get([:some, :config])
|
||||
# ...
|
||||
end
|
||||
```
|
||||
|
||||
2. **Mock the Config Module**:
|
||||
```elixir
|
||||
# In your test
|
||||
Pleroma.ConfigMock
|
||||
|> expect(:get, fn [:some, :config] -> "test_value" end)
|
||||
```
|
||||
|
||||
3. **Use Test-Specific Implementations**:
|
||||
```elixir
|
||||
# Define a test-specific implementation
|
||||
defmodule TestConfig do
|
||||
def get([:some, :config]), do: "test_value"
|
||||
def get(_), do: nil
|
||||
end
|
||||
|
||||
# In your test
|
||||
Mox.stub_with(Pleroma.ConfigMock, TestConfig)
|
||||
```
|
||||
|
||||
4. **Pass Configuration as Arguments**:
|
||||
```elixir
|
||||
# Refactor functions to accept configuration as arguments
|
||||
def some_function(config \\ nil) do
|
||||
config = config || Pleroma.Config.get([:some, :config])
|
||||
# ...
|
||||
end
|
||||
|
||||
# In your test
|
||||
some_function("test_value")
|
||||
```
|
||||
|
||||
By using these approaches, you can safely run tests with `async: true` without worrying about configuration conflicts.
|
||||
|
||||
## Setting Up Mox in Pleroma
|
||||
|
||||
### Step 1: Define a Behavior
|
||||
|
||||
Start by defining a behavior for the module you want to mock. This specifies the contract that both the real implementation and mocks must follow.
|
||||
|
||||
```elixir
|
||||
# In your implementation module (e.g., lib/pleroma/uploaders/s3.ex)
|
||||
defmodule Pleroma.Uploaders.S3.ExAwsAPI do
|
||||
@callback request(op :: ExAws.Operation.t()) :: {:ok, ExAws.Operation.t()} | {:error, term()}
|
||||
end
|
||||
```
|
||||
|
||||
### Step 2: Make Your Implementation Configurable
|
||||
|
||||
Modify your module to use a configurable implementation. This allows for dependency injection and easier testing.
|
||||
|
||||
```elixir
|
||||
# In your implementation module
|
||||
@ex_aws_impl Application.compile_env(:pleroma, [__MODULE__, :ex_aws_impl], ExAws)
|
||||
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
|
||||
|
||||
def put_file(%Pleroma.Upload{} = upload) do
|
||||
# Use @ex_aws_impl instead of ExAws directly
|
||||
case @ex_aws_impl.request(op) do
|
||||
{:ok, _} ->
|
||||
{:ok, {:file, s3_name}}
|
||||
|
||||
error ->
|
||||
Logger.error("#{__MODULE__}: #{inspect(error)}")
|
||||
error
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Step 3: Define the Mock in test/support/mocks.ex
|
||||
|
||||
Add your mock definition in the central mocks file:
|
||||
|
||||
```elixir
|
||||
# In test/support/mocks.ex
|
||||
Mox.defmock(Pleroma.Uploaders.S3.ExAwsMock, for: Pleroma.Uploaders.S3.ExAwsAPI)
|
||||
```
|
||||
|
||||
### Step 4: Configure the Mock in Test Environment
|
||||
|
||||
In your test configuration (e.g., `config/test.exs`), specify which mock implementation to use:
|
||||
|
||||
```elixir
|
||||
config :pleroma, Pleroma.Uploaders.S3, ex_aws_impl: Pleroma.Uploaders.S3.ExAwsMock
|
||||
config :pleroma, Pleroma.Uploaders.S3, config_impl: Pleroma.UnstubbedConfigMock
|
||||
```
|
||||
|
||||
## Writing Tests with Mox
|
||||
|
||||
### Setting Up Your Test
|
||||
|
||||
```elixir
|
||||
defmodule Pleroma.Uploaders.S3Test do
|
||||
use Pleroma.DataCase, async: true # Note: async: true is now possible!
|
||||
|
||||
alias Pleroma.Uploaders.S3
|
||||
alias Pleroma.Uploaders.S3.ExAwsMock
|
||||
alias Pleroma.UnstubbedConfigMock, as: ConfigMock
|
||||
|
||||
import Mox # Import Mox functions
|
||||
|
||||
# Note: verify_on_exit! is already called in DataCase setup
|
||||
# so you don't need to add it explicitly in your test module
|
||||
end
|
||||
```
|
||||
|
||||
### Setting Expectations with Mox
|
||||
|
||||
Mox uses an explicit expectation system. Here's how to use it:
|
||||
|
||||
```elixir
|
||||
# Basic expectation for a function call
|
||||
ExAwsMock
|
||||
|> expect(:request, fn _req -> {:ok, %{status_code: 200}} end)
|
||||
|
||||
# Expectation for multiple calls with same response
|
||||
ExAwsMock
|
||||
|> expect(:request, 3, fn _req -> {:ok, %{status_code: 200}} end)
|
||||
|
||||
# Expectation with specific arguments
|
||||
ExAwsMock
|
||||
|> expect(:request, fn %{bucket: "test_bucket"} -> {:ok, %{status_code: 200}} end)
|
||||
|
||||
# Complex configuration mocking
|
||||
ConfigMock
|
||||
|> expect(:get, fn key ->
|
||||
[
|
||||
{Pleroma.Upload, [uploader: Pleroma.Uploaders.S3, base_url: "https://s3.amazonaws.com"]},
|
||||
{Pleroma.Uploaders.S3, [bucket: "test_bucket"]}
|
||||
]
|
||||
|> get_in(key)
|
||||
end)
|
||||
```
|
||||
|
||||
### Understanding Mox Modes in Pleroma
|
||||
|
||||
Pleroma's DataCase automatically configures Mox differently based on whether your test is async or not:
|
||||
|
||||
1. **Async tests** (`async: true`):
|
||||
- Uses `Mox.set_mox_private()` - expectations are scoped to the current process
|
||||
- Stubs `Pleroma.CachexMock` with `Pleroma.NullCache`
|
||||
- Each test process has its own isolated mock expectations
|
||||
|
||||
2. **Non-async tests** (`async: false`):
|
||||
- Uses `Mox.set_mox_global()` - expectations are shared across processes
|
||||
- Stubs `Pleroma.CachexMock` with `Pleroma.CachexProxy`
|
||||
- Mock expectations can be set in one process and called from another
|
||||
|
||||
Choose the appropriate mode based on your test requirements. For most tests, async mode is preferred for better performance.
|
||||
|
||||
## Migrating from Mock/meck to Mox
|
||||
|
||||
Here's a step-by-step guide for migrating existing tests from Mock/meck to Mox:
|
||||
|
||||
### 1. Identify the Module to Mock
|
||||
|
||||
Look for `with_mock` or `test_with_mock` calls in your tests:
|
||||
|
||||
```elixir
|
||||
# Old approach with Mock
|
||||
with_mock ExAws, request: fn _ -> {:ok, :ok} end do
|
||||
assert S3.put_file(file_upload) == {:ok, {:file, "test_folder/image-tet.jpg"}}
|
||||
end
|
||||
```
|
||||
|
||||
### 2. Define a Behavior for the Module
|
||||
|
||||
Create a behavior that defines the functions you want to mock:
|
||||
|
||||
```elixir
|
||||
defmodule Pleroma.Uploaders.S3.ExAwsAPI do
|
||||
@callback request(op :: ExAws.Operation.t()) :: {:ok, ExAws.Operation.t()} | {:error, term()}
|
||||
end
|
||||
```
|
||||
|
||||
### 3. Update Your Implementation to Use a Configurable Dependency
|
||||
|
||||
```elixir
|
||||
# Old
|
||||
def put_file(%Pleroma.Upload{} = upload) do
|
||||
case ExAws.request(op) do
|
||||
# ...
|
||||
end
|
||||
end
|
||||
|
||||
# New
|
||||
@ex_aws_impl Application.compile_env(:pleroma, [__MODULE__, :ex_aws_impl], ExAws)
|
||||
|
||||
def put_file(%Pleroma.Upload{} = upload) do
|
||||
case @ex_aws_impl.request(op) do
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 4. Define the Mock in mocks.ex
|
||||
|
||||
```elixir
|
||||
Mox.defmock(Pleroma.Uploaders.S3.ExAwsMock, for: Pleroma.Uploaders.S3.ExAwsAPI)
|
||||
```
|
||||
|
||||
### 5. Configure the Test Environment
|
||||
|
||||
```elixir
|
||||
config :pleroma, Pleroma.Uploaders.S3, ex_aws_impl: Pleroma.Uploaders.S3.ExAwsMock
|
||||
```
|
||||
|
||||
### 6. Update Your Tests to Use Mox
|
||||
|
||||
```elixir
|
||||
# Old (with Mock)
|
||||
test_with_mock "save file", ExAws, request: fn _ -> {:ok, :ok} end do
|
||||
assert S3.put_file(file_upload) == {:ok, {:file, "test_folder/image-tet.jpg"}}
|
||||
assert_called(ExAws.request(:_))
|
||||
end
|
||||
|
||||
# New (with Mox)
|
||||
test "save file" do
|
||||
ExAwsMock
|
||||
|> expect(:request, fn _req -> {:ok, %{status_code: 200}} end)
|
||||
|
||||
assert S3.put_file(file_upload) == {:ok, {:file, "test_folder/image-tet.jpg"}}
|
||||
end
|
||||
```
|
||||
|
||||
### 7. Enable Async Testing
|
||||
|
||||
Now you can safely enable `async: true` in your test module:
|
||||
|
||||
```elixir
|
||||
use Pleroma.DataCase, async: true
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always define behaviors**: They serve as contracts and documentation
|
||||
2. **Keep mocks in a central location**: Use test/support/mocks.ex for all mock definitions
|
||||
3. **Use verify_on_exit!**: This is already set up in DataCase, ensuring all expected calls were made
|
||||
4. **Use specific expectations**: Be as specific as possible with your expectations
|
||||
5. **Enable async: true**: Take advantage of Mox's concurrent testing capability
|
||||
6. **Don't over-mock**: Only mock external dependencies that are difficult to test directly
|
||||
7. **Leverage existing stubs**: Use the default stubs provided by DataCase when possible
|
||||
8. **Avoid clear_config in async tests**: Use dependency injection and mocking instead
|
||||
|
||||
## Example: Complete Migration
|
||||
|
||||
For a complete example of migrating a test from Mock/meck to Mox, you can refer to commit `90a47ca050c5839e8b4dc3bac315dc436d49152d` in the Pleroma repository, which shows how the S3 uploader tests were migrated.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Migrating tests from Mock/meck to Mox provides significant benefits for the Pleroma test suite, including faster test execution through async testing, better isolation between tests, and more robust mocking through explicit contracts. By following this guide, you can successfully migrate existing tests and write new tests using Mox.
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
## Required dependencies
|
||||
|
||||
* PostgreSQL >=11.0
|
||||
* Elixir >=1.14.0 <1.17
|
||||
* Erlang OTP >=23.0.0 (supported: <27)
|
||||
* Elixir >=1.14.0 <1.19
|
||||
* Erlang OTP >=23.0.0 (supported: <28)
|
||||
* git
|
||||
* file / libmagic
|
||||
* gcc or clang
|
||||
|
|
|
|||
|
|
@ -1,25 +1,29 @@
|
|||
# Installing on OpenBSD
|
||||
|
||||
This guide describes the installation and configuration of pleroma (and the required software to run it) on a single OpenBSD 6.6 server.
|
||||
{! backend/installation/otp_vs_from_source_source.include !}
|
||||
|
||||
This guide describes the installation and configuration of Pleroma (and the required software to run it) on a single OpenBSD 7.7 server.
|
||||
|
||||
For any additional information regarding commands and configuration files mentioned here, check the man pages [online](https://man.openbsd.org/) or directly on your server with the man command.
|
||||
|
||||
{! backend/installation/generic_dependencies.include !}
|
||||
|
||||
## Installation
|
||||
|
||||
### Preparing the system
|
||||
#### Required software
|
||||
|
||||
To install them, run the following command (with doas or as root):
|
||||
To install required packages, run the following command:
|
||||
|
||||
```
|
||||
pkg_add elixir gmake git postgresql-server postgresql-contrib cmake ffmpeg ImageMagick libvips
|
||||
# pkg_add elixir gmake git postgresql-server postgresql-contrib cmake libmagic libvips
|
||||
```
|
||||
|
||||
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
||||
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd).
|
||||
Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
||||
|
||||
#### Optional software
|
||||
|
||||
Per [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md):
|
||||
* ImageMagick
|
||||
* ffmpeg
|
||||
* exiftool
|
||||
|
|
@ -27,234 +31,351 @@ Per [`docs/installation/optional/media_graphics_packages.md`](../installation/op
|
|||
To install the above:
|
||||
|
||||
```
|
||||
pkg_add ImageMagick ffmpeg p5-Image-ExifTool
|
||||
# pkg_add ImageMagick ffmpeg p5-Image-ExifTool
|
||||
```
|
||||
|
||||
#### Creating the pleroma user
|
||||
Pleroma will be run by a dedicated user, \_pleroma. Before creating it, insert the following lines in login.conf:
|
||||
For more information read [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md):
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
Switch to the \_postgresql user and initialize PostgreSQL:
|
||||
|
||||
```
|
||||
# su _postgresql
|
||||
$ initdb -D /var/postgresql/data -U postgres --encoding=utf-8 --lc-collate=C
|
||||
```
|
||||
|
||||
Running PostgreSQL in a different directory than `/var/postgresql/data` requires changing the `daemon_flags` variable in the `/etc/rc.d/postgresql` script.
|
||||
|
||||
For security reasons it is recommended to change the authentication method for `local` and `host` connections with the localhost address to `scram-sha-256`.<br>
|
||||
Do not forget to set a password for the `postgres` user before doing so, otherwise you won't be able to log back in unless you change the authentication method back to `trust`.<br>
|
||||
Changing the password hashing algorithm is not needed.<br>
|
||||
For more information [read](https://www.postgresql.org/docs/16/auth-pg-hba-conf.html) the PostgreSQL documentation.
|
||||
|
||||
Enable and start the postgresql service:
|
||||
|
||||
```
|
||||
# rcctl enable postgresql
|
||||
# rcctl start postgresql
|
||||
```
|
||||
|
||||
To check that PostgreSQL started properly and didn't fail right after starting, run `# rcctl check postgresql` which should return `postgresql(ok)`.
|
||||
|
||||
### Configuring Pleroma
|
||||
|
||||
Pleroma will be run by a dedicated \_pleroma user. Before creating it, insert the following lines in `/etc/login.conf`:
|
||||
|
||||
```
|
||||
pleroma:\
|
||||
:datasize-max=1536M:\
|
||||
:datasize-cur=1536M:\
|
||||
:openfiles-max=4096
|
||||
:datasize=1536M:\
|
||||
:openfiles-max=4096:\
|
||||
:openfiles-cur=1024:\
|
||||
:setenv=LC_ALL=en_US.UTF-8,VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS,MIX_ENV=prod:\
|
||||
:tc=daemon:
|
||||
```
|
||||
This creates a "pleroma" login class and sets higher values than default for datasize and openfiles (see [login.conf(5)](https://man.openbsd.org/login.conf)), this is required to avoid having pleroma crash some time after starting.
|
||||
|
||||
Create the \_pleroma user, assign it the pleroma login class and create its home directory (/home/\_pleroma/): `useradd -m -L pleroma _pleroma`
|
||||
This creates a "pleroma" login class and sets higher values than default for datasize and openfiles (see [login.conf(5)](https://man.openbsd.org/login.conf)), this is required to avoid having Pleroma crash some time after starting.
|
||||
|
||||
#### Clone pleroma's directory
|
||||
Enter a shell as the \_pleroma user. As root, run `su _pleroma -;cd`. Then clone the repository with `git clone -b stable https://git.pleroma.social/pleroma/pleroma.git`. Pleroma is now installed in /home/\_pleroma/pleroma/, it will be configured and started at the end of this guide.
|
||||
|
||||
#### PostgreSQL
|
||||
Start a shell as the \_postgresql user (as root run `su _postgresql -` then run the `initdb` command to initialize postgresql:
|
||||
You will need to specify pgdata directory to the default (/var/postgresql/data) with the `-D <path>` and set the user to postgres with the `-U <username>` flag. This can be done as follows:
|
||||
Create the \_pleroma user, assign it the pleroma login class and create its home directory (/home/\_pleroma/):
|
||||
|
||||
```
|
||||
initdb -D /var/postgresql/data -U postgres
|
||||
# useradd -m -L pleroma _pleroma
|
||||
```
|
||||
If you are not using the default directory, you will have to update the `datadir` variable in the /etc/rc.d/postgresql script.
|
||||
|
||||
When this is done, enable postgresql so that it starts on boot and start it. As root, run:
|
||||
Switch to the _pleroma user:
|
||||
|
||||
```
|
||||
rcctl enable postgresql
|
||||
rcctl start postgresql
|
||||
# su -l _pleroma
|
||||
```
|
||||
|
||||
Clone the Pleroma repository:
|
||||
|
||||
```
|
||||
$ git clone -b stable https://git.pleroma.social/pleroma/pleroma.git
|
||||
$ cd pleroma
|
||||
```
|
||||
|
||||
Pleroma is now installed in /home/\_pleroma/pleroma/. To configure it run:
|
||||
|
||||
```
|
||||
$ mix deps.get
|
||||
$ MIX_ENV=prod mix pleroma.instance gen # You will be asked a few questions here.
|
||||
$ cp config/generated_config.exs config/prod.secret.exs
|
||||
```
|
||||
|
||||
Note: Answer yes when asked to install Hex and rebar3. This step might take some time as Pleroma gets compiled first.
|
||||
|
||||
Create the Pleroma database:
|
||||
|
||||
```
|
||||
$ psql -U postgres -f config/setup_db.psql
|
||||
```
|
||||
|
||||
Apply database migrations:
|
||||
|
||||
```
|
||||
$ MIX_ENV=prod mix ecto.migrate
|
||||
```
|
||||
|
||||
Note: You will need to run this step again when updating your instance to a newer version with `git pull` or `git checkout tags/NEW_VERSION`.
|
||||
|
||||
As \_pleroma in /home/\_pleroma/pleroma, you can now run `MIX_ENV=prod mix phx.server` to start your instance.
|
||||
In another SSH session or a tmux window, check that it is working properly by running `ftp -MVo - http://127.0.0.1:4000/api/v1/instance`, you should get json output.
|
||||
Double-check that the *uri* value near the bottom is your instance's domain name and the instance *title* are correct.
|
||||
|
||||
### Configuring acme-client
|
||||
|
||||
acme-client is used to get SSL/TLS certificates from Let's Encrypt.
|
||||
Insert the following configuration in `/etc/acme-client.conf` and replace `example.tld` with your domain:
|
||||
|
||||
```
|
||||
#
|
||||
# $OpenBSD: acme-client.conf,v 1.5 2023/05/10 07:34:57 tb Exp $
|
||||
#
|
||||
|
||||
authority letsencrypt {
|
||||
api url "https://acme-v02.api.letsencrypt.org/directory"
|
||||
account key "/etc/acme/letsencrypt-privkey.pem"
|
||||
}
|
||||
|
||||
domain example.tld {
|
||||
# Adds alternative names to the certificate. Useful when serving media on another domain. Comma or space separated list.
|
||||
# alternative names { }
|
||||
|
||||
domain key "/etc/ssl/private/example.tld.key"
|
||||
domain certificate "/etc/ssl/example.tld_cert-only.crt"
|
||||
domain full chain certificate "/etc/ssl/example.tld.crt"
|
||||
sign with letsencrypt
|
||||
}
|
||||
```
|
||||
|
||||
Check the configuration:
|
||||
|
||||
```
|
||||
# acme-client -n
|
||||
```
|
||||
|
||||
### Configuring the Web server
|
||||
|
||||
Pleroma supports two Web servers:
|
||||
|
||||
* nginx (recommended for most users)
|
||||
* OpenBSD's httpd and relayd (ONLY for advanced users, media proxy cache is NOT supported and will NOT work properly)
|
||||
|
||||
#### nginx
|
||||
|
||||
Since nginx is not installed by default, install it by running:
|
||||
|
||||
```
|
||||
# pkg_add nginx
|
||||
```
|
||||
|
||||
Add the following to `/etc/nginx/nginx.conf`, within the `server {}` block listening on port 80 and change `server_name`, as follows:
|
||||
|
||||
```
|
||||
http {
|
||||
...
|
||||
|
||||
server {
|
||||
...
|
||||
server_name localhost; # Replace with your domain
|
||||
|
||||
location /.well-known/acme-challenge {
|
||||
rewrite ^/\.well-known/acme-challenge/(.*) /$1 break;
|
||||
root /var/www/acme;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Start the nginx service and acquire certificates:
|
||||
|
||||
```
|
||||
# rcctl start nginx
|
||||
# acme-client example.tld
|
||||
```
|
||||
|
||||
Add certificate auto-renewal by adding acme-client to `/etc/weekly.local`, replace `example.tld` with your domain:
|
||||
|
||||
```
|
||||
# echo "acme-client example.tld && rcctl reload nginx" >> /etc/weekly.local
|
||||
```
|
||||
|
||||
OpenBSD's default nginx configuration does not contain an include directive, which is typically used for multiple sites.
|
||||
Therefore, you will need to first create the required directory as follows:
|
||||
|
||||
```
|
||||
# mkdir /etc/nginx/sites-available
|
||||
# mkdir /etc/nginx/sites-enabled
|
||||
```
|
||||
|
||||
Next add the `include` directive to `/etc/nginx/nginx.conf`, within the `http {}` block, as follows:
|
||||
|
||||
```
|
||||
http {
|
||||
...
|
||||
|
||||
server {
|
||||
...
|
||||
}
|
||||
|
||||
include /etc/nginx/sites-enabled/*;
|
||||
}
|
||||
```
|
||||
|
||||
As root, copy `/home/_pleroma/pleroma/installation/pleroma.nginx` to `/etc/nginx/sites-available/pleroma.nginx`.
|
||||
|
||||
Edit default `/etc/nginx/sites-available/pleroma.nginx` settings and replace `example.tld` with your domain:
|
||||
|
||||
* Uncomment the location block for `~ /\.well-known/acme-challenge` in the server block listening on port 80
|
||||
- add `rewrite ^/\.well-known/acme-challenge/(.*) /$1 break;` above the `root` location
|
||||
- change the `root` location to `/var/www/acme;`
|
||||
* Change `ssl_trusted_certificate` to `/etc/ssl/example.tld_cert-only.crt`
|
||||
* Change `ssl_certificate` to `/etc/ssl/example.tld.crt`
|
||||
* Change `ssl_certificate_key` to `/etc/ssl/private/example.tld.key`
|
||||
|
||||
Remove the following `location {}` block from `/etc/nginx/nginx.conf`, that was previously added for acquiring certificates and change `server_name` back to `localhost`:
|
||||
|
||||
```
|
||||
http {
|
||||
...
|
||||
|
||||
server {
|
||||
...
|
||||
server_name example.tld; # Change back to localhost
|
||||
|
||||
# Delete this block
|
||||
location /.well-known/acme-challenge {
|
||||
rewrite ^/\.well-known/acme-challenge/(.*) /$1 break;
|
||||
root /var/www/acme;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Symlink the Pleroma configuration to the enabled sites:
|
||||
|
||||
```
|
||||
# ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled
|
||||
```
|
||||
|
||||
Check nginx configuration syntax by running:
|
||||
|
||||
```
|
||||
# nginx -t
|
||||
```
|
||||
|
||||
Note: If the above command complains about a `conflicting server name`, check again that the `location {}` block for acquiring certificates has been removed from `/etc/nginx/nginx.conf` and that the `server_name` has been reverted back to `localhost`.
|
||||
After doing so run `# nginx -t` again.
|
||||
|
||||
If the configuration is correct, you can now enable and reload the nginx service:
|
||||
|
||||
```
|
||||
# rcctl enable nginx
|
||||
# rcctl reload nginx
|
||||
```
|
||||
To check that it started properly and didn't fail right after starting, you can run `ps aux | grep postgres`, there should be multiple lines of output.
|
||||
|
||||
#### httpd
|
||||
httpd will have three functions:
|
||||
|
||||
***Skip this section when using nginx***
|
||||
|
||||
httpd will have two functions:
|
||||
|
||||
* redirect requests trying to reach the instance over http to the https URL
|
||||
* serve a robots.txt file
|
||||
* get Let's Encrypt certificates, with acme-client
|
||||
|
||||
Insert the following config in httpd.conf:
|
||||
As root, copy `/home/_pleroma/pleroma/installation/openbsd/httpd.conf` to `/etc/httpd.conf`, or modify the existing one.
|
||||
|
||||
Edit `/etc/httpd.conf` settings and change:
|
||||
|
||||
* `<ipaddr>` with your instance's IPv4 address
|
||||
* All occurrences of `example.tld` with your instance's domain name
|
||||
* When using IPv6 also change:
|
||||
- Uncomment the `ext_inet6="<ip6addr>"` line near the beginning of the file and change `<ip6addr` to your instance's IPv6 address
|
||||
- Uncomment the line starting with `listen on $ext_inet6` in the `server` block
|
||||
|
||||
Check the configuration by running:
|
||||
```
|
||||
# $OpenBSD: httpd.conf,v 1.17 2017/04/16 08:50:49 ajacoutot Exp $
|
||||
|
||||
ext_inet="<IPv4 address>"
|
||||
ext_inet6="<IPv6 address>"
|
||||
|
||||
server "default" {
|
||||
listen on $ext_inet port 80 # Comment to disable listening on IPv4
|
||||
listen on $ext_inet6 port 80 # Comment to disable listening on IPv6
|
||||
listen on 127.0.0.1 port 80 # Do NOT comment this line
|
||||
|
||||
log syslog
|
||||
directory no index
|
||||
|
||||
location "/.well-known/acme-challenge/*" {
|
||||
root "/acme"
|
||||
request strip 2
|
||||
}
|
||||
|
||||
location "/robots.txt" { root "/htdocs/local/" }
|
||||
location "/*" { block return 302 "https://$HTTP_HOST$REQUEST_URI" }
|
||||
}
|
||||
|
||||
types {
|
||||
}
|
||||
```
|
||||
Do not forget to change *<IPv4/6 address\>* to your server's address(es). If httpd should only listen on one protocol family, comment one of the two first *listen* options.
|
||||
|
||||
Create the /var/www/htdocs/local/ folder and write the content of your robots.txt in /var/www/htdocs/local/robots.txt.
|
||||
Check the configuration with `httpd -n`, if it is OK enable and start httpd (as root):
|
||||
```
|
||||
rcctl enable httpd
|
||||
rcctl start httpd
|
||||
# httpd -n
|
||||
```
|
||||
|
||||
#### acme-client
|
||||
acme-client is used to get SSL/TLS certificates from Let's Encrypt.
|
||||
Insert the following configuration in /etc/acme-client.conf:
|
||||
```
|
||||
#
|
||||
# $OpenBSD: acme-client.conf,v 1.4 2017/03/22 11:14:14 benno Exp $
|
||||
#
|
||||
If the configuration is correct, enable and start the `httpd` service:
|
||||
|
||||
authority letsencrypt-<domain name> {
|
||||
#agreement url "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"
|
||||
api url "https://acme-v02.api.letsencrypt.org/directory"
|
||||
account key "/etc/acme/letsencrypt-privkey-<domain name>.pem"
|
||||
}
|
||||
```
|
||||
# rcctl enable httpd
|
||||
# rcctl start httpd
|
||||
```
|
||||
|
||||
domain <domain name> {
|
||||
domain key "/etc/ssl/private/<domain name>.key"
|
||||
domain certificate "/etc/ssl/<domain name>.crt"
|
||||
domain full chain certificate "/etc/ssl/<domain name>.fullchain.pem"
|
||||
sign with letsencrypt-<domain name>
|
||||
challengedir "/var/www/acme/"
|
||||
}
|
||||
```
|
||||
Replace *<domain name\>* by the domain name you'll use for your instance. As root, run `acme-client -n` to check the config, then `acme-client -ADv <domain name>` to create account and domain keys, and request a certificate for the first time.
|
||||
Make acme-client run everyday by adding it in /etc/daily.local. As root, run the following command: `echo "acme-client <domain name>" >> /etc/daily.local`.
|
||||
Acquire certificate:
|
||||
|
||||
Relayd will look for certificates and keys based on the address it listens on (see next part), the easiest way to make them available to relayd is to create a link, as root run:
|
||||
```
|
||||
ln -s /etc/ssl/<domain name>.fullchain.pem /etc/ssl/<IP address>.crt
|
||||
ln -s /etc/ssl/private/<domain name>.key /etc/ssl/private/<IP address>.key
|
||||
# acme-client example.tld
|
||||
```
|
||||
This will have to be done for each IPv4 and IPv6 address relayd listens on.
|
||||
|
||||
#### relayd
|
||||
|
||||
***Skip this section when using nginx***
|
||||
|
||||
relayd will be used as the reverse proxy sitting in front of pleroma.
|
||||
Insert the following configuration in /etc/relayd.conf:
|
||||
|
||||
As root, copy `/home/_pleroma/pleroma/installation/openbsd/relayd.conf` to `/etc/relayd.conf`, or modify the existing one.
|
||||
|
||||
Edit `/etc/relayd.conf` settings and change:
|
||||
|
||||
* `<ipaddr>` with your instance's IPv4 address
|
||||
* All occurrences of `example.tld` with your instance's domain name
|
||||
* When using IPv6 also change:
|
||||
- Uncomment the `ext_inet6="<ip6addr>"` line near the beginning of the file and change `<ip6addr>` to your instance's IPv6 address
|
||||
- Uncomment the line starting with `listen on $ext_inet6` in the `relay wwwtls` block
|
||||
|
||||
Check the configuration by running:
|
||||
```
|
||||
# $OpenBSD: relayd.conf,v 1.4 2018/03/23 09:55:06 claudio Exp $
|
||||
|
||||
ext_inet="<IPv4 address>"
|
||||
ext_inet6="<IPv6 address>"
|
||||
|
||||
table <pleroma_server> { 127.0.0.1 }
|
||||
table <httpd_server> { 127.0.0.1 }
|
||||
|
||||
http protocol plerup { # Protocol for upstream pleroma server
|
||||
#tcp { nodelay, sack, socket buffer 65536, backlog 128 } # Uncomment and adjust as you see fit
|
||||
tls ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305"
|
||||
tls ecdhe secp384r1
|
||||
|
||||
# Forward some paths to the local server (as pleroma won't respond to them as you might want)
|
||||
pass request quick path "/robots.txt" forward to <httpd_server>
|
||||
|
||||
# Append a bunch of headers
|
||||
match request header append "X-Forwarded-For" value "$REMOTE_ADDR" # This two header and the next one are not strictly required by pleroma but adding them won't hurt
|
||||
match request header append "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT"
|
||||
|
||||
match response header append "X-XSS-Protection" value "1; mode=block"
|
||||
match response header append "X-Permitted-Cross-Domain-Policies" value "none"
|
||||
match response header append "X-Frame-Options" value "DENY"
|
||||
match response header append "X-Content-Type-Options" value "nosniff"
|
||||
match response header append "Referrer-Policy" value "same-origin"
|
||||
match response header append "X-Download-Options" value "noopen"
|
||||
match response header append "Content-Security-Policy" value "default-src 'none'; base-uri 'self'; form-action 'self'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://CHANGEME.tld; upgrade-insecure-requests;" # Modify "CHANGEME.tld" and set your instance's domain here
|
||||
match request header append "Connection" value "upgrade"
|
||||
#match response header append "Strict-Transport-Security" value "max-age=31536000; includeSubDomains" # Uncomment this only after you get HTTPS working.
|
||||
|
||||
# If you do not want remote frontends to be able to access your Pleroma backend server, comment these lines
|
||||
match response header append "Access-Control-Allow-Origin" value "*"
|
||||
match response header append "Access-Control-Allow-Methods" value "POST, PUT, DELETE, GET, PATCH, OPTIONS"
|
||||
match response header append "Access-Control-Allow-Headers" value "Authorization, Content-Type, Idempotency-Key"
|
||||
match response header append "Access-Control-Expose-Headers" value "Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id"
|
||||
# Stop commenting lines here
|
||||
}
|
||||
|
||||
relay wwwtls {
|
||||
listen on $ext_inet port https tls # Comment to disable listening on IPv4
|
||||
listen on $ext_inet6 port https tls # Comment to disable listening on IPv6
|
||||
|
||||
protocol plerup
|
||||
|
||||
forward to <pleroma_server> port 4000 check http "/" code 200
|
||||
forward to <httpd_server> port 80 check http "/robots.txt" code 200
|
||||
}
|
||||
```
|
||||
Again, change *<IPv4/6 address\>* to your server's address(es) and comment one of the two *listen* options if needed. Also change *wss://CHANGEME.tld* to *wss://<your instance's domain name\>*.
|
||||
Check the configuration with `relayd -n`, if it is OK enable and start relayd (as root):
|
||||
```
|
||||
rcctl enable relayd
|
||||
rcctl start relayd
|
||||
# relayd -n
|
||||
```
|
||||
|
||||
##### (Strongly recommended) serve media on another domain
|
||||
If the configuration is correct, enable and start the `relayd` service:
|
||||
|
||||
```
|
||||
# rcctl enable relayd
|
||||
# rcctl start relayd
|
||||
```
|
||||
|
||||
Add certificate auto-renewal by adding acme-client to `/etc/weekly.local`, replace `example.tld` with your domain:
|
||||
|
||||
```
|
||||
# echo "acme-client example.tld && rcctl reload relayd" >> /etc/weekly.local
|
||||
```
|
||||
|
||||
#### (Strongly recommended) serve media on another domain
|
||||
|
||||
Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors.
|
||||
|
||||
#### pf
|
||||
Enabling and configuring pf is highly recommended.
|
||||
In /etc/pf.conf, insert the following configuration:
|
||||
### Starting pleroma at boot
|
||||
|
||||
Copy the startup script and make sure it's executable:
|
||||
|
||||
```
|
||||
# Macros
|
||||
if="<network interface>"
|
||||
authorized_ssh_clients="any"
|
||||
|
||||
# Skip traffic on loopback interface
|
||||
set skip on lo
|
||||
|
||||
# Default behavior
|
||||
set block-policy drop
|
||||
block in log all
|
||||
pass out quick
|
||||
|
||||
# Security features
|
||||
match in all scrub (no-df random-id)
|
||||
block in log from urpf-failed
|
||||
|
||||
# Rules
|
||||
pass in quick on $if inet proto icmp to ($if) icmp-type { echoreq unreach paramprob trace } # ICMP
|
||||
pass in quick on $if inet6 proto icmp6 to ($if) icmp6-type { echoreq unreach paramprob timex toobig } # ICMPv6
|
||||
pass in quick on $if proto tcp to ($if) port { http https } # relayd/httpd
|
||||
pass in quick on $if proto tcp from $authorized_ssh_clients to ($if) port ssh
|
||||
```
|
||||
Replace *<network interface\>* by your server's network interface name (which you can get with ifconfig). Consider replacing the content of the authorized\_ssh\_clients macro by, for example, your home IP address, to avoid SSH connection attempts from bots.
|
||||
|
||||
Check pf's configuration by running `pfctl -nf /etc/pf.conf`, load it with `pfctl -f /etc/pf.conf` and enable pf at boot with `rcctl enable pf`.
|
||||
|
||||
#### Configure and start pleroma
|
||||
Enter a shell as \_pleroma (as root `su _pleroma -`) and enter pleroma's installation directory (`cd ~/pleroma/`).
|
||||
|
||||
Then follow the main installation guide:
|
||||
|
||||
* run `mix deps.get`
|
||||
* run `MIX_ENV=prod mix pleroma.instance gen` and enter your instance's information when asked
|
||||
* copy config/generated\_config.exs to config/prod.secret.exs. The default values should be sufficient but you should edit it and check that everything seems OK.
|
||||
* exit your current shell back to a root one and run `psql -U postgres -f /home/_pleroma/pleroma/config/setup_db.psql` to setup the database.
|
||||
* return to a \_pleroma shell into pleroma's installation directory (`su _pleroma -;cd ~/pleroma`) and run `MIX_ENV=prod mix ecto.migrate`
|
||||
|
||||
As \_pleroma in /home/\_pleroma/pleroma, you can now run `LC_ALL=en_US.UTF-8 MIX_ENV=prod mix phx.server` to start your instance.
|
||||
In another SSH session/tmux window, check that it is working properly by running `ftp -MVo - http://127.0.0.1:4000/api/v1/instance`, you should get json output. Double-check that *uri*'s value is your instance's domain name.
|
||||
|
||||
##### Starting pleroma at boot
|
||||
An rc script to automatically start pleroma at boot hasn't been written yet, it can be run in a tmux session (tmux is in base).
|
||||
|
||||
|
||||
#### Create administrative user
|
||||
|
||||
If your instance is up and running, you can create your first user with administrative rights with the following command as the \_pleroma user.
|
||||
```
|
||||
LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
|
||||
# cp /home/_pleroma/pleroma/installation/openbsd/rc.d/pleroma /etc/rc.d/pleroma
|
||||
# chmod 555 /etc/rc.d/pleroma
|
||||
```
|
||||
|
||||
#### Further reading
|
||||
Enable and start the pleroma service:
|
||||
|
||||
```
|
||||
# rcctl enable pleroma
|
||||
# rcctl start pleroma
|
||||
```
|
||||
|
||||
### Create administrative user
|
||||
|
||||
If your instance is up and running, you can create your first user with administrative rights with the following commands as the \_pleroma user:
|
||||
|
||||
```
|
||||
$ cd pleroma
|
||||
$ MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
|
||||
```
|
||||
|
||||
### Further reading
|
||||
|
||||
{! backend/installation/further_reading.include !}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Note: This article is potentially outdated because at this time we may not have
|
|||
|
||||
Tarvitset:
|
||||
* Oman domainin
|
||||
* OpenBSD 6.3 -serverin
|
||||
* OpenBSD 7.5 -serverin
|
||||
* Auttavan ymmärryksen unix-järjestelmistä
|
||||
|
||||
Komennot, joiden edessä on '#', tulee ajaa käyttäjänä `root`. Tämä on
|
||||
|
|
@ -18,7 +18,7 @@ Matrix-kanava #pleroma:libera.chat ovat hyviä paikkoja löytää apua
|
|||
|
||||
Asenna tarvittava ohjelmisto:
|
||||
|
||||
`# pkg_add git elixir gmake postgresql-server-10.3 postgresql-contrib-10.3 cmake ffmpeg ImageMagick libvips`
|
||||
`# pkg_add git elixir gmake postgresql-server postgresql-contrib cmake libmagic libvips`
|
||||
|
||||
#### Optional software
|
||||
|
||||
|
|
|
|||
|
|
@ -24,4 +24,6 @@ command=/usr/local/bin/elixir
|
|||
command_args="--erl \"-detached\" -S /usr/local/bin/mix phx.server"
|
||||
procname="*beam.smp"
|
||||
|
||||
PATH="${PATH}:/usr/local/sbin:/usr/local/bin"
|
||||
|
||||
run_rc_command "$1"
|
||||
|
|
|
|||
|
|
@ -2,20 +2,21 @@
|
|||
# Default httpd.conf file for Pleroma on OpenBSD
|
||||
# Simple installation instructions
|
||||
# 1. Place file in /etc
|
||||
# 2. Replace <IPv4 address> with your public IP address
|
||||
# 3. If using IPv6, uncomment IPv6 lines and replace <IPv6 address> with your public IPv6 address
|
||||
# 4. Check file using 'doas httpd -n'
|
||||
# 5. Enable and start httpd:
|
||||
# 2. Replace <ipaddr> with your public IP address
|
||||
# 3. If using IPv6, uncomment IPv6 lines and replace <ip6addr> with your public IPv6 address
|
||||
# 4. Replace all occurences of example.tld with your instance's domain name.
|
||||
# 5. Check file using 'doas httpd -n'
|
||||
# 6. Enable and start httpd:
|
||||
# # doas rcctl enable httpd
|
||||
# # doas rcctl start httpd
|
||||
#
|
||||
|
||||
ext_inet="<IPv4 address>"
|
||||
#ext_inet6="<IPv6 address>"
|
||||
ext_inet="<ipaddr>"
|
||||
#ext_inet6="<ip6addr>"
|
||||
|
||||
server "default" {
|
||||
server "example.tld" {
|
||||
listen on $ext_inet port 80 # Comment to disable listening on IPv4
|
||||
# listen on $ext_inet6 port 80 # Comment to disable listening on IPv6
|
||||
#listen on $ext_inet6 port 80 # Comment to disable listening on IPv6
|
||||
listen on 127.0.0.1 port 80 # Do NOT comment this line
|
||||
|
||||
log syslog
|
||||
|
|
@ -26,10 +27,18 @@ server "default" {
|
|||
request strip 2
|
||||
}
|
||||
|
||||
location "/robots.txt" { root "/htdocs/local/" }
|
||||
location "/*" { block return 302 "https://$HTTP_HOST$REQUEST_URI" }
|
||||
location "/*" { block return 301 "https://$HTTP_HOST$REQUEST_URI" }
|
||||
}
|
||||
|
||||
# Example of serving a basic static website besides Pleroma using the example configuration in relayd
|
||||
#server "site.example.tld" {
|
||||
# listen on 127.0.0.1 port 8080
|
||||
#
|
||||
# location "/*" {
|
||||
# root "/website"
|
||||
# }
|
||||
#}
|
||||
|
||||
types {
|
||||
include "/usr/share/misc/mime.types"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@
|
|||
#
|
||||
# Simple installation instructions:
|
||||
# 1. Install Pleroma per wiki instructions
|
||||
# 2. Place this pleromad file in /etc/rc.d
|
||||
# 2. Place this pleroma file in /etc/rc.d
|
||||
# 3. Enable and start Pleroma
|
||||
# # doas rcctl enable pleromad
|
||||
# # doas rcctl start pleromad
|
||||
# # doas rcctl enable pleroma
|
||||
# # doas rcctl start pleroma
|
||||
#
|
||||
|
||||
daemon="/usr/local/bin/elixir"
|
||||
daemon_flags="--detached -S /usr/local/bin/mix phx.server"
|
||||
daemon_flags="--erl \"-detached\" -S /usr/local/bin/mix phx.server"
|
||||
daemon_user="_pleroma"
|
||||
daemon_execdir="/home/_pleroma/pleroma"
|
||||
|
||||
. /etc/rc.d/rc.subr
|
||||
|
||||
|
|
@ -23,10 +24,6 @@ rc_check() {
|
|||
pgrep -q -U _pleroma -f "phx.server"
|
||||
}
|
||||
|
||||
rc_start() {
|
||||
${rcexec} "cd pleroma; ${daemon} ${daemon_flags}"
|
||||
}
|
||||
|
||||
rc_stop() {
|
||||
pkill -q -U _pleroma -f "phx.server"
|
||||
}
|
||||
|
|
@ -3,9 +3,10 @@
|
|||
# Simple installation instructions:
|
||||
# 1. Place in /etc
|
||||
# 2. Replace <ipaddr> with your public IPv4 address
|
||||
# 3. If using IPv6i, uncomment IPv6 lines and replace <ip6addr> with your public IPv6 address
|
||||
# 4. Check file using 'doas relayd -n'
|
||||
# 5. Reload/start relayd
|
||||
# 3. If using IPv6, uncomment IPv6 lines and replace <ip6addr> with your public IPv6 address
|
||||
# 4. Replace all occurrences of example.tld with your instance's domain
|
||||
# 5. Check file using 'doas relayd -n'
|
||||
# 6. Reload/start relayd
|
||||
# # doas rcctl enable relayd
|
||||
# # doas rcctl start relayd
|
||||
#
|
||||
|
|
@ -14,31 +15,54 @@ ext_inet="<ipaddr>"
|
|||
#ext_inet6="<ip6addr>"
|
||||
|
||||
table <pleroma_server> { 127.0.0.1 }
|
||||
table <httpd_server> { 127.0.0.1 }
|
||||
|
||||
http protocol plerup { # Protocol for upstream pleroma server
|
||||
# Uncomment when you want to serve other services than Pleroma.
|
||||
# In this example tables are used only as way to differentiate between Pleroma and other services.
|
||||
# Feel free to rename "httpd_server" everywhere to fit your setup.
|
||||
#table <httpd_server> { 127.0.0.1 }
|
||||
|
||||
http protocol pleroma { # Protocol for upstream Pleroma server
|
||||
#tcp { nodelay, sack, socket buffer 65536, backlog 128 } # Uncomment and adjust as you see fit
|
||||
tls ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA0-POLY1305"
|
||||
tls ecdhe secp384r1
|
||||
tls ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"
|
||||
tls ecdhe "X25519,P-256,P-384,secp521r1" # relayd default+secp521r1
|
||||
|
||||
# Forward some paths to the local server (as pleroma won't respond to them as you might want)
|
||||
pass request quick path "/robots.txt" forward to <httpd_server>
|
||||
return error
|
||||
|
||||
# Append a bunch of headers
|
||||
match request header append "X-Forwarded-For" value "$REMOTE_ADDR" # This two header and the next one are not strictl required by pleroma but adding them won't hurt
|
||||
match request header append "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT"
|
||||
# When serving multiple services with different certificates, specify multiple "tls keypair" keywords
|
||||
# and add forwards to those services before the block keyword near the bottom of the protocol and relay configurations.
|
||||
# The string in quotes must match the fullchain certificate file created by acme-client without the extension.
|
||||
# For example:
|
||||
# tls keypair "pleroma.example.tld"
|
||||
# tls keypair "example.tld"
|
||||
tls keypair "example.tld"
|
||||
|
||||
match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
|
||||
match request header append "Connection" value "upgrade"
|
||||
|
||||
# When hosting Pleroma on a subdomain, replace example.tld accordingly (not the base domain).
|
||||
# From the above example, "example.tld" should be replaced with "pleroma.example.tld" instead.
|
||||
pass request quick header "Host" value "example.tld" forward to <pleroma_server>
|
||||
|
||||
# Uncomment when serving media uploads on a different (sub)domain.
|
||||
# Keep media proxy disabled, as it will NOT work under relayd/httpd. If you want to also setup media proxy, use nginx instead.
|
||||
#pass request quick header "Host" value "media.example.tld" forward to <pleroma_server>
|
||||
|
||||
# When serving multiple services, add the forwards here.
|
||||
# Example:
|
||||
#pass request quick header "Host" value "example.tld" forward to <httpd_server>
|
||||
|
||||
block
|
||||
}
|
||||
|
||||
relay wwwtls {
|
||||
listen on $ext_inet port https tls # Comment to disable listening on IPv4
|
||||
# listen on $ext_inet6 port https tls # Comment to disable listening on IPv6
|
||||
#listen on $ext_inet6 port https tls # Comment to disable listening on IPv6
|
||||
|
||||
protocol plerup
|
||||
protocol pleroma
|
||||
|
||||
forward to <pleroma_server> port 4000 check http "/" code 200
|
||||
forward to <httpd_server> port 80 check http "/robots.txt" code 200
|
||||
forward to <pleroma_server> port 4000 check tcp timeout 500 # Adjust timeout accordingly when relayd returns 502 while Pleroma is running without problems.
|
||||
|
||||
# When serving multiple services, add the forwards here.
|
||||
# Example:
|
||||
#forward to <httpd_server> port 8080
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
[config_dir, psql_dir, static_dir, uploads_dir]
|
||||
|> Enum.reject(&File.exists?/1)
|
||||
|> Enum.each(fn dir ->
|
||||
File.mkdir_p!(dir)
|
||||
Pleroma.Backports.mkdir_p!(dir)
|
||||
File.chmod!(dir, 0o700)
|
||||
end)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ defmodule Mix.Tasks.Pleroma.RobotsTxt do
|
|||
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
|
||||
|
||||
if !File.exists?(static_dir) do
|
||||
File.mkdir_p!(static_dir)
|
||||
Pleroma.Backports.mkdir_p!(static_dir)
|
||||
end
|
||||
|
||||
robots_txt_path = Path.join(static_dir, "robots.txt")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ defmodule Mix.Tasks.Pleroma.TestRunner do
|
|||
use Mix.Task
|
||||
|
||||
def run(args \\ []) do
|
||||
case System.cmd("mix", ["test"] ++ args, into: IO.stream(:stdio, :line)) do
|
||||
case System.cmd("mix", ["test", "--warnings-as-errors"] ++ args,
|
||||
into: IO.stream(:stdio, :line)
|
||||
) do
|
||||
{_, 0} ->
|
||||
:ok
|
||||
|
||||
|
|
|
|||
|
|
@ -43,9 +43,6 @@ defmodule Pleroma.Application do
|
|||
# every time the application is restarted, so we disable module
|
||||
# conflicts at runtime
|
||||
Code.compiler_options(ignore_module_conflict: true)
|
||||
# Disable warnings_as_errors at runtime, it breaks Phoenix live reload
|
||||
# due to protocol consolidation warnings
|
||||
Code.compiler_options(warnings_as_errors: false)
|
||||
Pleroma.Telemetry.Logger.attach()
|
||||
Config.Holder.save_default()
|
||||
Pleroma.HTML.compile_scrubbers()
|
||||
|
|
@ -56,7 +53,10 @@ defmodule Pleroma.Application do
|
|||
Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled()
|
||||
end
|
||||
|
||||
Pleroma.ApplicationRequirements.verify!()
|
||||
if Config.get(:env) != :test do
|
||||
Pleroma.ApplicationRequirements.verify!()
|
||||
end
|
||||
|
||||
load_custom_modules()
|
||||
Pleroma.Docs.JSON.compile()
|
||||
limiters_setup()
|
||||
|
|
@ -68,26 +68,11 @@ defmodule Pleroma.Application do
|
|||
Finch.start_link(name: MyFinch)
|
||||
end
|
||||
|
||||
if adapter == Tesla.Adapter.Gun do
|
||||
if version = Pleroma.OTPVersion.version() do
|
||||
[major, minor] =
|
||||
version
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.take(2)
|
||||
|
||||
if (major == 22 and minor < 2) or major < 22 do
|
||||
raise "
|
||||
!!!OTP VERSION WARNING!!!
|
||||
You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. Please update your Erlang/OTP to at least 22.2.
|
||||
"
|
||||
end
|
||||
else
|
||||
raise "
|
||||
!!!OTP VERSION WARNING!!!
|
||||
To support correct handling of unordered certificates chains - OTP version must be > 22.2.
|
||||
"
|
||||
end
|
||||
# Disable warnings_as_errors at runtime, it breaks Phoenix live reload
|
||||
# due to protocol consolidation warnings
|
||||
# :warnings_as_errors is deprecated via Code.compiler_options/2 since 1.18
|
||||
if Version.compare(System.version(), "1.18.0") == :lt do
|
||||
Code.compiler_options(warnings_as_errors: false)
|
||||
end
|
||||
|
||||
# Define workers and child supervisors to be supervised
|
||||
|
|
@ -169,7 +154,8 @@ defmodule Pleroma.Application do
|
|||
limit: 500_000
|
||||
),
|
||||
build_cachex("rel_me", limit: 2500),
|
||||
build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5000)
|
||||
build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5_000),
|
||||
build_cachex("translations", default_ttl: :timer.hours(24), limit: 5_000)
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,40 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
false
|
||||
end
|
||||
|
||||
if Enum.all?([preview_proxy_commands_status | filter_commands_statuses], & &1) do
|
||||
language_detector_commands_status =
|
||||
if Pleroma.Language.LanguageDetector.missing_dependencies() == [] do
|
||||
true
|
||||
else
|
||||
Logger.error(
|
||||
"The following dependencies required by the currently enabled " <>
|
||||
"language detection provider are not installed: " <>
|
||||
inspect(Pleroma.Language.LanguageDetector.missing_dependencies())
|
||||
)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
translation_commands_status =
|
||||
if Pleroma.Language.Translation.missing_dependencies() == [] do
|
||||
true
|
||||
else
|
||||
Logger.error(
|
||||
"The following dependencies required by the currently enabled " <>
|
||||
"translation provider are not installed: " <>
|
||||
inspect(Pleroma.Language.Translation.missing_dependencies())
|
||||
)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
if Enum.all?(
|
||||
[
|
||||
preview_proxy_commands_status,
|
||||
language_detector_commands_status,
|
||||
translation_commands_status | filter_commands_statuses
|
||||
],
|
||||
& &1
|
||||
) do
|
||||
:ok
|
||||
else
|
||||
{:error,
|
||||
|
|
|
|||
72
lib/pleroma/backports.ex
Normal file
72
lib/pleroma/backports.ex
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Copyright 2012 Plataformatec
|
||||
# Copyright 2021 The Elixir Team
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
defmodule Pleroma.Backports do
|
||||
import File, only: [dir?: 1]
|
||||
|
||||
# <https://github.com/elixir-lang/elixir/pull/14242>
|
||||
# To be removed when we require Elixir 1.19
|
||||
@doc """
|
||||
Tries to create the directory `path`.
|
||||
|
||||
Missing parent directories are created. Returns `:ok` if successful, or
|
||||
`{:error, reason}` if an error occurs.
|
||||
|
||||
Typical error reasons are:
|
||||
|
||||
* `:eacces` - missing search or write permissions for the parent
|
||||
directories of `path`
|
||||
* `:enospc` - there is no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory
|
||||
|
||||
"""
|
||||
@spec mkdir_p(Path.t()) :: :ok | {:error, File.posix() | :badarg}
|
||||
def mkdir_p(path) do
|
||||
do_mkdir_p(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
defp do_mkdir_p("/") do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp do_mkdir_p(path) do
|
||||
parent = Path.dirname(path)
|
||||
|
||||
if parent == path do
|
||||
:ok
|
||||
else
|
||||
case do_mkdir_p(parent) do
|
||||
:ok ->
|
||||
case :file.make_dir(path) do
|
||||
{:error, :eexist} ->
|
||||
if dir?(path), do: :ok, else: {:error, :enotdir}
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
|
||||
e ->
|
||||
e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `mkdir_p/1`, but raises a `File.Error` exception in case of failure.
|
||||
Otherwise `:ok`.
|
||||
"""
|
||||
@spec mkdir_p!(Path.t()) :: :ok
|
||||
def mkdir_p!(path) do
|
||||
case mkdir_p(path) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.Error,
|
||||
reason: reason,
|
||||
action: "make directory (with -p)",
|
||||
path: IO.chardata_to_string(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -302,7 +302,7 @@ defmodule Pleroma.ConfigDB do
|
|||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => entity}) do
|
||||
Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1)))
|
||||
Enum.reduce(entity, {}, &Tuple.insert_at(&2, tuple_size(&2), to_elixir_types(&1)))
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_map(entity) do
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ defmodule Pleroma.Constants do
|
|||
"Add",
|
||||
"Remove",
|
||||
"Like",
|
||||
"Dislike",
|
||||
"Announce",
|
||||
"Undo",
|
||||
"Flag",
|
||||
|
|
@ -115,6 +116,7 @@ defmodule Pleroma.Constants do
|
|||
"Flag",
|
||||
"Follow",
|
||||
"Like",
|
||||
"Dislike",
|
||||
"EmojiReact",
|
||||
"Announce"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -488,7 +488,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
with true <- String.contains?(file_path, "/"),
|
||||
path <- Path.dirname(file_path),
|
||||
false <- File.exists?(path) do
|
||||
File.mkdir_p!(path)
|
||||
Pleroma.Backports.mkdir_p!(path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -536,7 +536,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
emoji_path = emoji_path()
|
||||
# Create the directory first if it does not exist. This is probably the first request made
|
||||
# with the API so it should be sufficient
|
||||
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
|
||||
with {:create_dir, :ok} <- {:create_dir, Pleroma.Backports.mkdir_p(emoji_path)},
|
||||
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
|
||||
{:ok, Enum.sort(results)}
|
||||
else
|
||||
|
|
@ -561,7 +561,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
end
|
||||
|
||||
defp unzip(archive, pack_info, remote_pack, local_pack) do
|
||||
with :ok <- File.mkdir_p!(local_pack.path) do
|
||||
with :ok <- Pleroma.Backports.mkdir_p!(local_pack.path) do
|
||||
files = Enum.map(remote_pack["files"], fn {_, path} -> path end)
|
||||
# Fallback cannot contain a pack.json file
|
||||
files = if pack_info[:fallback], do: files, else: ["pack.json" | files]
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ defmodule Pleroma.Frontend do
|
|||
|
||||
def unzip(zip, dest) do
|
||||
File.rm_rf!(dest)
|
||||
File.mkdir_p!(dest)
|
||||
Pleroma.Backports.mkdir_p!(dest)
|
||||
|
||||
case Pleroma.SafeZip.unzip_data(zip, dest) do
|
||||
{:ok, _} -> :ok
|
||||
|
|
@ -90,7 +90,7 @@ defmodule Pleroma.Frontend do
|
|||
defp install_frontend(frontend_info, source, dest) do
|
||||
from = frontend_info["build_dir"] || "dist"
|
||||
File.rm_rf!(dest)
|
||||
File.mkdir_p!(dest)
|
||||
Pleroma.Backports.mkdir_p!(dest)
|
||||
File.cp_r!(Path.join([source, from]), dest)
|
||||
:ok
|
||||
end
|
||||
|
|
|
|||
59
lib/pleroma/language/language_detector.ex
Normal file
59
lib/pleroma/language/language_detector.ex
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Language.LanguageDetector do
|
||||
import Pleroma.EctoType.ActivityPub.ObjectValidators.LanguageCode,
|
||||
only: [good_locale_code?: 1]
|
||||
|
||||
@words_threshold 4
|
||||
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
|
||||
|
||||
def configured? do
|
||||
provider = get_provider()
|
||||
|
||||
!!provider and provider.configured?()
|
||||
end
|
||||
|
||||
def missing_dependencies do
|
||||
provider = get_provider()
|
||||
|
||||
if provider do
|
||||
provider.missing_dependencies()
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
# Strip tags from text, etc.
|
||||
defp prepare_text(text) do
|
||||
text
|
||||
|> Floki.parse_fragment!()
|
||||
|> Floki.filter_out(
|
||||
".h-card, .mention, .hashtag, .u-url, .quote-inline, .recipients-inline, code, pre"
|
||||
)
|
||||
|> Floki.text()
|
||||
end
|
||||
|
||||
def detect(text) do
|
||||
provider = get_provider()
|
||||
|
||||
text = prepare_text(text)
|
||||
word_count = text |> String.split(~r/\s+/) |> Enum.count()
|
||||
|
||||
if word_count < @words_threshold or !provider or !provider.configured?() do
|
||||
nil
|
||||
else
|
||||
with language <- provider.detect(text),
|
||||
true <- good_locale_code?(language) do
|
||||
language
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_provider do
|
||||
@config_impl.get([__MODULE__, :provider])
|
||||
end
|
||||
end
|
||||
47
lib/pleroma/language/language_detector/fasttext.ex
Normal file
47
lib/pleroma/language/language_detector/fasttext.ex
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Language.LanguageDetector.Fasttext do
|
||||
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
|
||||
|
||||
alias Pleroma.Language.LanguageDetector.Provider
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@impl Provider
|
||||
def missing_dependencies do
|
||||
if Pleroma.Utils.command_available?("fasttext") do
|
||||
[]
|
||||
else
|
||||
["fasttext"]
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def configured?, do: not_empty_string(get_model())
|
||||
|
||||
@impl Provider
|
||||
def detect(text) do
|
||||
text_path = Path.join(System.tmp_dir!(), "fasttext-#{Ecto.UUID.generate()}")
|
||||
|
||||
File.write(text_path, text |> String.replace(~r/\s+/, " "))
|
||||
|
||||
detected_language =
|
||||
case System.cmd("fasttext", ["predict", get_model(), text_path]) do
|
||||
{"__label__" <> language, _} ->
|
||||
language |> String.trim()
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
File.rm(text_path)
|
||||
|
||||
detected_language
|
||||
end
|
||||
|
||||
defp get_model do
|
||||
Pleroma.Config.get([__MODULE__, :model])
|
||||
end
|
||||
end
|
||||
11
lib/pleroma/language/language_detector/provider.ex
Normal file
11
lib/pleroma/language/language_detector/provider.ex
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Language.LanguageDetector.Provider do
|
||||
@callback missing_dependencies() :: [String.t()]
|
||||
|
||||
@callback configured?() :: boolean()
|
||||
|
||||
@callback detect(text :: String.t()) :: String.t() | nil
|
||||
end
|
||||
127
lib/pleroma/language/translation.ex
Normal file
127
lib/pleroma/language/translation.ex
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Language.Translation do
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
def configured? do
|
||||
provider = get_provider()
|
||||
|
||||
!!provider and provider.configured?()
|
||||
end
|
||||
|
||||
def missing_dependencies do
|
||||
provider = get_provider()
|
||||
|
||||
if provider do
|
||||
provider.missing_dependencies()
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def translate(text, source_language, target_language) do
|
||||
cache_key = get_cache_key(text, source_language, target_language)
|
||||
|
||||
case @cachex.get(:translations_cache, cache_key) do
|
||||
{:ok, nil} ->
|
||||
provider = get_provider()
|
||||
|
||||
result =
|
||||
if !configured?() do
|
||||
{:error, :not_found}
|
||||
else
|
||||
provider.translate(text, source_language, target_language)
|
||||
|> scrub_html()
|
||||
end
|
||||
|
||||
store_result(result, cache_key)
|
||||
|
||||
result
|
||||
|
||||
{:ok, result} ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def supported_languages(type) when type in [:source, :target] do
|
||||
provider = get_provider()
|
||||
|
||||
cache_key = "#{type}_languages/#{provider.name()}"
|
||||
|
||||
case @cachex.get(:translations_cache, cache_key) do
|
||||
{:ok, nil} ->
|
||||
result =
|
||||
if !configured?() do
|
||||
{:error, :not_found}
|
||||
else
|
||||
provider.supported_languages(type)
|
||||
end
|
||||
|
||||
store_result(result, cache_key)
|
||||
|
||||
result
|
||||
|
||||
{:ok, result} ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def languages_matrix do
|
||||
provider = get_provider()
|
||||
|
||||
cache_key = "languages_matrix/#{provider.name()}"
|
||||
|
||||
case @cachex.get(:translations_cache, cache_key) do
|
||||
{:ok, nil} ->
|
||||
result =
|
||||
if !configured?() do
|
||||
{:error, :not_found}
|
||||
else
|
||||
provider.languages_matrix()
|
||||
end
|
||||
|
||||
store_result(result, cache_key)
|
||||
|
||||
result
|
||||
|
||||
{:ok, result} ->
|
||||
{:ok, result}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_provider, do: Pleroma.Config.get([__MODULE__, :provider])
|
||||
|
||||
defp get_cache_key(text, source_language, target_language) do
|
||||
"#{source_language}/#{target_language}/#{content_hash(text)}"
|
||||
end
|
||||
|
||||
defp store_result({:ok, result}, cache_key) do
|
||||
@cachex.put(:translations_cache, cache_key, result)
|
||||
end
|
||||
|
||||
defp store_result(_, _), do: nil
|
||||
|
||||
defp content_hash(text), do: :crypto.hash(:sha256, text) |> Base.encode64()
|
||||
|
||||
defp scrub_html({:ok, %{content: content} = result}) when is_binary(content) do
|
||||
scrubbers = Pleroma.Config.get([:markup, :scrub_policy])
|
||||
|
||||
content
|
||||
|> Pleroma.HTML.filter_tags(scrubbers)
|
||||
|
||||
{:ok, %{result | content: content}}
|
||||
end
|
||||
|
||||
defp scrub_html(result), do: result
|
||||
end
|
||||
121
lib/pleroma/language/translation/deepl.ex
Normal file
121
lib/pleroma/language/translation/deepl.ex
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Language.Translation.Deepl do
|
||||
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
|
||||
|
||||
alias Pleroma.Language.Translation.Provider
|
||||
|
||||
use Provider
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@name "DeepL"
|
||||
|
||||
@impl Provider
|
||||
def configured?, do: not_empty_string(base_url()) and not_empty_string(api_key())
|
||||
|
||||
@impl Provider
|
||||
def translate(content, source_language, target_language) do
|
||||
endpoint =
|
||||
base_url()
|
||||
|> URI.merge("/v2/translate")
|
||||
|> URI.to_string()
|
||||
|
||||
case Pleroma.HTTP.post(
|
||||
endpoint,
|
||||
Jason.encode!(%{
|
||||
text: [content],
|
||||
source_lang: source_language |> String.upcase(),
|
||||
target_lang: target_language,
|
||||
tag_handling: "html"
|
||||
}),
|
||||
[
|
||||
{"Content-Type", "application/json"},
|
||||
{"Authorization", "DeepL-Auth-Key #{api_key()}"}
|
||||
]
|
||||
) do
|
||||
{:ok, %{status: 429}} ->
|
||||
{:error, :too_many_requests}
|
||||
|
||||
{:ok, %{status: 456}} ->
|
||||
{:error, :quota_exceeded}
|
||||
|
||||
{:ok, %{status: 200} = res} ->
|
||||
%{
|
||||
"translations" => [
|
||||
%{"text" => content, "detected_source_language" => detected_source_language}
|
||||
]
|
||||
} = Jason.decode!(res.body)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
content: content,
|
||||
detected_source_language: detected_source_language,
|
||||
provider: @name
|
||||
}}
|
||||
|
||||
_ ->
|
||||
{:error, :internal_server_error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def supported_languages(type) when type in [:source, :target] do
|
||||
endpoint =
|
||||
base_url()
|
||||
|> URI.merge("/v2/languages")
|
||||
|> URI.to_string()
|
||||
|
||||
case Pleroma.HTTP.post(
|
||||
endpoint <> "?" <> URI.encode_query(%{type: type}),
|
||||
"",
|
||||
[
|
||||
{"Content-Type", "application/x-www-form-urlencoded"},
|
||||
{"Authorization", "DeepL-Auth-Key #{api_key()}"}
|
||||
]
|
||||
) do
|
||||
{:ok, %{status: 200} = res} ->
|
||||
languages =
|
||||
Jason.decode!(res.body)
|
||||
|> Enum.map(fn %{"language" => language} -> language |> String.downcase() end)
|
||||
|> Enum.map(fn language ->
|
||||
if String.contains?(language, "-") do
|
||||
[language, language |> String.split("-") |> Enum.at(0)]
|
||||
else
|
||||
language
|
||||
end
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.uniq()
|
||||
|
||||
{:ok, languages}
|
||||
|
||||
_ ->
|
||||
{:error, :internal_server_error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def languages_matrix do
|
||||
with {:ok, source_languages} <- supported_languages(:source),
|
||||
{:ok, target_languages} <- supported_languages(:target) do
|
||||
{:ok,
|
||||
Map.new(source_languages, fn language -> {language, target_languages -- [language]} end)}
|
||||
else
|
||||
{:error, error} -> {:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def name, do: @name
|
||||
|
||||
defp base_url do
|
||||
Pleroma.Config.get([__MODULE__, :base_url])
|
||||
end
|
||||
|
||||
defp api_key do
|
||||
Pleroma.Config.get([__MODULE__, :api_key])
|
||||
end
|
||||
end
|
||||
93
lib/pleroma/language/translation/libretranslate.ex
Normal file
93
lib/pleroma/language/translation/libretranslate.ex
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Language.Translation.Libretranslate do
|
||||
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
|
||||
|
||||
alias Pleroma.Language.Translation.Provider
|
||||
|
||||
use Provider
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@name "LibreTranslate"
|
||||
|
||||
@impl Provider
|
||||
def configured?, do: not_empty_string(base_url()) and not_empty_string(api_key())
|
||||
|
||||
@impl Provider
|
||||
def translate(content, source_language, target_language) do
|
||||
case Pleroma.HTTP.post(
|
||||
base_url() <> "/translate",
|
||||
Jason.encode!(%{
|
||||
q: content,
|
||||
source: source_language |> String.upcase(),
|
||||
target: target_language,
|
||||
format: "html",
|
||||
api_key: api_key()
|
||||
}),
|
||||
[
|
||||
{"Content-Type", "application/json"}
|
||||
]
|
||||
) do
|
||||
{:ok, %{status: 429}} ->
|
||||
{:error, :too_many_requests}
|
||||
|
||||
{:ok, %{status: 403}} ->
|
||||
{:error, :quota_exceeded}
|
||||
|
||||
{:ok, %{status: 200} = res} ->
|
||||
%{
|
||||
"translatedText" => content
|
||||
} = Jason.decode!(res.body)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
content: content,
|
||||
detected_source_language: source_language,
|
||||
provider: @name
|
||||
}}
|
||||
|
||||
_ ->
|
||||
{:error, :internal_server_error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def supported_languages(_) do
|
||||
case Pleroma.HTTP.get(base_url() <> "/languages") do
|
||||
{:ok, %{status: 200} = res} ->
|
||||
languages =
|
||||
Jason.decode!(res.body)
|
||||
|> Enum.map(fn %{"code" => code} -> code end)
|
||||
|
||||
{:ok, languages}
|
||||
|
||||
_ ->
|
||||
{:error, :internal_server_error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def languages_matrix do
|
||||
with {:ok, source_languages} <- supported_languages(:source),
|
||||
{:ok, target_languages} <- supported_languages(:target) do
|
||||
{:ok,
|
||||
Map.new(source_languages, fn language -> {language, target_languages -- [language]} end)}
|
||||
else
|
||||
{:error, error} -> {:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def name, do: @name
|
||||
|
||||
defp base_url do
|
||||
Pleroma.Config.get([__MODULE__, :base_url])
|
||||
end
|
||||
|
||||
defp api_key do
|
||||
Pleroma.Config.get([__MODULE__, :api_key], "")
|
||||
end
|
||||
end
|
||||
40
lib/pleroma/language/translation/provider.ex
Normal file
40
lib/pleroma/language/translation/provider.ex
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Language.Translation.Provider do
|
||||
alias Pleroma.Language.Translation.Provider
|
||||
|
||||
@callback missing_dependencies() :: [String.t()]
|
||||
|
||||
@callback configured?() :: boolean()
|
||||
|
||||
@callback translate(
|
||||
content :: String.t(),
|
||||
source_language :: String.t(),
|
||||
target_language :: String.t()
|
||||
) ::
|
||||
{:ok,
|
||||
%{
|
||||
content: String.t(),
|
||||
detected_source_language: String.t(),
|
||||
provider: String.t()
|
||||
}}
|
||||
| {:error, atom()}
|
||||
|
||||
@callback supported_languages(type :: :string | :target) ::
|
||||
{:ok, [String.t()]} | {:error, atom()}
|
||||
|
||||
@callback languages_matrix() :: {:ok, Map.t()} | {:error, atom()}
|
||||
|
||||
@callback name() :: String.t()
|
||||
|
||||
defmacro __using__(_opts) do
|
||||
quote do
|
||||
@impl Provider
|
||||
def missing_dependencies, do: []
|
||||
|
||||
defoverridable missing_dependencies: 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.OTPVersion do
|
||||
@spec version() :: String.t() | nil
|
||||
def version do
|
||||
# OTP Version https://erlang.org/doc/system_principles/versions.html#otp-version
|
||||
[
|
||||
Path.join(:code.root_dir(), "OTP_VERSION"),
|
||||
Path.join([:code.root_dir(), "releases", :erlang.system_info(:otp_release), "OTP_VERSION"])
|
||||
]
|
||||
|> get_version_from_files()
|
||||
end
|
||||
|
||||
@spec get_version_from_files([Path.t()]) :: String.t() | nil
|
||||
def get_version_from_files([]), do: nil
|
||||
|
||||
def get_version_from_files([path | paths]) do
|
||||
if File.exists?(path) do
|
||||
path
|
||||
|> File.read!()
|
||||
|> String.replace(~r/\r|\n|\s/, "")
|
||||
else
|
||||
get_version_from_files(paths)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -102,7 +102,8 @@ defmodule Pleroma.Search.DatabaseSearch do
|
|||
^tsc,
|
||||
o.data,
|
||||
^search_query
|
||||
)
|
||||
),
|
||||
order_by: [desc: :inserted_at]
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ defmodule Pleroma.Uploaders.Local do
|
|||
|
||||
[file | folders] ->
|
||||
path = Path.join([upload_path()] ++ Enum.reverse(folders))
|
||||
File.mkdir_p!(path)
|
||||
Pleroma.Backports.mkdir_p!(path)
|
||||
{path, file}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ defmodule Pleroma.User do
|
|||
field(:allow_following_move, :boolean, default: true)
|
||||
field(:skip_thread_containment, :boolean, default: false)
|
||||
field(:actor_type, :string, default: "Person")
|
||||
field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
field(:also_known_as, {:array, ObjectValidators.BareUri}, default: [])
|
||||
field(:inbox, :string)
|
||||
field(:shared_inbox, :string)
|
||||
field(:accepts_chat_messages, :boolean, default: nil)
|
||||
|
|
@ -308,7 +308,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def binary_id(%User{} = user), do: binary_id(user.id)
|
||||
|
||||
@doc "Returns status account"
|
||||
@doc "Returns account status"
|
||||
@spec account_status(User.t()) :: account_status()
|
||||
def account_status(%User{is_active: false}), do: :deactivated
|
||||
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
|
||||
|
|
@ -895,7 +895,7 @@ defmodule Pleroma.User do
|
|||
end)
|
||||
end
|
||||
|
||||
def validate_email_not_in_blacklisted_domain(changeset, field) do
|
||||
defp validate_email_not_in_blacklisted_domain(changeset, field) do
|
||||
validate_change(changeset, field, fn _, value ->
|
||||
valid? =
|
||||
Config.get([User, :email_blacklist])
|
||||
|
|
@ -912,9 +912,9 @@ defmodule Pleroma.User do
|
|||
end)
|
||||
end
|
||||
|
||||
def maybe_validate_required_email(changeset, true), do: changeset
|
||||
defp maybe_validate_required_email(changeset, true), do: changeset
|
||||
|
||||
def maybe_validate_required_email(changeset, _) do
|
||||
defp maybe_validate_required_email(changeset, _) do
|
||||
if Config.get([:instance, :account_activation_required]) do
|
||||
validate_required(changeset, [:email])
|
||||
else
|
||||
|
|
@ -1109,15 +1109,15 @@ defmodule Pleroma.User do
|
|||
|
||||
defp maybe_send_registration_email(_), do: {:ok, :noop}
|
||||
|
||||
def needs_update?(%User{local: true}), do: false
|
||||
defp needs_update?(%User{local: true}), do: false
|
||||
|
||||
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
||||
defp needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
||||
|
||||
def needs_update?(%User{local: false} = user) do
|
||||
defp needs_update?(%User{local: false} = user) do
|
||||
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
|
||||
end
|
||||
|
||||
def needs_update?(_), do: true
|
||||
defp needs_update?(_), do: true
|
||||
|
||||
@spec maybe_direct_follow(User.t(), User.t()) ::
|
||||
{:ok, User.t(), User.t()} | {:error, String.t()}
|
||||
|
|
@ -1708,7 +1708,9 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def block(%User{} = blocker, %User{} = blocked) do
|
||||
def block(blocker, blocked, params \\ %{})
|
||||
|
||||
def block(%User{} = blocker, %User{} = blocked, params) do
|
||||
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
|
||||
blocker =
|
||||
if following?(blocker, blocked) do
|
||||
|
|
@ -1738,12 +1740,33 @@ defmodule Pleroma.User do
|
|||
|
||||
{:ok, blocker} = update_follower_count(blocker)
|
||||
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
||||
add_to_block(blocker, blocked)
|
||||
|
||||
duration = Map.get(params, :duration, 0)
|
||||
|
||||
expires_at =
|
||||
if duration > 0 do
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(duration)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
user_block = add_to_block(blocker, blocked, expires_at)
|
||||
|
||||
if duration > 0 do
|
||||
Pleroma.Workers.MuteExpireWorker.new(
|
||||
%{"op" => "unblock_user", "blocker_id" => blocker.id, "blocked_id" => blocked.id},
|
||||
scheduled_at: expires_at
|
||||
)
|
||||
|> Oban.insert()
|
||||
end
|
||||
|
||||
user_block
|
||||
end
|
||||
|
||||
# helper to handle the block given only an actor's AP id
|
||||
def block(%User{} = blocker, %{ap_id: ap_id}) do
|
||||
block(blocker, get_cached_by_ap_id(ap_id))
|
||||
def block(%User{} = blocker, %{ap_id: ap_id}, params) do
|
||||
block(blocker, get_cached_by_ap_id(ap_id), params)
|
||||
end
|
||||
|
||||
def unblock(%User{} = blocker, %User{} = blocked) do
|
||||
|
|
@ -1984,7 +2007,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
@spec purge_user_changeset(User.t()) :: Ecto.Changeset.t()
|
||||
def purge_user_changeset(user) do
|
||||
defp purge_user_changeset(user) do
|
||||
# "Right to be forgotten"
|
||||
# https://gdpr.eu/right-to-be-forgotten/
|
||||
change(user, %{
|
||||
|
|
@ -2156,7 +2179,7 @@ defmodule Pleroma.User do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
|
||||
defp delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
|
||||
Notification
|
||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||
|> where([n, a], fragment("? = ?", a.actor, ^ap_id))
|
||||
|
|
@ -2615,7 +2638,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
# Internal function; public one is `deactivate/2`
|
||||
# Internal function; public one is `set_activation/2`
|
||||
defp set_activation_status(user, status) do
|
||||
user
|
||||
|> cast(%{is_active: status}, [:is_active])
|
||||
|
|
@ -2634,7 +2657,7 @@ defmodule Pleroma.User do
|
|||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def validate_fields(changeset, remote? \\ false) do
|
||||
defp validate_fields(changeset, remote?) do
|
||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||
limit = Config.get([:instance, limit_name], 0)
|
||||
|
||||
|
|
@ -2779,10 +2802,10 @@ defmodule Pleroma.User do
|
|||
set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
|
||||
end
|
||||
|
||||
@spec add_to_block(User.t(), User.t()) ::
|
||||
@spec add_to_block(User.t(), User.t(), integer() | nil) ::
|
||||
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp add_to_block(%User{} = user, %User{} = blocked) do
|
||||
with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
|
||||
defp add_to_block(%User{} = user, %User{} = blocked, expires_at) do
|
||||
with {:ok, relationship} <- UserRelationship.create_block(user, blocked, expires_at) do
|
||||
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
|
||||
{:ok, relationship}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ defmodule Pleroma.User.Backup do
|
|||
backup = Repo.preload(backup, :user)
|
||||
tempfile = Path.join([backup.tempdir, backup.file_name])
|
||||
|
||||
with {_, :ok} <- {:mkdir, File.mkdir_p(backup.tempdir)},
|
||||
with {_, :ok} <- {:mkdir, Pleroma.Backports.mkdir_p(backup.tempdir)},
|
||||
{_, :ok} <- {:actor, actor(backup.tempdir, backup.user)},
|
||||
{_, :ok} <- {:statuses, statuses(backup.tempdir, backup.user)},
|
||||
{_, :ok} <- {:likes, likes(backup.tempdir, backup.user)},
|
||||
|
|
|
|||
|
|
@ -327,8 +327,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
}, []}
|
||||
end
|
||||
|
||||
@spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
|
||||
def block(blocker, blocked) do
|
||||
@spec block(User.t(), User.t(), map()) :: {:ok, map(), keyword()}
|
||||
def block(blocker, blocked, params \\ %{}) do
|
||||
{:ok,
|
||||
%{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
|
|
@ -336,7 +336,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
"actor" => blocker.ap_id,
|
||||
"object" => blocked.ap_id,
|
||||
"to" => [blocked.ap_id]
|
||||
}, []}
|
||||
}, Keyword.new(params)}
|
||||
end
|
||||
|
||||
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
|
|||
Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
|
||||
)
|
||||
|
||||
File.mkdir_p(emoji_dir_path)
|
||||
Pleroma.Backports.mkdir_p(emoji_dir_path)
|
||||
|
||||
new_emojis =
|
||||
foreign_emojis
|
||||
|
|
|
|||
|
|
@ -200,14 +200,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
end
|
||||
|
||||
def validate(%{"type" => type} = object, meta)
|
||||
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
||||
when type in ~w[Accept Reject Follow Like EmojiReact Announce
|
||||
ChatMessage Answer] do
|
||||
validator =
|
||||
case type do
|
||||
"Accept" -> AcceptRejectValidator
|
||||
"Reject" -> AcceptRejectValidator
|
||||
"Follow" -> FollowValidator
|
||||
"Update" -> UpdateValidator
|
||||
"Like" -> LikeValidator
|
||||
"EmojiReact" -> EmojiReactValidator
|
||||
"Announce" -> AnnounceValidator
|
||||
|
|
@ -215,16 +214,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
"Answer" -> AnswerValidator
|
||||
end
|
||||
|
||||
cast_func =
|
||||
if type == "Update" do
|
||||
fn o -> validator.cast_and_validate(o, meta) end
|
||||
else
|
||||
fn o -> validator.cast_and_validate(o) end
|
||||
end
|
||||
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> cast_func.()
|
||||
|> validator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => type} = object, meta) when type == "Update" do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> UpdateValidator.cast_and_validate(meta)
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Language.LanguageDetector
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
|
|
@ -151,10 +152,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
|||
def maybe_add_language(object) do
|
||||
language =
|
||||
[
|
||||
get_language_from_context(object),
|
||||
get_language_from_content_map(object)
|
||||
&get_language_from_context/1,
|
||||
&get_language_from_content_map/1,
|
||||
&get_language_from_content/1
|
||||
]
|
||||
|> Enum.find(&good_locale_code?(&1))
|
||||
|> Enum.find_value(fn get_language ->
|
||||
language = get_language.(object)
|
||||
|
||||
if good_locale_code?(language) do
|
||||
language
|
||||
else
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|
||||
if language do
|
||||
Map.put(object, "language", language)
|
||||
|
|
@ -187,6 +197,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
|||
|
||||
defp get_language_from_content_map(_), do: nil
|
||||
|
||||
defp get_language_from_content(%{"content" => content} = object) do
|
||||
LanguageDetector.detect("#{object["summary"] || ""} #{content}")
|
||||
end
|
||||
|
||||
defp get_language_from_content(_), do: nil
|
||||
|
||||
def maybe_add_content_map(%{"language" => language, "content" => content} = object)
|
||||
when not_empty_string(language) do
|
||||
Map.put(object, "contentMap", Map.put(%{}, language, content))
|
||||
|
|
|
|||
|
|
@ -50,13 +50,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
|||
end
|
||||
|
||||
def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) do
|
||||
name =
|
||||
cond do
|
||||
"#" <> name -> name
|
||||
name -> name
|
||||
end
|
||||
|> String.downcase()
|
||||
|
||||
name = String.downcase(name)
|
||||
data = Map.put(data, "name", name)
|
||||
|
||||
struct
|
||||
|
|
|
|||
|
|
@ -93,7 +93,20 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
cc = Map.get(params, :cc, [])
|
||||
param_cc = Map.get(params, :cc, [])
|
||||
|
||||
original_cc = Map.get(data, "cc", [])
|
||||
|
||||
public_address = Pleroma.Constants.as_public()
|
||||
|
||||
# Ensure unlisted posts don't lose the public address in the cc
|
||||
# if the param_cc was set
|
||||
cc =
|
||||
if public_address in original_cc and public_address not in param_cc do
|
||||
[public_address | param_cc]
|
||||
else
|
||||
param_cc
|
||||
end
|
||||
|
||||
json =
|
||||
data
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
) do
|
||||
with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user),
|
||||
%User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do
|
||||
User.block(blocker, blocked)
|
||||
User.block(blocker, blocked, Enum.into(meta, %{}))
|
||||
end
|
||||
|
||||
{:ok, object, meta}
|
||||
|
|
|
|||
|
|
@ -492,6 +492,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
}
|
||||
|
||||
# Rewrite misskey likes into EmojiReacts
|
||||
defp handle_incoming_normalized(
|
||||
%{
|
||||
"type" => "Like",
|
||||
"content" => content
|
||||
} = data,
|
||||
options
|
||||
)
|
||||
when is_binary(content) do
|
||||
data
|
||||
|> Map.put("type", "EmojiReact")
|
||||
|> handle_incoming_normalized(options)
|
||||
end
|
||||
|
||||
defp handle_incoming_normalized(
|
||||
%{
|
||||
"type" => "Like",
|
||||
|
|
@ -500,7 +513,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
options
|
||||
) do
|
||||
data
|
||||
|> Map.put("type", "EmojiReact")
|
||||
|> Map.put("content", @misskey_reactions[reaction] || reaction)
|
||||
|> handle_incoming_normalized(options)
|
||||
end
|
||||
|
|
@ -652,6 +664,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
# Rewrite dislikes into the thumbs down emoji
|
||||
defp handle_incoming_normalized(%{"type" => "Dislike"} = data, options) do
|
||||
data
|
||||
|> Map.put("type", "EmojiReact")
|
||||
|> Map.put("content", "👎")
|
||||
|> handle_incoming_normalized(options)
|
||||
end
|
||||
|
||||
defp handle_incoming_normalized(
|
||||
%{"type" => "Undo", "object" => %{"type" => "Dislike"}} = data,
|
||||
options
|
||||
) do
|
||||
data
|
||||
|> put_in(["object", "type"], "EmojiReact")
|
||||
|> put_in(["object", "content"], "👎")
|
||||
|> handle_incoming_normalized(options)
|
||||
end
|
||||
|
||||
defp handle_incoming_normalized(_, _), do: :error
|
||||
|
||||
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
|
||||
|
|
|
|||
|
|
@ -335,13 +335,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
if params["password"] do
|
||||
User.force_password_reset_async(user)
|
||||
end
|
||||
|
||||
ModerationLog.insert_log(%{
|
||||
actor: admin,
|
||||
subject: [user],
|
||||
action: "force_password_reset"
|
||||
})
|
||||
ModerationLog.insert_log(%{
|
||||
actor: admin,
|
||||
subject: [user],
|
||||
action: "force_password_reset"
|
||||
})
|
||||
end
|
||||
|
||||
json(conn, %{status: "success"})
|
||||
else
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ defmodule Pleroma.Web.ApiSpec do
|
|||
"Frontend management",
|
||||
"Instance configuration",
|
||||
"Instance documents",
|
||||
"Instance rule managment",
|
||||
"Instance rule management",
|
||||
"Invites",
|
||||
"MediaProxy cache",
|
||||
"OAuth application management",
|
||||
|
|
|
|||
|
|
@ -284,18 +284,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
:query,
|
||||
%Schema{allOf: [BooleanLike], default: true},
|
||||
"Mute notifications in addition to statuses? Defaults to `true`."
|
||||
),
|
||||
Operation.parameter(
|
||||
:duration,
|
||||
:query,
|
||||
%Schema{type: :integer},
|
||||
"Expire the mute in `duration` seconds. Default 0 for infinity"
|
||||
),
|
||||
Operation.parameter(
|
||||
:expires_in,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 0},
|
||||
"Deprecated, use `duration` instead"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
|
|
@ -323,16 +311,37 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
tags: ["Account actions"],
|
||||
summary: "Block",
|
||||
operationId: "AccountController.block",
|
||||
requestBody: request_body("Parameters", block_request()),
|
||||
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||
description:
|
||||
"Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
|
||||
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
|
||||
parameters: [
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Relationship", "application/json", AccountRelationship)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp block_request do
|
||||
%Schema{
|
||||
title: "AccountBlockRequest",
|
||||
description: "POST body for blocking an account",
|
||||
type: :object,
|
||||
properties: %{
|
||||
duration: %Schema{
|
||||
type: :integer,
|
||||
nullable: true,
|
||||
description: "Expire the mute in `duration` seconds. Default 0 for infinity"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"duration" => 86_400
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def unblock_operation do
|
||||
%Operation{
|
||||
tags: ["Account actions"],
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do
|
|||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Instance rule managment"],
|
||||
tags: ["Instance rule management"],
|
||||
summary: "Retrieve list of instance rules",
|
||||
operationId: "AdminAPI.RuleController.index",
|
||||
security: [%{"oAuth" => ["admin:read"]}],
|
||||
|
|
@ -33,7 +33,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do
|
|||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Instance rule managment"],
|
||||
tags: ["Instance rule management"],
|
||||
summary: "Create new rule",
|
||||
operationId: "AdminAPI.RuleController.create",
|
||||
security: [%{"oAuth" => ["admin:write"]}],
|
||||
|
|
@ -49,7 +49,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do
|
|||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Instance rule managment"],
|
||||
tags: ["Instance rule management"],
|
||||
summary: "Modify existing rule",
|
||||
operationId: "AdminAPI.RuleController.update",
|
||||
security: [%{"oAuth" => ["admin:write"]}],
|
||||
|
|
@ -65,7 +65,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do
|
|||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["Instance rule managment"],
|
||||
tags: ["Instance rule management"],
|
||||
summary: "Delete rule",
|
||||
operationId: "AdminAPI.RuleController.delete",
|
||||
parameters: [Operation.parameter(:id, :path, :string, "Rule ID")],
|
||||
|
|
|
|||
|
|
@ -52,7 +52,30 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
|
|||
summary: "Retrieve list of instance rules",
|
||||
operationId: "InstanceController.rules",
|
||||
responses: %{
|
||||
200 => Operation.response("Array of domains", "application/json", array_of_rules())
|
||||
200 => Operation.response("Array of rules", "application/json", array_of_rules())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def translation_languages_operation do
|
||||
%Operation{
|
||||
tags: ["Instance misc"],
|
||||
summary: "Retrieve supported languages matrix",
|
||||
operationId: "InstanceController.translation_languages",
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"Translation languages matrix",
|
||||
"application/json",
|
||||
%Schema{
|
||||
type: :object,
|
||||
additionalProperties: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{type: :string},
|
||||
description: "Supported target languages for a source language"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -59,11 +59,15 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
|
|||
album: %Schema{type: :string, description: "The album of the media playing"},
|
||||
artist: %Schema{type: :string, description: "The artist of the media playing"},
|
||||
length: %Schema{type: :integer, description: "The length of the media playing"},
|
||||
externalLink: %Schema{type: :string, description: "A URL referencing the media playing"},
|
||||
external_link: %Schema{type: :string, description: "A URL referencing the media playing"},
|
||||
visibility: %Schema{
|
||||
allOf: [VisibilityScope],
|
||||
default: "public",
|
||||
description: "Scrobble visibility"
|
||||
},
|
||||
externalLink: %Schema{
|
||||
type: :string,
|
||||
description: "Deprecated, use `external_link` instead"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
|
|
@ -71,7 +75,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
|
|||
"artist" => "Some Artist",
|
||||
"album" => "Some Album",
|
||||
"length" => 180_000,
|
||||
"externalLink" => "https://www.last.fm/music/Some+Artist/_/Some+Title"
|
||||
"external_link" => "https://www.last.fm/music/Some+Artist/_/Some+Title"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -85,7 +89,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
|
|||
title: %Schema{type: :string, description: "The title of the media playing"},
|
||||
album: %Schema{type: :string, description: "The album of the media playing"},
|
||||
artist: %Schema{type: :string, description: "The artist of the media playing"},
|
||||
externalLink: %Schema{type: :string, description: "A URL referencing the media playing"},
|
||||
external_link: %Schema{type: :string, description: "A URL referencing the media playing"},
|
||||
length: %Schema{
|
||||
type: :integer,
|
||||
description: "The length of the media playing",
|
||||
|
|
@ -100,7 +104,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
|
|||
"artist" => "Some Artist",
|
||||
"album" => "Some Album",
|
||||
"length" => 180_000,
|
||||
"externalLink" => "https://www.last.fm/music/Some+Artist/_/Some+Title",
|
||||
"external_link" => "https://www.last.fm/music/Some+Artist/_/Some+Title",
|
||||
"created_at" => "2019-09-28T12:40:45.000Z"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -427,6 +427,38 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def translate_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve status information"],
|
||||
summary: "Translate status",
|
||||
description: "Translate status with an external API",
|
||||
operationId: "StatusController.translate",
|
||||
security: [%{"oAuth" => ["read:statuses"]}],
|
||||
parameters: [id_param()],
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
lang: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "Translation target language."
|
||||
}
|
||||
}
|
||||
},
|
||||
required: false
|
||||
),
|
||||
responses: %{
|
||||
200 => Operation.response("Translation", "application/json", translation()),
|
||||
400 => Operation.response("Error", "application/json", ApiError),
|
||||
404 => Operation.response("Error", "application/json", ApiError),
|
||||
503 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def favourites_operation do
|
||||
%Operation{
|
||||
tags: ["Timelines"],
|
||||
|
|
@ -819,4 +851,32 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp translation do
|
||||
%Schema{
|
||||
title: "StatusTranslation",
|
||||
description: "Represents status translation with related information.",
|
||||
type: :object,
|
||||
required: [:content, :detected_source_language, :provider],
|
||||
properties: %{
|
||||
content: %Schema{
|
||||
type: :string,
|
||||
description: "Translated status content"
|
||||
},
|
||||
detected_source_language: %Schema{
|
||||
type: :string,
|
||||
description: "Detected source language"
|
||||
},
|
||||
provider: %Schema{
|
||||
type: :string,
|
||||
description: "Translation provider service name"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"content" => "Software für die nächste Generation der sozialen Medien.",
|
||||
"detected_source_language" => "en",
|
||||
"provider" => "Deepl"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
id: FlakeID,
|
||||
locked: %Schema{type: :boolean},
|
||||
mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
|
||||
block_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
|
||||
note: %Schema{type: :string, format: :html},
|
||||
statuses_count: %Schema{type: :integer},
|
||||
url: %Schema{type: :string, format: :uri},
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
require Logger
|
||||
|
||||
@spec block(User.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
def block(blocked, blocker) do
|
||||
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
||||
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
||||
def block(blocked, blocker, params \\ %{}) do
|
||||
with {:ok, block_data, meta} <- Builder.block(blocker, blocked, params),
|
||||
{:ok, block, _} <- Pipeline.common_pipeline(block_data, meta ++ [local: true]) do
|
||||
{:ok, block}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Language.LanguageDetector
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
|
@ -90,7 +91,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
defp listen_object(draft) do
|
||||
object =
|
||||
draft.params
|
||||
|> Map.take([:album, :artist, :title, :length, :externalLink])
|
||||
|> Map.take([:album, :artist, :title, :length])
|
||||
|> Map.put(:externalLink, Map.get(draft.params, :external_link))
|
||||
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
||||
|> Map.put("type", "Audio")
|
||||
|> Map.put("to", draft.to)
|
||||
|
|
@ -255,13 +257,15 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
end
|
||||
|
||||
defp language(draft) do
|
||||
language = draft.params[:language]
|
||||
language =
|
||||
with language <- draft.params[:language],
|
||||
true <- good_locale_code?(language) do
|
||||
language
|
||||
else
|
||||
_ -> LanguageDetector.detect(draft.content_html <> " " <> draft.summary)
|
||||
end
|
||||
|
||||
if good_locale_code?(language) do
|
||||
%__MODULE__{draft | language: language}
|
||||
else
|
||||
draft
|
||||
end
|
||||
%__MODULE__{draft | language: language}
|
||||
end
|
||||
|
||||
defp object(draft) do
|
||||
|
|
|
|||
|
|
@ -122,6 +122,10 @@ defmodule Pleroma.Web.Federator do
|
|||
Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
|
||||
{:error, e}
|
||||
|
||||
{:reject, reason} = e ->
|
||||
Logger.debug("Rejected by MRF: #{inspect(reason)}")
|
||||
{:error, e}
|
||||
|
||||
e ->
|
||||
# Just drop those for now
|
||||
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ defmodule Pleroma.Web.InstanceDocument do
|
|||
|
||||
defp put_file(origin_path, destination_path) do
|
||||
with destination <- instance_static_dir(destination_path),
|
||||
{_, :ok} <- {:mkdir_p, File.mkdir_p(Path.dirname(destination))},
|
||||
{_, :ok} <- {:mkdir_p, Pleroma.Backports.mkdir_p(Path.dirname(destination))},
|
||||
{_, {:ok, _}} <- {:copy, File.copy(origin_path, destination)} do
|
||||
:ok
|
||||
else
|
||||
|
|
|
|||
|
|
@ -501,8 +501,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
end
|
||||
|
||||
@doc "POST /api/v1/accounts/:id/block"
|
||||
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
||||
with {:ok, _activity} <- CommonAPI.block(blocked, blocker) do
|
||||
def block(
|
||||
%{
|
||||
assigns: %{user: blocker, account: blocked},
|
||||
private: %{open_api_spex: %{body_params: params}}
|
||||
} = conn,
|
||||
_params
|
||||
) do
|
||||
with {:ok, _activity} <- CommonAPI.block(blocked, blocker, params) do
|
||||
render(conn, "relationship.json", user: blocker, target: blocked)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
|
|
@ -607,7 +613,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
users: users,
|
||||
for: user,
|
||||
as: :user,
|
||||
embed_relationships: embed_relationships?(params)
|
||||
embed_relationships: embed_relationships?(params),
|
||||
blocks: true
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -30,4 +30,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do
|
|||
def rules(conn, _params) do
|
||||
render(conn, "rules.json")
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/instance/translation_languages"
|
||||
def translation_languages(conn, _params) do
|
||||
render(conn, "translation_languages.json")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
f.()
|
||||
rescue
|
||||
error ->
|
||||
Logger.error("#{__MODULE__} search error: #{inspect(error)}")
|
||||
Logger.error(Exception.format(:error, error, __STACKTRACE__))
|
||||
fallback
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.BookmarkFolder
|
||||
alias Pleroma.Language.Translation
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.ScheduledActivity
|
||||
|
|
@ -44,6 +45,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
]
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :translate)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:statuses"]}
|
||||
|
|
@ -85,7 +88,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
%{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark]
|
||||
)
|
||||
|
||||
@rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a
|
||||
@rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete translate)a
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
|
|
@ -549,6 +552,41 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/statuses/:id/translate"
|
||||
def translate(
|
||||
%{
|
||||
assigns: %{user: user},
|
||||
private: %{open_api_spex: %{body_params: params, params: %{id: status_id}}}
|
||||
} = conn,
|
||||
_
|
||||
) do
|
||||
with %Activity{object: object} <- Activity.get_by_id_with_object(status_id),
|
||||
{:visibility, visibility} when visibility in ["public", "unlisted"] <-
|
||||
{:visibility, Visibility.get_visibility(object)},
|
||||
{:language, language} when is_binary(language) <-
|
||||
{:language, Map.get(params, :lang) || user.language},
|
||||
{:ok, result} <-
|
||||
Translation.translate(
|
||||
object.data["content"],
|
||||
object.data["language"],
|
||||
language
|
||||
) do
|
||||
render(conn, "translation.json", result)
|
||||
else
|
||||
{:language, nil} ->
|
||||
render_error(conn, :bad_request, "Language not specified")
|
||||
|
||||
{:visibility, _} ->
|
||||
render_error(conn, :not_found, "Record not found")
|
||||
|
||||
{:error, :not_found} ->
|
||||
render_error(conn, :not_found, "Translation service not configured")
|
||||
|
||||
{:error, error} when error in [:unexpected_response, :quota_exceeded, :too_many_requests] ->
|
||||
render_error(conn, :service_unavailable, "Translation service not available")
|
||||
end
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/favourites"
|
||||
def favourites(
|
||||
%{assigns: %{user: %User{} = user}, private: %{open_api_spex: %{params: params}}} = conn,
|
||||
|
|
|
|||
|
|
@ -340,6 +340,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|> maybe_put_unread_notification_count(user, opts[:for])
|
||||
|> maybe_put_email_address(user, opts[:for])
|
||||
|> maybe_put_mute_expires_at(user, opts[:for], opts)
|
||||
|> maybe_put_block_expires_at(user, opts[:for], opts)
|
||||
|> maybe_show_birthday(user, opts[:for])
|
||||
end
|
||||
|
||||
|
|
@ -476,6 +477,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
defp maybe_put_mute_expires_at(data, _, _, _), do: data
|
||||
|
||||
defp maybe_put_block_expires_at(data, %User{} = user, target, %{blocks: true}) do
|
||||
Map.put(
|
||||
data,
|
||||
:block_expires_at,
|
||||
UserRelationship.get_block_expire_date(target, user)
|
||||
)
|
||||
end
|
||||
|
||||
defp maybe_put_block_expires_at(data, _, _, _), do: data
|
||||
|
||||
defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do
|
||||
data
|
||||
|> Kernel.put_in([:pleroma, :birthday], user.birthday)
|
||||
|
|
|
|||
|
|
@ -90,6 +90,15 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
}
|
||||
end
|
||||
|
||||
def render("translation_languages.json", _) do
|
||||
with true <- Pleroma.Language.Translation.configured?(),
|
||||
{:ok, languages} <- Pleroma.Language.Translation.languages_matrix() do
|
||||
languages
|
||||
else
|
||||
_ -> %{}
|
||||
end
|
||||
end
|
||||
|
||||
defp common_information(instance) do
|
||||
%{
|
||||
languages: Keyword.get(instance, :languages, ["en"]),
|
||||
|
|
@ -145,7 +154,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
end,
|
||||
"pleroma:get:main/ostatus",
|
||||
"pleroma:group_actors",
|
||||
"pleroma:bookmark_folders"
|
||||
"pleroma:bookmark_folders",
|
||||
if Pleroma.Language.LanguageDetector.configured?() do
|
||||
"pleroma:language_detection"
|
||||
end,
|
||||
"pleroma:block_expiration"
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
|
@ -243,11 +256,27 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
},
|
||||
vapid: %{
|
||||
public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||
}
|
||||
},
|
||||
translation: %{enabled: Pleroma.Language.Translation.configured?()}
|
||||
})
|
||||
end
|
||||
|
||||
defp pleroma_configuration(instance) do
|
||||
base_urls = %{}
|
||||
|
||||
base_urls =
|
||||
if Config.get([:media_proxy, :enabled]) do
|
||||
Map.put(base_urls, :media_proxy, Config.get([:media_proxy, :base_url]))
|
||||
else
|
||||
base_urls
|
||||
end
|
||||
|
||||
base_urls =
|
||||
case Config.get([Pleroma.Upload, :base_url]) do
|
||||
nil -> base_urls
|
||||
url -> Map.put(base_urls, :upload, url)
|
||||
end
|
||||
|
||||
%{
|
||||
metadata: %{
|
||||
account_activation_required: Keyword.get(instance, :account_activation_required),
|
||||
|
|
@ -256,7 +285,10 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
fields_limits: fields_limits(),
|
||||
post_formats: Config.get([:instance, :allowed_post_formats]),
|
||||
birthday_required: Config.get([:instance, :birthday_required]),
|
||||
birthday_min_age: Config.get([:instance, :birthday_min_age])
|
||||
birthday_min_age: Config.get([:instance, :birthday_min_age]),
|
||||
translation: supported_languages(),
|
||||
base_urls: base_urls,
|
||||
markup: markup()
|
||||
},
|
||||
stats: %{mau: Pleroma.User.active_user_count()},
|
||||
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||
|
|
@ -282,4 +314,37 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
})
|
||||
})
|
||||
end
|
||||
|
||||
defp supported_languages do
|
||||
enabled = Pleroma.Language.Translation.configured?()
|
||||
|
||||
source_languages =
|
||||
with true <- enabled,
|
||||
{:ok, languages} <- Pleroma.Language.Translation.supported_languages(:source) do
|
||||
languages
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
target_languages =
|
||||
with true <- enabled,
|
||||
{:ok, languages} <- Pleroma.Language.Translation.supported_languages(:target) do
|
||||
languages
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
%{
|
||||
source_languages: source_languages,
|
||||
target_languages: target_languages
|
||||
}
|
||||
end
|
||||
|
||||
defp markup do
|
||||
%{
|
||||
allow_inline_images: Config.get([:markup, :allow_inline_images]),
|
||||
allow_headings: Config.get([:markup, :allow_headings]),
|
||||
allow_tables: Config.get([:markup, :allow_tables])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -681,6 +681,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
}
|
||||
end
|
||||
|
||||
def render("translation.json", %{
|
||||
content: content,
|
||||
detected_source_language: detected_source_language,
|
||||
provider: provider
|
||||
}) do
|
||||
%{content: content, detected_source_language: detected_source_language, provider: provider}
|
||||
end
|
||||
|
||||
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
|
||||
object = Object.normalize(activity, fetch: false)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
|
|||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaScrobbleOperation
|
||||
|
||||
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
||||
params =
|
||||
params
|
||||
|> Map.put_new(:external_link, Map.get(params, :externalLink))
|
||||
|
||||
with {:ok, activity} <- CommonAPI.listen(user, params) do
|
||||
render(conn, "show.json", activity: activity, for: user)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -27,8 +27,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
|
|||
title: object.data["title"] |> HTML.strip_tags(),
|
||||
artist: object.data["artist"] |> HTML.strip_tags(),
|
||||
album: object.data["album"] |> HTML.strip_tags(),
|
||||
externalLink: object.data["externalLink"],
|
||||
length: object.data["length"]
|
||||
external_link: object.data["externalLink"],
|
||||
length: object.data["length"],
|
||||
# DEPRECATED
|
||||
externalLink: object.data["externalLink"]
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.RichMedia.Parser do
|
||||
alias Pleroma.Web.RichMedia.Helpers
|
||||
import Pleroma.Web.Metadata.Utils, only: [scrub_html_and_truncate: 2]
|
||||
require Logger
|
||||
|
||||
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
|
||||
|
|
@ -63,8 +64,20 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
not match?({:ok, _}, Jason.encode(%{key => val}))
|
||||
end)
|
||||
|> Map.new()
|
||||
|> truncate_title()
|
||||
|> truncate_desc()
|
||||
end
|
||||
|
||||
defp truncate_title(%{"title" => title} = data) when is_binary(title),
|
||||
do: %{data | "title" => scrub_html_and_truncate(title, 120)}
|
||||
|
||||
defp truncate_title(data), do: data
|
||||
|
||||
defp truncate_desc(%{"description" => desc} = data) when is_binary(desc),
|
||||
do: %{data | "description" => scrub_html_and_truncate(desc, 200)}
|
||||
|
||||
defp truncate_desc(data), do: data
|
||||
|
||||
@spec validate_page_url(URI.t() | binary()) :: :ok | :error
|
||||
defp validate_page_url(page_url) when is_binary(page_url) do
|
||||
validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld])
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue