diff --git a/.woodpecker.yml b/.woodpecker.yml index b9aca6a4b..c97e3eb4f 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -14,6 +14,14 @@ variables: - stable - refs/tags/v* - refs/tags/stable-* + - &on-stable + when: + event: + - push + - tag + branch: + - stable + - refs/tags/stable-* - &on-point-release when: event: @@ -110,6 +118,8 @@ pipeline: - export SOURCE=akkoma-ubuntu-jammy.zip - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-ubuntu-jammy.zip - /bin/sh /entrypoint.sh + - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-ubuntu-jammy.zip + - /bin/sh /entrypoint.sh debian-bullseye: image: hexpm/elixir:1.13.4-erlang-24.3.4.5-debian-bullseye-20220801 @@ -142,7 +152,7 @@ pipeline: # Canonical amd64-musl musl: image: hexpm/elixir:1.13.4-erlang-24.3.4.5-alpine-3.15.6 - <<: *on-release + <<: *on-stable environment: MIX_ENV: prod commands: @@ -157,7 +167,7 @@ pipeline: release-musl: image: akkoma/releaser - <<: *on-release + <<: *on-stable secrets: *scw-secrets commands: - export SOURCE=akkoma-amd64-musl.zip @@ -172,8 +182,12 @@ pipeline: - SCW_DEFAULT_ORGANIZATION_ID environment: CI: "true" - image: akkoma/docs-builder + image: python:3.10-slim commands: + - apt-get update && apt-get install -y rclone wget git zip + - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64 + - mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli + - chmod +x scaleway-cli - ./scaleway-cli object config install type=rclone - cd docs - pip install -r requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 96b80693c..90a5910e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 2022.10 + +### Added +- Ability to sync frontend profiles between clients, with a name attached +- Status card generation will now use the media summary if it is available + +### Changed +- Emoji updated to latest 15.0 draft +- **Breaking**: `/api/v1/pleroma/backups` endpoints now requires `read:backups` scope instead of `read:accounts` + +### Fixed +- OAuthPlug no longer joins with the database every call and uses the user cache +- Undo activities no longer try to look up by ID, and render correctly +- prevent false-errors from meilisearch + ## 2022.09 ### Added @@ -18,6 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - MFM parsing is now done on the backend by a modified version of ilja's parser -> https://akkoma.dev/AkkomaGang/mfm-parser - InlineQuotePolicy is now on by default +- Enable remote users to interact with posts ### Fixed - Compatibility with latest meilisearch @@ -49,7 +65,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - amd64 is built for debian stable. Compatible with ubuntu 20. - ubuntu-jammy is built for... well, ubuntu 22 (LTS) - amd64-musl is built for alpine 3.16 -- Enable remote users to interact with posts ### Fixed - Updated mastoFE path, for the newer version diff --git a/config/config.exs b/config/config.exs index bf7e7db44..d7005770e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -48,6 +48,7 @@ config :pleroma, Pleroma.Repo, telemetry_event: [Pleroma.Repo.Instrumenter], + queue_target: 20_000, migration_lock: nil config :pleroma, Pleroma.Captcha, @@ -260,7 +261,8 @@ password_reset_token_validity: 60 * 60 * 24, profile_directory: true, privileged_staff: false, - local_bubble: [] + local_bubble: [], + max_frontend_settings_json_chars: 100_000 config :pleroma, :welcome, direct_message: [ @@ -752,9 +754,9 @@ }, "soapbox-fe" => %{ "name" => "soapbox-fe", - "git" => "https://gitlab.com/soapbox-pub/soapbox-fe", + "git" => "https://gitlab.com/soapbox-pub/soapbox", "build_url" => - "https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production", + "https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/${ref}/download?job=build-production", "ref" => "v2.0.0", "build_dir" => "static" }, diff --git a/docs/docs/administration/updating.md b/docs/docs/administration/updating.md index 6f7ffcd63..2d9e77075 100644 --- a/docs/docs/administration/updating.md +++ b/docs/docs/administration/updating.md @@ -14,6 +14,10 @@ su akkoma -s $SHELL -lc "./bin/pleroma_ctl update" su akkoma -s $SHELL -lc "./bin/pleroma_ctl migrate" ``` +If you selected an alternate flavour on installation, +you _may_ need to specify `--flavour`, in the same way as +[when installing](../../installation/otp_en#detecting-flavour). + ## For from source installations (using git) 1. Go to the working directory of Akkoma (default is `/opt/akkoma`) diff --git a/docs/docs/configuration/howto_theming_your_instance.md b/docs/docs/configuration/howto_theming_your_instance.md index 213afcf13..af417aee4 100644 --- a/docs/docs/configuration/howto_theming_your_instance.md +++ b/docs/docs/configuration/howto_theming_your_instance.md @@ -21,7 +21,7 @@ This will only save the theme for you personally. To make it available to the wh ### Upload the theme to the server -Themes can be found in the [static directory](static_dir.md). Create `STATIC-DIR/static/themes/` if needed and copy your theme there. Next you need to add an entry for your theme to `STATIC-DIR/static/styles.json`. If you use a from source installation, you'll first need to copy the file from `priv/static/static/styles.json`. +Themes can be found in the [static directory](static_dir.md). Create `STATIC-DIR/static/themes/` if needed and copy your theme there. Next you need to add an entry for your theme to `STATIC-DIR/static/styles.json`. If you use a from source installation, you'll first need to copy the file from `STATIC-DIR/frontends/pleroma-fe/REF/static/styles.json` (where `REF` is `stable` or `develop` depending on which ref you decided to install). Example of `styles.json` where we add our own `my-awesome-theme.json` ```json diff --git a/docs/docs/configuration/onion_federation.md b/docs/docs/configuration/onion_federation.md index 077c3eb50..499b4a693 100644 --- a/docs/docs/configuration/onion_federation.md +++ b/docs/docs/configuration/onion_federation.md @@ -14,11 +14,12 @@ apt -yq install tor **WARNING:** Onion instances not using a Tor version supporting V3 addresses will not be able to federate with you. -Create the hidden service for your Akkoma instance in `/etc/tor/torrc`: +Create the hidden service for your Akkoma instance in `/etc/tor/torrc`, with an HTTP tunnel: ``` HiddenServiceDir /var/lib/tor/akkoma_hidden_service/ HiddenServicePort 80 127.0.0.1:8099 HiddenServiceVersion 3 # Remove if Tor version is below 0.3 ( tor --version ) +HTTPTunnelPort 9080 ``` Restart Tor to generate an adress: ``` @@ -35,7 +36,7 @@ Next, edit your Akkoma config. If running in prod, navigate to your Akkoma directory, edit `config/prod.secret.exs` and append this line: ``` -config :pleroma, :http, proxy_url: {:socks5, :localhost, 9050} +config :pleroma, :http, proxy_url: "http://localhost:9080" ``` In your Akkoma directory, assuming you're running prod, run the following: diff --git a/docs/docs/configuration/search.md b/docs/docs/configuration/search.md index ebb2c6ab7..1e343032f 100644 --- a/docs/docs/configuration/search.md +++ b/docs/docs/configuration/search.md @@ -141,8 +141,7 @@ You then need to set the URL and authentication credentials if relevant. ### Initial indexing -After setting up the configuration, you'll want to index all of your already existsing posts. Only public posts are indexed. You'll only -have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. +After setting up the configuration, you'll want to index all of your already existsing posts. You'll only have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. The sequence of actions is as follows: diff --git a/docs/docs/index.md b/docs/docs/index.md index f9340d5d3..1018e9c2b 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -7,6 +7,20 @@ It actually consists of two components: a backend, named simply Akkoma, and a us It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other. One account on an instance is enough to talk to the entire fediverse! +## Community Channels + +### IRC + +For support or general questions, pop over to #akkoma and #akkoma-dev at [irc.akkoma.dev](https://irc.akkoma.dev) (port 6697, SSL) + +### Discourse + +For more general meta-discussion, for example discussion of potential future features, head on over to [meta.akkoma.dev](https://meta.akkoma.dev) + +### Dev diaries and release notifications + +will be posted via [@akkoma@ihba](https://ihatebeinga.live/users/akkoma) + ## How can I use it? Akkoma instances are already widely deployed, a list can be found at and . @@ -26,3 +40,4 @@ Just add a "/web" after your instance url (e.g. The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation. Remember, what you see is only the frontend part of Mastodon, the backend is still Akkoma. + diff --git a/docs/docs/installation/otp_en.md b/docs/docs/installation/otp_en.md index 329afe967..3e00d3262 100644 --- a/docs/docs/installation/otp_en.md +++ b/docs/docs/installation/otp_en.md @@ -19,12 +19,12 @@ This is a little more complex than it used to be (thanks ubuntu) Use the following mapping to figure out your flavour: -| distribution | flavour | -| ------------- | ------------ | -| debian stable | amd64 | -| ubuntu focal | amd64 | -| ubuntu jammy | ubuntu-jammy | -| alpine | amd64-musl | +| distribution | flavour | available branches | +| ------------- | ------------------ | ------------------- | +| debian stable | amd64 | develop, stable | +| ubuntu focal | amd64 | develop, stable | +| ubuntu jammy | amd64-ubuntu-jammy | develop, stable | +| alpine | amd64-musl | stable | Other similar distributions will _probably_ work, but if it is not listed above, there is no official support. diff --git a/lib/pleroma/akkoma/frontend_setting_profile.ex b/lib/pleroma/akkoma/frontend_setting_profile.ex new file mode 100644 index 000000000..18208a7dd --- /dev/null +++ b/lib/pleroma/akkoma/frontend_setting_profile.ex @@ -0,0 +1,100 @@ +defmodule Pleroma.Akkoma.FrontendSettingsProfile do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + alias Pleroma.Repo + alias Pleroma.Config + alias Pleroma.User + + @primary_key false + schema "user_frontend_setting_profiles" do + belongs_to(:user, Pleroma.User, primary_key: true, type: FlakeId.Ecto.CompatType) + field(:frontend_name, :string, primary_key: true) + field(:profile_name, :string, primary_key: true) + field(:settings, :map) + field(:version, :integer) + timestamps() + end + + def changeset(%__MODULE__{} = struct, attrs) do + struct + |> cast(attrs, [:user_id, :frontend_name, :profile_name, :settings, :version]) + |> validate_required([:user_id, :frontend_name, :profile_name, :settings, :version]) + |> validate_length(:frontend_name, min: 1, max: 255) + |> validate_length(:profile_name, min: 1, max: 255) + |> validate_version(struct) + |> validate_number(:version, greater_than: 0) + |> validate_settings_length(Config.get([:instance, :max_frontend_settings_json_chars])) + end + + def create_or_update(%User{} = user, frontend_name, profile_name, settings, version) do + struct = + case get_by_user_and_frontend_name_and_profile_name(user, frontend_name, profile_name) do + nil -> + %__MODULE__{} + + %__MODULE__{} = profile -> + profile + end + + struct + |> changeset(%{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: version + }) + |> Repo.insert_or_update() + end + + def get_all_by_user_and_frontend_name(%User{id: user_id}, frontend_name) do + Repo.all( + from(p in __MODULE__, where: p.user_id == ^user_id and p.frontend_name == ^frontend_name) + ) + end + + def get_by_user_and_frontend_name_and_profile_name( + %User{id: user_id}, + frontend_name, + profile_name + ) do + Repo.one( + from(p in __MODULE__, + where: + p.user_id == ^user_id and p.frontend_name == ^frontend_name and + p.profile_name == ^profile_name + ) + ) + end + + def delete_profile(profile) do + Repo.delete(profile) + end + + defp validate_settings_length( + %Ecto.Changeset{changes: %{settings: settings}} = changeset, + max_length + ) do + settings_json = Jason.encode!(settings) + + if String.length(settings_json) > max_length do + add_error(changeset, :settings, "is too long") + else + changeset + end + end + + defp validate_version(changeset, %{version: nil}), do: changeset + + defp validate_version(%Ecto.Changeset{changes: %{version: version}} = changeset, %{ + version: prev_version + }) do + if version != prev_version + 1 do + add_error(changeset, :version, "must be incremented by 1") + else + changeset + end + end +end diff --git a/lib/pleroma/emoji-test.txt b/lib/pleroma/emoji-test.txt index dd5493366..87d093d64 100644 --- a/lib/pleroma/emoji-test.txt +++ b/lib/pleroma/emoji-test.txt @@ -1,13 +1,13 @@ # emoji-test.txt -# Date: 2021-08-26, 17:22:23 GMT -# ยฉ 2021 Unicodeยฎ, Inc. +# Date: 2022-08-12, 20:24:39 GMT +# ยฉ 2022 Unicodeยฎ, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. -# For terms of use, see http://www.unicode.org/terms_of_use.html +# For terms of use, see https://www.unicode.org/terms_of_use.html # # Emoji Keyboard/Display Test Data for UTS #51 -# Version: 14.0 +# Version: 15.0 # -# For documentation and usage, see http://www.unicode.org/reports/tr51 +# For documentation and usage, see https://www.unicode.org/reports/tr51 # # This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed. # Format: code points; status # emoji name @@ -92,6 +92,7 @@ 1F62C ; fully-qualified # ๐Ÿ˜ฌ E1.0 grimacing face 1F62E 200D 1F4A8 ; fully-qualified # ๐Ÿ˜ฎโ€๐Ÿ’จ E13.1 face exhaling 1F925 ; fully-qualified # ๐Ÿคฅ E3.0 lying face +1FAE8 ; fully-qualified # ๐Ÿซจ E15.0 shaking face # subgroup: face-sleepy 1F60C ; fully-qualified # ๐Ÿ˜Œ E0.6 relieved face @@ -155,7 +156,7 @@ # subgroup: face-negative 1F624 ; fully-qualified # ๐Ÿ˜ค E0.6 face with steam from nose -1F621 ; fully-qualified # ๐Ÿ˜ก E0.6 pouting face +1F621 ; fully-qualified # ๐Ÿ˜ก E0.6 enraged face 1F620 ; fully-qualified # ๐Ÿ˜  E0.6 angry face 1F92C ; fully-qualified # ๐Ÿคฌ E5.0 face with symbols on mouth 1F608 ; fully-qualified # ๐Ÿ˜ˆ E1.0 smiling face with horns @@ -190,8 +191,7 @@ 1F649 ; fully-qualified # ๐Ÿ™‰ E0.6 hear-no-evil monkey 1F64A ; fully-qualified # ๐Ÿ™Š E0.6 speak-no-evil monkey -# subgroup: emotion -1F48B ; fully-qualified # ๐Ÿ’‹ E0.6 kiss mark +# subgroup: heart 1F48C ; fully-qualified # ๐Ÿ’Œ E0.6 love letter 1F498 ; fully-qualified # ๐Ÿ’˜ E0.6 heart with arrow 1F49D ; fully-qualified # ๐Ÿ’ E0.6 heart with ribbon @@ -210,14 +210,20 @@ 2764 200D 1FA79 ; unqualified # โคโ€๐Ÿฉน E13.1 mending heart 2764 FE0F ; fully-qualified # โค๏ธ E0.6 red heart 2764 ; unqualified # โค E0.6 red heart +1FA77 ; fully-qualified # ๐Ÿฉท E15.0 pink heart 1F9E1 ; fully-qualified # ๐Ÿงก E5.0 orange heart 1F49B ; fully-qualified # ๐Ÿ’› E0.6 yellow heart 1F49A ; fully-qualified # ๐Ÿ’š E0.6 green heart 1F499 ; fully-qualified # ๐Ÿ’™ E0.6 blue heart +1FA75 ; fully-qualified # ๐Ÿฉต E15.0 light blue heart 1F49C ; fully-qualified # ๐Ÿ’œ E0.6 purple heart 1F90E ; fully-qualified # ๐ŸคŽ E12.0 brown heart 1F5A4 ; fully-qualified # ๐Ÿ–ค E3.0 black heart +1FA76 ; fully-qualified # ๐Ÿฉถ E15.0 grey heart 1F90D ; fully-qualified # ๐Ÿค E12.0 white heart + +# subgroup: emotion +1F48B ; fully-qualified # ๐Ÿ’‹ E0.6 kiss mark 1F4AF ; fully-qualified # ๐Ÿ’ฏ E0.6 hundred points 1F4A2 ; fully-qualified # ๐Ÿ’ข E0.6 anger symbol 1F4A5 ; fully-qualified # ๐Ÿ’ฅ E0.6 collision @@ -226,21 +232,20 @@ 1F4A8 ; fully-qualified # ๐Ÿ’จ E0.6 dashing away 1F573 FE0F ; fully-qualified # ๐Ÿ•ณ๏ธ E0.7 hole 1F573 ; unqualified # ๐Ÿ•ณ E0.7 hole -1F4A3 ; fully-qualified # ๐Ÿ’ฃ E0.6 bomb 1F4AC ; fully-qualified # ๐Ÿ’ฌ E0.6 speech balloon 1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # ๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ E2.0 eye in speech bubble 1F441 200D 1F5E8 FE0F ; unqualified # ๐Ÿ‘โ€๐Ÿ—จ๏ธ E2.0 eye in speech bubble -1F441 FE0F 200D 1F5E8 ; unqualified # ๐Ÿ‘๏ธโ€๐Ÿ—จ E2.0 eye in speech bubble +1F441 FE0F 200D 1F5E8 ; minimally-qualified # ๐Ÿ‘๏ธโ€๐Ÿ—จ E2.0 eye in speech bubble 1F441 200D 1F5E8 ; unqualified # ๐Ÿ‘โ€๐Ÿ—จ E2.0 eye in speech bubble 1F5E8 FE0F ; fully-qualified # ๐Ÿ—จ๏ธ E2.0 left speech bubble 1F5E8 ; unqualified # ๐Ÿ—จ E2.0 left speech bubble 1F5EF FE0F ; fully-qualified # ๐Ÿ—ฏ๏ธ E0.7 right anger bubble 1F5EF ; unqualified # ๐Ÿ—ฏ E0.7 right anger bubble 1F4AD ; fully-qualified # ๐Ÿ’ญ E1.0 thought balloon -1F4A4 ; fully-qualified # ๐Ÿ’ค E0.6 zzz +1F4A4 ; fully-qualified # ๐Ÿ’ค E0.6 ZZZ -# Smileys & Emotion subtotal: 177 -# Smileys & Emotion subtotal: 177 w/o modifiers +# Smileys & Emotion subtotal: 180 +# Smileys & Emotion subtotal: 180 w/o modifiers # group: People & Body @@ -300,6 +305,18 @@ 1FAF4 1F3FD ; fully-qualified # ๐Ÿซด๐Ÿฝ E14.0 palm up hand: medium skin tone 1FAF4 1F3FE ; fully-qualified # ๐Ÿซด๐Ÿพ E14.0 palm up hand: medium-dark skin tone 1FAF4 1F3FF ; fully-qualified # ๐Ÿซด๐Ÿฟ E14.0 palm up hand: dark skin tone +1FAF7 ; fully-qualified # ๐Ÿซท E15.0 leftwards pushing hand +1FAF7 1F3FB ; fully-qualified # ๐Ÿซท๐Ÿป E15.0 leftwards pushing hand: light skin tone +1FAF7 1F3FC ; fully-qualified # ๐Ÿซท๐Ÿผ E15.0 leftwards pushing hand: medium-light skin tone +1FAF7 1F3FD ; fully-qualified # ๐Ÿซท๐Ÿฝ E15.0 leftwards pushing hand: medium skin tone +1FAF7 1F3FE ; fully-qualified # ๐Ÿซท๐Ÿพ E15.0 leftwards pushing hand: medium-dark skin tone +1FAF7 1F3FF ; fully-qualified # ๐Ÿซท๐Ÿฟ E15.0 leftwards pushing hand: dark skin tone +1FAF8 ; fully-qualified # ๐Ÿซธ E15.0 rightwards pushing hand +1FAF8 1F3FB ; fully-qualified # ๐Ÿซธ๐Ÿป E15.0 rightwards pushing hand: light skin tone +1FAF8 1F3FC ; fully-qualified # ๐Ÿซธ๐Ÿผ E15.0 rightwards pushing hand: medium-light skin tone +1FAF8 1F3FD ; fully-qualified # ๐Ÿซธ๐Ÿฝ E15.0 rightwards pushing hand: medium skin tone +1FAF8 1F3FE ; fully-qualified # ๐Ÿซธ๐Ÿพ E15.0 rightwards pushing hand: medium-dark skin tone +1FAF8 1F3FF ; fully-qualified # ๐Ÿซธ๐Ÿฟ E15.0 rightwards pushing hand: dark skin tone # subgroup: hand-fingers-partial 1F44C ; fully-qualified # ๐Ÿ‘Œ E0.6 OK hand @@ -473,11 +490,11 @@ 1F932 1F3FE ; fully-qualified # ๐Ÿคฒ๐Ÿพ E5.0 palms up together: medium-dark skin tone 1F932 1F3FF ; fully-qualified # ๐Ÿคฒ๐Ÿฟ E5.0 palms up together: dark skin tone 1F91D ; fully-qualified # ๐Ÿค E3.0 handshake -1F91D 1F3FB ; fully-qualified # ๐Ÿค๐Ÿป E3.0 handshake: light skin tone -1F91D 1F3FC ; fully-qualified # ๐Ÿค๐Ÿผ E3.0 handshake: medium-light skin tone -1F91D 1F3FD ; fully-qualified # ๐Ÿค๐Ÿฝ E3.0 handshake: medium skin tone -1F91D 1F3FE ; fully-qualified # ๐Ÿค๐Ÿพ E3.0 handshake: medium-dark skin tone -1F91D 1F3FF ; fully-qualified # ๐Ÿค๐Ÿฟ E3.0 handshake: dark skin tone +1F91D 1F3FB ; fully-qualified # ๐Ÿค๐Ÿป E14.0 handshake: light skin tone +1F91D 1F3FC ; fully-qualified # ๐Ÿค๐Ÿผ E14.0 handshake: medium-light skin tone +1F91D 1F3FD ; fully-qualified # ๐Ÿค๐Ÿฝ E14.0 handshake: medium skin tone +1F91D 1F3FE ; fully-qualified # ๐Ÿค๐Ÿพ E14.0 handshake: medium-dark skin tone +1F91D 1F3FF ; fully-qualified # ๐Ÿค๐Ÿฟ E14.0 handshake: dark skin tone 1FAF1 1F3FB 200D 1FAF2 1F3FC ; fully-qualified # ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿผ E14.0 handshake: light skin tone, medium-light skin tone 1FAF1 1F3FB 200D 1FAF2 1F3FD ; fully-qualified # ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿฝ E14.0 handshake: light skin tone, medium skin tone 1FAF1 1F3FB 200D 1FAF2 1F3FE ; fully-qualified # ๐Ÿซฑ๐Ÿปโ€๐Ÿซฒ๐Ÿพ E14.0 handshake: light skin tone, medium-dark skin tone @@ -1455,7 +1472,7 @@ 1F575 1F3FF ; fully-qualified # ๐Ÿ•ต๐Ÿฟ E2.0 detective: dark skin tone 1F575 FE0F 200D 2642 FE0F ; fully-qualified # ๐Ÿ•ต๏ธโ€โ™‚๏ธ E4.0 man detective 1F575 200D 2642 FE0F ; unqualified # ๐Ÿ•ตโ€โ™‚๏ธ E4.0 man detective -1F575 FE0F 200D 2642 ; unqualified # ๐Ÿ•ต๏ธโ€โ™‚ E4.0 man detective +1F575 FE0F 200D 2642 ; minimally-qualified # ๐Ÿ•ต๏ธโ€โ™‚ E4.0 man detective 1F575 200D 2642 ; unqualified # ๐Ÿ•ตโ€โ™‚ E4.0 man detective 1F575 1F3FB 200D 2642 FE0F ; fully-qualified # ๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ E4.0 man detective: light skin tone 1F575 1F3FB 200D 2642 ; minimally-qualified # ๐Ÿ•ต๐Ÿปโ€โ™‚ E4.0 man detective: light skin tone @@ -1469,7 +1486,7 @@ 1F575 1F3FF 200D 2642 ; minimally-qualified # ๐Ÿ•ต๐Ÿฟโ€โ™‚ E4.0 man detective: dark skin tone 1F575 FE0F 200D 2640 FE0F ; fully-qualified # ๐Ÿ•ต๏ธโ€โ™€๏ธ E4.0 woman detective 1F575 200D 2640 FE0F ; unqualified # ๐Ÿ•ตโ€โ™€๏ธ E4.0 woman detective -1F575 FE0F 200D 2640 ; unqualified # ๐Ÿ•ต๏ธโ€โ™€ E4.0 woman detective +1F575 FE0F 200D 2640 ; minimally-qualified # ๐Ÿ•ต๏ธโ€โ™€ E4.0 woman detective 1F575 200D 2640 ; unqualified # ๐Ÿ•ตโ€โ™€ E4.0 woman detective 1F575 1F3FB 200D 2640 FE0F ; fully-qualified # ๐Ÿ•ต๐Ÿปโ€โ™€๏ธ E4.0 woman detective: light skin tone 1F575 1F3FB 200D 2640 ; minimally-qualified # ๐Ÿ•ต๐Ÿปโ€โ™€ E4.0 woman detective: light skin tone @@ -2302,7 +2319,7 @@ 1F3CC 1F3FF ; fully-qualified # ๐ŸŒ๐Ÿฟ E4.0 person golfing: dark skin tone 1F3CC FE0F 200D 2642 FE0F ; fully-qualified # ๐ŸŒ๏ธโ€โ™‚๏ธ E4.0 man golfing 1F3CC 200D 2642 FE0F ; unqualified # ๐ŸŒโ€โ™‚๏ธ E4.0 man golfing -1F3CC FE0F 200D 2642 ; unqualified # ๐ŸŒ๏ธโ€โ™‚ E4.0 man golfing +1F3CC FE0F 200D 2642 ; minimally-qualified # ๐ŸŒ๏ธโ€โ™‚ E4.0 man golfing 1F3CC 200D 2642 ; unqualified # ๐ŸŒโ€โ™‚ E4.0 man golfing 1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # ๐ŸŒ๐Ÿปโ€โ™‚๏ธ E4.0 man golfing: light skin tone 1F3CC 1F3FB 200D 2642 ; minimally-qualified # ๐ŸŒ๐Ÿปโ€โ™‚ E4.0 man golfing: light skin tone @@ -2316,7 +2333,7 @@ 1F3CC 1F3FF 200D 2642 ; minimally-qualified # ๐ŸŒ๐Ÿฟโ€โ™‚ E4.0 man golfing: dark skin tone 1F3CC FE0F 200D 2640 FE0F ; fully-qualified # ๐ŸŒ๏ธโ€โ™€๏ธ E4.0 woman golfing 1F3CC 200D 2640 FE0F ; unqualified # ๐ŸŒโ€โ™€๏ธ E4.0 woman golfing -1F3CC FE0F 200D 2640 ; unqualified # ๐ŸŒ๏ธโ€โ™€ E4.0 woman golfing +1F3CC FE0F 200D 2640 ; minimally-qualified # ๐ŸŒ๏ธโ€โ™€ E4.0 woman golfing 1F3CC 200D 2640 ; unqualified # ๐ŸŒโ€โ™€ E4.0 woman golfing 1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # ๐ŸŒ๐Ÿปโ€โ™€๏ธ E4.0 woman golfing: light skin tone 1F3CC 1F3FB 200D 2640 ; minimally-qualified # ๐ŸŒ๐Ÿปโ€โ™€ E4.0 woman golfing: light skin tone @@ -2427,7 +2444,7 @@ 26F9 1F3FF ; fully-qualified # โ›น๐Ÿฟ E2.0 person bouncing ball: dark skin tone 26F9 FE0F 200D 2642 FE0F ; fully-qualified # โ›น๏ธโ€โ™‚๏ธ E4.0 man bouncing ball 26F9 200D 2642 FE0F ; unqualified # โ›นโ€โ™‚๏ธ E4.0 man bouncing ball -26F9 FE0F 200D 2642 ; unqualified # โ›น๏ธโ€โ™‚ E4.0 man bouncing ball +26F9 FE0F 200D 2642 ; minimally-qualified # โ›น๏ธโ€โ™‚ E4.0 man bouncing ball 26F9 200D 2642 ; unqualified # โ›นโ€โ™‚ E4.0 man bouncing ball 26F9 1F3FB 200D 2642 FE0F ; fully-qualified # โ›น๐Ÿปโ€โ™‚๏ธ E4.0 man bouncing ball: light skin tone 26F9 1F3FB 200D 2642 ; minimally-qualified # โ›น๐Ÿปโ€โ™‚ E4.0 man bouncing ball: light skin tone @@ -2441,7 +2458,7 @@ 26F9 1F3FF 200D 2642 ; minimally-qualified # โ›น๐Ÿฟโ€โ™‚ E4.0 man bouncing ball: dark skin tone 26F9 FE0F 200D 2640 FE0F ; fully-qualified # โ›น๏ธโ€โ™€๏ธ E4.0 woman bouncing ball 26F9 200D 2640 FE0F ; unqualified # โ›นโ€โ™€๏ธ E4.0 woman bouncing ball -26F9 FE0F 200D 2640 ; unqualified # โ›น๏ธโ€โ™€ E4.0 woman bouncing ball +26F9 FE0F 200D 2640 ; minimally-qualified # โ›น๏ธโ€โ™€ E4.0 woman bouncing ball 26F9 200D 2640 ; unqualified # โ›นโ€โ™€ E4.0 woman bouncing ball 26F9 1F3FB 200D 2640 FE0F ; fully-qualified # โ›น๐Ÿปโ€โ™€๏ธ E4.0 woman bouncing ball: light skin tone 26F9 1F3FB 200D 2640 ; minimally-qualified # โ›น๐Ÿปโ€โ™€ E4.0 woman bouncing ball: light skin tone @@ -2462,7 +2479,7 @@ 1F3CB 1F3FF ; fully-qualified # ๐Ÿ‹๐Ÿฟ E2.0 person lifting weights: dark skin tone 1F3CB FE0F 200D 2642 FE0F ; fully-qualified # ๐Ÿ‹๏ธโ€โ™‚๏ธ E4.0 man lifting weights 1F3CB 200D 2642 FE0F ; unqualified # ๐Ÿ‹โ€โ™‚๏ธ E4.0 man lifting weights -1F3CB FE0F 200D 2642 ; unqualified # ๐Ÿ‹๏ธโ€โ™‚ E4.0 man lifting weights +1F3CB FE0F 200D 2642 ; minimally-qualified # ๐Ÿ‹๏ธโ€โ™‚ E4.0 man lifting weights 1F3CB 200D 2642 ; unqualified # ๐Ÿ‹โ€โ™‚ E4.0 man lifting weights 1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # ๐Ÿ‹๐Ÿปโ€โ™‚๏ธ E4.0 man lifting weights: light skin tone 1F3CB 1F3FB 200D 2642 ; minimally-qualified # ๐Ÿ‹๐Ÿปโ€โ™‚ E4.0 man lifting weights: light skin tone @@ -2476,7 +2493,7 @@ 1F3CB 1F3FF 200D 2642 ; minimally-qualified # ๐Ÿ‹๐Ÿฟโ€โ™‚ E4.0 man lifting weights: dark skin tone 1F3CB FE0F 200D 2640 FE0F ; fully-qualified # ๐Ÿ‹๏ธโ€โ™€๏ธ E4.0 woman lifting weights 1F3CB 200D 2640 FE0F ; unqualified # ๐Ÿ‹โ€โ™€๏ธ E4.0 woman lifting weights -1F3CB FE0F 200D 2640 ; unqualified # ๐Ÿ‹๏ธโ€โ™€ E4.0 woman lifting weights +1F3CB FE0F 200D 2640 ; minimally-qualified # ๐Ÿ‹๏ธโ€โ™€ E4.0 woman lifting weights 1F3CB 200D 2640 ; unqualified # ๐Ÿ‹โ€โ™€ E4.0 woman lifting weights 1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # ๐Ÿ‹๐Ÿปโ€โ™€๏ธ E4.0 woman lifting weights: light skin tone 1F3CB 1F3FB 200D 2640 ; minimally-qualified # ๐Ÿ‹๐Ÿปโ€โ™€ E4.0 woman lifting weights: light skin tone @@ -3262,8 +3279,8 @@ 1FAC2 ; fully-qualified # ๐Ÿซ‚ E13.0 people hugging 1F463 ; fully-qualified # ๐Ÿ‘ฃ E0.6 footprints -# People & Body subtotal: 2986 -# People & Body subtotal: 506 w/o modifiers +# People & Body subtotal: 2998 +# People & Body subtotal: 508 w/o modifiers # group: Component @@ -3306,6 +3323,8 @@ 1F405 ; fully-qualified # ๐Ÿ… E1.0 tiger 1F406 ; fully-qualified # ๐Ÿ† E1.0 leopard 1F434 ; fully-qualified # ๐Ÿด E0.6 horse face +1FACE ; fully-qualified # ๐ŸซŽ E15.0 moose +1FACF ; fully-qualified # ๐Ÿซ E15.0 donkey 1F40E ; fully-qualified # ๐ŸŽ E0.6 horse 1F984 ; fully-qualified # ๐Ÿฆ„ E1.0 unicorn 1F993 ; fully-qualified # ๐Ÿฆ“ E5.0 zebra @@ -3373,6 +3392,9 @@ 1F9A9 ; fully-qualified # ๐Ÿฆฉ E12.0 flamingo 1F99A ; fully-qualified # ๐Ÿฆš E11.0 peacock 1F99C ; fully-qualified # ๐Ÿฆœ E11.0 parrot +1FABD ; fully-qualified # ๐Ÿชฝ E15.0 wing +1F426 200D 2B1B ; fully-qualified # ๐Ÿฆโ€โฌ› E15.0 black bird +1FABF ; fully-qualified # ๐Ÿชฟ E15.0 goose # subgroup: animal-amphibian 1F438 ; fully-qualified # ๐Ÿธ E0.6 frog @@ -3399,6 +3421,7 @@ 1F419 ; fully-qualified # ๐Ÿ™ E0.6 octopus 1F41A ; fully-qualified # ๐Ÿš E0.6 spiral shell 1FAB8 ; fully-qualified # ๐Ÿชธ E14.0 coral +1FABC ; fully-qualified # ๐Ÿชผ E15.0 jellyfish # subgroup: animal-bug 1F40C ; fully-qualified # ๐ŸŒ E0.6 snail @@ -3433,6 +3456,7 @@ 1F33B ; fully-qualified # ๐ŸŒป E0.6 sunflower 1F33C ; fully-qualified # ๐ŸŒผ E0.6 blossom 1F337 ; fully-qualified # ๐ŸŒท E0.6 tulip +1FABB ; fully-qualified # ๐Ÿชป E15.0 hyacinth # subgroup: plant-other 1F331 ; fully-qualified # ๐ŸŒฑ E0.6 seedling @@ -3451,9 +3475,10 @@ 1F343 ; fully-qualified # ๐Ÿƒ E0.6 leaf fluttering in wind 1FAB9 ; fully-qualified # ๐Ÿชน E14.0 empty nest 1FABA ; fully-qualified # ๐Ÿชบ E14.0 nest with eggs +1F344 ; fully-qualified # ๐Ÿ„ E0.6 mushroom -# Animals & Nature subtotal: 151 -# Animals & Nature subtotal: 151 w/o modifiers +# Animals & Nature subtotal: 159 +# Animals & Nature subtotal: 159 w/o modifiers # group: Food & Drink @@ -3492,10 +3517,11 @@ 1F966 ; fully-qualified # ๐Ÿฅฆ E5.0 broccoli 1F9C4 ; fully-qualified # ๐Ÿง„ E12.0 garlic 1F9C5 ; fully-qualified # ๐Ÿง… E12.0 onion -1F344 ; fully-qualified # ๐Ÿ„ E0.6 mushroom 1F95C ; fully-qualified # ๐Ÿฅœ E3.0 peanuts 1FAD8 ; fully-qualified # ๐Ÿซ˜ E14.0 beans 1F330 ; fully-qualified # ๐ŸŒฐ E0.6 chestnut +1FADA ; fully-qualified # ๐Ÿซš E15.0 ginger root +1FADB ; fully-qualified # ๐Ÿซ› E15.0 pea pod # subgroup: food-prepared 1F35E ; fully-qualified # ๐Ÿž E0.6 bread @@ -3607,8 +3633,8 @@ 1FAD9 ; fully-qualified # ๐Ÿซ™ E14.0 jar 1F3FA ; fully-qualified # ๐Ÿบ E1.0 amphora -# Food & Drink subtotal: 134 -# Food & Drink subtotal: 134 w/o modifiers +# Food & Drink subtotal: 135 +# Food & Drink subtotal: 135 w/o modifiers # group: Travel & Places @@ -3974,11 +4000,10 @@ 1F3AF ; fully-qualified # ๐ŸŽฏ E0.6 bullseye 1FA80 ; fully-qualified # ๐Ÿช€ E12.0 yo-yo 1FA81 ; fully-qualified # ๐Ÿช E12.0 kite +1F52B ; fully-qualified # ๐Ÿ”ซ E0.6 water pistol 1F3B1 ; fully-qualified # ๐ŸŽฑ E0.6 pool 8 ball 1F52E ; fully-qualified # ๐Ÿ”ฎ E0.6 crystal ball 1FA84 ; fully-qualified # ๐Ÿช„ E13.0 magic wand -1F9FF ; fully-qualified # ๐Ÿงฟ E11.0 nazar amulet -1FAAC ; fully-qualified # ๐Ÿชฌ E14.0 hamsa 1F3AE ; fully-qualified # ๐ŸŽฎ E0.6 video game 1F579 FE0F ; fully-qualified # ๐Ÿ•น๏ธ E0.7 joystick 1F579 ; unqualified # ๐Ÿ•น E0.7 joystick @@ -4013,8 +4038,8 @@ 1F9F6 ; fully-qualified # ๐Ÿงถ E11.0 yarn 1FAA2 ; fully-qualified # ๐Ÿชข E13.0 knot -# Activities subtotal: 97 -# Activities subtotal: 97 w/o modifiers +# Activities subtotal: 96 +# Activities subtotal: 96 w/o modifiers # group: Objects @@ -4040,6 +4065,7 @@ 1FA73 ; fully-qualified # ๐Ÿฉณ E12.0 shorts 1F459 ; fully-qualified # ๐Ÿ‘™ E0.6 bikini 1F45A ; fully-qualified # ๐Ÿ‘š E0.6 womanโ€™s clothes +1FAAD ; fully-qualified # ๐Ÿชญ E15.0 folding hand fan 1F45B ; fully-qualified # ๐Ÿ‘› E0.6 purse 1F45C ; fully-qualified # ๐Ÿ‘œ E0.6 handbag 1F45D ; fully-qualified # ๐Ÿ‘ E0.6 clutch bag @@ -4055,6 +4081,7 @@ 1F461 ; fully-qualified # ๐Ÿ‘ก E0.6 womanโ€™s sandal 1FA70 ; fully-qualified # ๐Ÿฉฐ E12.0 ballet shoes 1F462 ; fully-qualified # ๐Ÿ‘ข E0.6 womanโ€™s boot +1FAAE ; fully-qualified # ๐Ÿชฎ E15.0 hair pick 1F451 ; fully-qualified # ๐Ÿ‘‘ E0.6 crown 1F452 ; fully-qualified # ๐Ÿ‘’ E0.6 womanโ€™s hat 1F3A9 ; fully-qualified # ๐ŸŽฉ E0.6 top hat @@ -4103,6 +4130,8 @@ 1FA95 ; fully-qualified # ๐Ÿช• E12.0 banjo 1F941 ; fully-qualified # ๐Ÿฅ E3.0 drum 1FA98 ; fully-qualified # ๐Ÿช˜ E13.0 long drum +1FA87 ; fully-qualified # ๐Ÿช‡ E15.0 maracas +1FA88 ; fully-qualified # ๐Ÿชˆ E15.0 flute # subgroup: phone 1F4F1 ; fully-qualified # ๐Ÿ“ฑ E0.6 mobile phone @@ -4275,7 +4304,7 @@ 1F5E1 ; unqualified # ๐Ÿ—ก E0.7 dagger 2694 FE0F ; fully-qualified # โš”๏ธ E1.0 crossed swords 2694 ; unqualified # โš” E1.0 crossed swords -1F52B ; fully-qualified # ๐Ÿ”ซ E0.6 water pistol +1F4A3 ; fully-qualified # ๐Ÿ’ฃ E0.6 bomb 1FA83 ; fully-qualified # ๐Ÿชƒ E13.0 boomerang 1F3F9 ; fully-qualified # ๐Ÿน E1.0 bow and arrow 1F6E1 FE0F ; fully-qualified # ๐Ÿ›ก๏ธ E0.7 shield @@ -4354,12 +4383,14 @@ 1FAA6 ; fully-qualified # ๐Ÿชฆ E13.0 headstone 26B1 FE0F ; fully-qualified # โšฑ๏ธ E1.0 funeral urn 26B1 ; unqualified # โšฑ E1.0 funeral urn +1F9FF ; fully-qualified # ๐Ÿงฟ E11.0 nazar amulet +1FAAC ; fully-qualified # ๐Ÿชฌ E14.0 hamsa 1F5FF ; fully-qualified # ๐Ÿ—ฟ E0.6 moai 1FAA7 ; fully-qualified # ๐Ÿชง E13.0 placard 1FAAA ; fully-qualified # ๐Ÿชช E14.0 identification card -# Objects subtotal: 304 -# Objects subtotal: 304 w/o modifiers +# Objects subtotal: 310 +# Objects subtotal: 310 w/o modifiers # group: Symbols @@ -4455,6 +4486,7 @@ 262E ; unqualified # โ˜ฎ E1.0 peace symbol 1F54E ; fully-qualified # ๐Ÿ•Ž E1.0 menorah 1F52F ; fully-qualified # ๐Ÿ”ฏ E0.6 dotted six-pointed star +1FAAF ; fully-qualified # ๐Ÿชฏ E15.0 khanda # subgroup: zodiac 2648 ; fully-qualified # โ™ˆ E0.6 Aries @@ -4503,6 +4535,7 @@ 1F505 ; fully-qualified # ๐Ÿ”… E1.0 dim button 1F506 ; fully-qualified # ๐Ÿ”† E1.0 bright button 1F4F6 ; fully-qualified # ๐Ÿ“ถ E0.6 antenna bars +1F6DC ; fully-qualified # ๐Ÿ›œ E15.0 wireless 1F4F3 ; fully-qualified # ๐Ÿ“ณ E0.6 vibration mode 1F4F4 ; fully-qualified # ๐Ÿ“ด E0.6 mobile phone off @@ -4693,8 +4726,8 @@ 1F533 ; fully-qualified # ๐Ÿ”ณ E0.6 white square button 1F532 ; fully-qualified # ๐Ÿ”ฒ E0.6 black square button -# Symbols subtotal: 302 -# Symbols subtotal: 302 w/o modifiers +# Symbols subtotal: 304 +# Symbols subtotal: 304 w/o modifiers # group: Flags @@ -4709,7 +4742,7 @@ 1F3F3 200D 1F308 ; unqualified # ๐Ÿณโ€๐ŸŒˆ E4.0 rainbow flag 1F3F3 FE0F 200D 26A7 FE0F ; fully-qualified # ๐Ÿณ๏ธโ€โšง๏ธ E13.0 transgender flag 1F3F3 200D 26A7 FE0F ; unqualified # ๐Ÿณโ€โšง๏ธ E13.0 transgender flag -1F3F3 FE0F 200D 26A7 ; unqualified # ๐Ÿณ๏ธโ€โšง E13.0 transgender flag +1F3F3 FE0F 200D 26A7 ; minimally-qualified # ๐Ÿณ๏ธโ€โšง E13.0 transgender flag 1F3F3 200D 26A7 ; unqualified # ๐Ÿณโ€โšง E13.0 transgender flag 1F3F4 200D 2620 FE0F ; fully-qualified # ๐Ÿดโ€โ˜ ๏ธ E11.0 pirate flag 1F3F4 200D 2620 ; minimally-qualified # ๐Ÿดโ€โ˜  E11.0 pirate flag @@ -4983,9 +5016,9 @@ # Flags subtotal: 275 w/o modifiers # Status Counts -# fully-qualified : 3624 -# minimally-qualified : 817 -# unqualified : 252 +# fully-qualified : 3655 +# minimally-qualified : 827 +# unqualified : 242 # component : 9 #EOF diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 00af77f57..a75d85c47 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -145,7 +145,7 @@ defp warn_on_no_object_preloaded(ap_id) do Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") end - def normalize(_, options \\ [fetch: false]) + def normalize(_, options \\ [fetch: false, id_only: false]) # If we pass an Activity to Object.normalize(), we can try to use the preloaded object. # Use this whenever possible, especially when walking graphs in an O(N) loop! @@ -173,10 +173,15 @@ def normalize(%Activity{data: %{"object" => ap_id}}, options) do def normalize(%{"id" => ap_id}, options), do: normalize(ap_id, options) def normalize(ap_id, options) when is_binary(ap_id) do - if Keyword.get(options, :fetch) do - Fetcher.fetch_object_from_id!(ap_id, options) - else - get_cached_by_ap_id(ap_id) + cond do + Keyword.get(options, :id_only) -> + ap_id + + Keyword.get(options, :fetch) -> + Fetcher.fetch_object_from_id!(ap_id, options) + + true -> + get_cached_by_ap_id(ap_id) end end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 3db65f261..770557858 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -153,7 +153,7 @@ def add_to_index(activity) do ) with {:ok, res} <- result, - true <- Map.has_key?(res, "uid") do + true <- Map.has_key?(res, "taskUid") do # Do nothing else _ -> diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index a71a504b3..043a0643e 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -66,9 +66,8 @@ def refetch_public_key(conn) do end end - def sign(%User{} = user, headers) do - with {:ok, %{keys: keys}} <- User.ensure_keys_present(user), - {:ok, private_key, _} <- Keys.keys_from_pem(keys) do + def sign(%User{keys: keys} = user, headers) do + with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers) end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 4383f8f53..700cab2b5 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -165,6 +165,8 @@ defmodule Pleroma.User do has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id) has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id) + has_many(:frontend_profiles, Pleroma.Akkoma.FrontendSettingsProfile) + for {relationship_type, [ {outgoing_relation, outgoing_relation_target}, @@ -681,9 +683,9 @@ def register_changeset_ldap(struct, params = %{password: password}) |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames])) |> validate_format(:nickname, local_nickname_regex()) |> put_ap_id() - |> put_keys() |> unique_constraint(:ap_id) |> put_following_and_follower_and_featured_address() + |> put_private_key() end def register_changeset(struct, params \\ %{}, opts \\ []) do @@ -741,10 +743,10 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do |> validate_length(:registration_reason, max: reason_limit) |> maybe_validate_required_email(opts[:external]) |> put_password_hash - |> put_keys() |> put_ap_id() |> unique_constraint(:ap_id) |> put_following_and_follower_and_featured_address() + |> put_private_key() end def maybe_validate_required_email(changeset, true), do: changeset @@ -757,11 +759,6 @@ def maybe_validate_required_email(changeset, _) do end end - def put_keys(changeset) do - {:ok, pem} = Keys.generate_rsa_pem() - put_change(changeset, :keys, pem) - end - def put_ap_id(changeset) do ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)}) put_change(changeset, :ap_id, ap_id) @@ -779,6 +776,11 @@ def put_following_and_follower_and_featured_address(changeset) do |> put_change(:featured_address, featured) end + defp put_private_key(changeset) do + {:ok, pem} = Keys.generate_rsa_pem() + put_change(changeset, :keys, pem) + end + defp autofollow_users(user) do candidates = Config.get([:instance, :autofollowed_nicknames]) @@ -1955,6 +1957,7 @@ defp create_service_actor(uri, nickname) do follower_address: uri <> "/followers" } |> change + |> put_private_key() |> unique_constraint(:nickname) |> Repo.insert() |> set_cache() @@ -1987,7 +1990,8 @@ def ap_enabled?(_), do: false @doc "Gets or fetch a user by uri or nickname." @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()} - def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri) + def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri) + def get_or_fetch("https://" <> _host = uri), do: get_or_fetch_by_ap_id(uri) def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname) # wait a period of time and return newest version of the User structs @@ -2220,17 +2224,6 @@ def get_mascot(%{mascot: mascot}) when is_nil(mascot) do } end - def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user} - - def ensure_keys_present(%User{} = user) do - with {:ok, pem} <- Keys.generate_rsa_pem() do - user - |> cast(%{keys: pem}, [:keys]) - |> validate_required([:keys]) - |> update_and_set_cache() - end - end - def get_ap_ids_by_nicknames(nicknames) do from(u in User, where: u.nickname in ^nicknames, diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index a4f6abca2..6b3f58999 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -94,6 +94,7 @@ defp search_query(query_string, for_user, following, top_user_ids) do |> subquery() |> order_by(desc: :search_rank) |> maybe_restrict_local(for_user) + |> filter_deactivated_users() end defp select_top_users(query, top_user_ids) do @@ -166,6 +167,10 @@ defp filter_internal_users(query) do from(q in query, where: q.actor_type != "Application") end + defp filter_deactivated_users(query) do + from(q in query, where: q.is_active == true) + end + defp filter_blocked_user(query, %User{} = blocker) do query |> join(:left, [u], b in Pleroma.UserRelationship, diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 1eb0a3620..c07f91b2e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -66,8 +66,7 @@ defp relay_active?(conn, _) do end def user(conn, %{"nickname" => nickname}) do - with %User{local: true} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- User.ensure_keys_present(user) do + with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) @@ -174,7 +173,6 @@ def relay_following(conn, _params) do def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user), {:show_follows, true} <- {:show_follows, (for_user && for_user == user) || !user.hide_follows} do {page, _} = Integer.parse(page) @@ -192,8 +190,7 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "p end def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname(nickname), - {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) @@ -213,7 +210,6 @@ def relay_followers(conn, _params) do def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user), {:show_followers, true} <- {:show_followers, (for_user && for_user == user) || !user.hide_followers} do {page, _} = Integer.parse(page) @@ -231,8 +227,7 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "p end def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname(nickname), - {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) @@ -245,8 +240,7 @@ def outbox( %{"nickname" => nickname, "page" => page?} = params ) when page? in [true, "true"] do - with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- User.ensure_keys_present(user) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do # "include_poll_votes" is a hack because postgres generates inefficient # queries when filtering by 'Answer', poll votes will be hidden by the # visibility filter in this case anyway @@ -270,8 +264,7 @@ def outbox( end def outbox(conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- User.ensure_keys_present(user) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) @@ -328,14 +321,10 @@ defp post_inbox_relayed_create(conn, params) do end defp represent_service_actor(%User{} = user, conn) do - with {:ok, user} <- User.ensure_keys_present(user) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("user.json", %{user: user}) - else - nil -> {:error, :not_found} - end + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("user.json", %{user: user}) end defp represent_service_actor(nil, _), do: {:error, :not_found} @@ -388,12 +377,10 @@ def read_inbox( def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{ "nickname" => nickname }) do - with {:ok, user} <- User.ensure_keys_present(user) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"}) - end + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"}) end def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{ @@ -530,19 +517,6 @@ defp set_requester_reachable(%Plug.Conn{} = conn, _) do conn end - defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do - {:ok, new_user} = User.ensure_keys_present(user) - - for_user = - if new_user != user and match?(%User{}, for_user) do - User.get_cached_by_nickname(for_user.nickname) - else - for_user - end - - {new_user, for_user} - end - def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do with {:ok, object} <- ActivityPub.upload( diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex index d9b59406c..29e2bbc81 100644 --- a/lib/pleroma/web/activity_pub/views/object_view.ex +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -29,11 +29,11 @@ def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} def render("object.json", %{object: %Activity{} = activity}) do base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() - object = Object.normalize(activity, fetch: false) + object_id = Object.normalize(activity, id_only: true) additional = Transmogrifier.prepare_object(activity.data) - |> Map.put("object", object.data["id"]) + |> Map.put("object", object_id) Map.merge(base, additional) end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 760515f34..310f3ce3e 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -34,7 +34,6 @@ def render("endpoints.json", %{user: %User{local: true} = _user}) do def render("endpoints.json", _), do: %{} def render("service.json", %{user: user}) do - {:ok, user} = User.ensure_keys_present(user) {:ok, _, public_key} = Keys.keys_from_pem(user.keys) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_encode([public_key]) @@ -71,7 +70,6 @@ def render("user.json", %{user: %User{nickname: "internal." <> _} = user}), do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname) def render("user.json", %{user: user}) do - {:ok, user} = User.ensure_keys_present(user) {:ok, _, public_key} = Keys.keys_from_pem(user.keys) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_encode([public_key]) diff --git a/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex b/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex new file mode 100644 index 000000000..c13ff9096 --- /dev/null +++ b/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex @@ -0,0 +1,96 @@ +defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsController do + use Pleroma.Web, :controller + + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Akkoma.FrontendSettingsProfile + + @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} + plug( + OAuthScopesPlug, + %{@unauthenticated_access | scopes: ["read:accounts"]} + when action in [ + :list_profiles, + :get_profile + ] + ) + + plug( + OAuthScopesPlug, + %{@unauthenticated_access | scopes: ["write:accounts"]} + when action in [ + :update_profile, + :delete_profile + ] + ) + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FrontendSettingsOperation + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @doc "GET /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" + def get_profile(conn, %{frontend_name: frontend_name, profile_name: profile_name}) do + with %FrontendSettingsProfile{} = profile <- + FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + conn.assigns.user, + frontend_name, + profile_name + ) do + conn + |> json(%{ + settings: profile.settings, + version: profile.version + }) + else + nil -> {:error, :not_found} + end + end + + @doc "GET /api/v1/akkoma/frontend_settings/:frontend_name" + def list_profiles(conn, %{frontend_name: frontend_name}) do + with profiles <- + FrontendSettingsProfile.get_all_by_user_and_frontend_name( + conn.assigns.user, + frontend_name + ), + data <- + Enum.map(profiles, fn profile -> + %{name: profile.profile_name, version: profile.version} + end) do + json(conn, data) + end + end + + @doc "DELETE /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" + def delete_profile(conn, %{frontend_name: frontend_name, profile_name: profile_name}) do + with %FrontendSettingsProfile{} = profile <- + FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + conn.assigns.user, + frontend_name, + profile_name + ), + {:ok, _} <- FrontendSettingsProfile.delete_profile(profile) do + json(conn, %{deleted: "ok"}) + else + nil -> {:error, :not_found} + end + end + + @doc "PUT /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" + def update_profile(%{body_params: %{settings: settings, version: version}} = conn, %{ + frontend_name: frontend_name, + profile_name: profile_name + }) do + with {:ok, profile} <- + FrontendSettingsProfile.create_or_update( + conn.assigns.user, + frontend_name, + profile_name, + settings, + version + ) do + conn + |> json(profile.settings) + end + end +end diff --git a/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex b/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex new file mode 100644 index 000000000..40e81ad55 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex @@ -0,0 +1,133 @@ +defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + import Pleroma.Web.ApiSpec.Helpers + + @spec open_api_operation(atom) :: Operation.t() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + @spec list_profiles_operation() :: Operation.t() + def list_profiles_operation() do + %Operation{ + tags: ["Retrieve frontend setting profiles"], + summary: "Frontend Settings Profiles", + description: "List frontend setting profiles", + operationId: "AkkomaAPI.FrontendSettingsController.list_profiles", + parameters: [frontend_name_param()], + security: [%{"oAuth" => ["read:accounts"]}], + responses: %{ + 200 => + Operation.response("Profiles", "application/json", %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + name: %Schema{type: :string}, + version: %Schema{type: :integer} + } + } + }) + } + } + end + + @spec get_profile_operation() :: Operation.t() + def get_profile_operation() do + %Operation{ + tags: ["Retrieve frontend setting profile"], + summary: "Frontend Settings Profile", + description: "Get frontend setting profile", + operationId: "AkkomaAPI.FrontendSettingsController.get_profile", + security: [%{"oAuth" => ["read:accounts"]}], + parameters: [frontend_name_param(), profile_name_param()], + responses: %{ + 200 => + Operation.response("Profile", "application/json", %Schema{ + type: :object, + properties: %{ + "version" => %Schema{type: :integer}, + "settings" => %Schema{type: :object, additionalProperties: true} + } + }), + 404 => Operation.response("Not Found", "application/json", %Schema{type: :object}) + } + } + end + + @spec delete_profile_operation() :: Operation.t() + def delete_profile_operation() do + %Operation{ + tags: ["Delete frontend setting profile"], + summary: "Delete frontend Settings Profile", + description: "Delete frontend setting profile", + operationId: "AkkomaAPI.FrontendSettingsController.delete_profile", + security: [%{"oAuth" => ["write:accounts"]}], + parameters: [frontend_name_param(), profile_name_param()], + responses: %{ + 200 => Operation.response("Empty", "application/json", %Schema{type: :object}), + 404 => Operation.response("Not Found", "application/json", %Schema{type: :object}) + } + } + end + + @spec update_profile_operation() :: Operation.t() + def update_profile_operation() do + %Operation{ + tags: ["Update frontend setting profile"], + summary: "Frontend Settings Profile", + description: "Update frontend setting profile", + operationId: "AkkomaAPI.FrontendSettingsController.update_profile_operation", + security: [%{"oAuth" => ["write:accounts"]}], + parameters: [frontend_name_param(), profile_name_param()], + requestBody: profile_body_param(), + responses: %{ + 200 => Operation.response("Settings", "application/json", %Schema{type: :object}), + 422 => Operation.response("Invalid", "application/json", %Schema{type: :object}) + } + } + end + + def frontend_name_param do + Operation.parameter(:frontend_name, :path, :string, "Frontend name", + example: "pleroma-fe", + required: true + ) + end + + def profile_name_param do + Operation.parameter(:profile_name, :path, :string, "Profile name", + example: "mobile", + required: true + ) + end + + def profile_body_param do + request_body( + "Settings", + %Schema{ + title: "Frontend Setting Profile", + type: :object, + required: [:version, :settings], + properties: %{ + version: %Schema{ + type: :integer, + description: "Version of the profile, must increment by 1 each time", + example: 1 + }, + settings: %Schema{ + type: :object, + description: "Settings of the profile", + example: %{ + theme: "dark", + locale: "en" + } + } + } + }, + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex index c78e9780f..9af556736 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex @@ -16,7 +16,7 @@ def index_operation do %Operation{ tags: ["Backups"], summary: "List backups", - security: [%{"oAuth" => ["read:account"]}], + security: [%{"oAuth" => ["read:backups"]}], operationId: "PleromaAPI.BackupController.index", responses: %{ 200 => @@ -37,7 +37,7 @@ def create_operation do %Operation{ tags: ["Backups"], summary: "Create a backup", - security: [%{"oAuth" => ["read:account"]}], + security: [%{"oAuth" => ["read:backups"]}], operationId: "PleromaAPI.BackupController.create", responses: %{ 200 => diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index 82fb9e4e0..bc61130f1 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -69,10 +69,8 @@ def perform(:publish_one, module, params) do def perform(:publish, activity) do Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) - with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]), - {:ok, actor} <- User.ensure_keys_present(actor) do - Publisher.publish(actor, activity) - end + %User{} = actor = User.get_cached_by_ap_id(activity.data["actor"]) + Publisher.publish(actor, activity) end def perform(:incoming_ap_doc, params) do diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index caca42934..8990bef54 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.Metadata.Utils do alias Pleroma.Formatter alias Pleroma.HTML - def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do - content + defp scrub_html_and_truncate_object_field(field, object) do + field # html content comes from DB already encoded, decode first and scrub after |> HtmlEntities.decode() |> String.replace(~r//, " ") @@ -19,6 +19,17 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do |> Formatter.truncate() end + def scrub_html_and_truncate(%{data: %{"summary" => summary}} = object) + when is_binary(summary) and summary != "" do + summary + |> scrub_html_and_truncate_object_field(object) + end + + def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do + content + |> scrub_html_and_truncate_object_field(object) + end + def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do content |> scrub_html diff --git a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex index fc5d16771..88f38a911 100644 --- a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupController do alias Pleroma.Web.Plugs.OAuthScopesPlug action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action in [:index, :create]) + plug(OAuthScopesPlug, %{scopes: ["read:backups"]} when action in [:index, :create]) plug(Pleroma.Web.ApiSpec.CastAndValidate) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex index 5e06ac3f6..91f6e9974 100644 --- a/lib/pleroma/web/plugs/o_auth_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_plug.ex @@ -47,15 +47,17 @@ def call(conn, _) do # @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil defp fetch_user_and_token(token) do - query = + token_query = from(t in Token, - where: t.token == ^token, - join: user in assoc(t, :user), - preload: [user: user] + where: t.token == ^token ) - with %Token{user: user} = token_record <- Repo.one(query) do + with %Token{user_id: user_id} = token_record <- Repo.one(token_query), + false <- is_nil(user_id), + %User{} = user <- User.get_cached_by_id(user_id) do {:ok, user, token_record} + else + _ -> nil end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index f722d94f7..838599c4d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -466,6 +466,26 @@ defmodule Pleroma.Web.Router do scope "/api/v1/akkoma", Pleroma.Web.AkkomaAPI do pipe_through(:authenticated_api) get("/translation/languages", TranslationController, :languages) + + get("/frontend_settings/:frontend_name", FrontendSettingsController, :list_profiles) + + get( + "/frontend_settings/:frontend_name/:profile_name", + FrontendSettingsController, + :get_profile + ) + + put( + "/frontend_settings/:frontend_name/:profile_name", + FrontendSettingsController, + :update_profile + ) + + delete( + "/frontend_settings/:frontend_name/:profile_name", + FrontendSettingsController, + :delete_profile + ) end scope "/api/v1", Pleroma.Web.MastodonAPI do diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index b5f2cb72e..f5a46ce25 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -69,8 +69,6 @@ defp gather_aliases(%User{} = user) do end def represent_user(user, "JSON") do - {:ok, user} = User.ensure_keys_present(user) - %{ "subject" => "acct:#{user.nickname}@#{domain()}", "aliases" => gather_aliases(user), @@ -79,8 +77,6 @@ def represent_user(user, "JSON") do end def represent_user(user, "XML") do - {:ok, user} = User.ensure_keys_present(user) - aliases = user |> gather_aliases() diff --git a/mix.exs b/mix.exs index 19e6fd045..c7e66b158 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.2.0"), + version: version("3.3.0"), elixir: "~> 1.12", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), diff --git a/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs b/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs new file mode 100644 index 000000000..43bc7100b --- /dev/null +++ b/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright ยฉ 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.GenerateUnsetUserKeys do + use Ecto.Migration + import Ecto.Query + alias Pleroma.Keys + alias Pleroma.Repo + alias Pleroma.User + + def change do + query = + from(u in User, + where: u.local == true, + where: is_nil(u.keys), + select: u + ) + + Repo.stream(query) + |> Enum.each(fn user -> + with {:ok, pem} <- Keys.generate_rsa_pem() do + Ecto.Changeset.cast(user, %{keys: pem}, [:keys]) + |> Repo.update() + end + end) + end +end diff --git a/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs b/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs new file mode 100644 index 000000000..c1c449719 --- /dev/null +++ b/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs @@ -0,0 +1,29 @@ +defmodule Pleroma.Repo.Migrations.AddUserFrontendProfiles do + use Ecto.Migration + + def up do + create_if_not_exists table("user_frontend_setting_profiles", primary_key: false) do + add(:user_id, references(:users, type: :uuid, on_delete: :delete_all), primary_key: true) + add(:frontend_name, :string, primary_key: true) + add(:profile_name, :string, primary_key: true) + add(:version, :integer) + add(:settings, :map) + timestamps() + end + + create_if_not_exists(index(:user_frontend_setting_profiles, [:user_id, :frontend_name])) + + create_if_not_exists( + unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name]) + ) + end + + def down do + drop_if_exists(table("user_frontend_setting_profiles")) + drop_if_exists(index(:user_frontend_setting_profiles, [:user_id, :frontend_name])) + + drop_if_exists( + unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name]) + ) + end +end diff --git a/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs b/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs index 37880ff99..1d0a6e050 100644 --- a/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs +++ b/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs @@ -1,9 +1,15 @@ defmodule Pleroma.Repo.Migrations.EnsureMastofeSettings do use Ecto.Migration - def change do + def up do alter table(:users) do add_if_not_exists(:mastofe_settings, :map) end end + + def down do + alter table(:users) do + remove_if_exists(:mastofe_settings, :map) + end + end end diff --git a/priv/static/instance/static.css b/priv/static/instance/static.css index 487e1ec27..48c74c125 100644 Binary files a/priv/static/instance/static.css and b/priv/static/instance/static.css differ diff --git a/rel/files/bin/pleroma_ctl b/rel/files/bin/pleroma_ctl index d2949e8fe..e0e6d1b5a 100755 --- a/rel/files/bin/pleroma_ctl +++ b/rel/files/bin/pleroma_ctl @@ -2,28 +2,24 @@ # XXX: This should be removed when elixir's releases get custom command support detect_flavour() { - arch="$(uname -m)" - if [ "$arch" = "x86_64" ]; then - arch="amd64" - elif [ "$arch" = "aarch64" ]; then - arch="arm64" - else - echo "Unsupported arch: $arch" >&2 - exit 1 - fi + arch="amd64" + # Special cases + if grep -qe "VERSION_CODENAME=jammy" /etc/os-release; then + echo "$arch-ubuntu-jammy" + else + if getconf GNU_LIBC_VERSION >/dev/null; then + libc_postfix="" + elif [ "$(ldd 2>&1 | head -c 9)" = "musl libc" ]; then + libc_postfix="-musl" + elif [ "$(find /lib/libc.musl* | wc -l)" ]; then + libc_postfix="-musl" + else + echo "Unsupported libc" >&2 + exit 1 + fi - if getconf GNU_LIBC_VERSION >/dev/null; then - libc_postfix="" - elif [ "$(ldd 2>&1 | head -c 9)" = "musl libc" ]; then - libc_postfix="-musl" - elif [ "$(find /lib/libc.musl* | wc -l)" ]; then - libc_postfix="-musl" - else - echo "Unsupported libc" >&2 - exit 1 - fi - - echo "$arch$libc_postfix" + echo "$arch$libc_postfix" + fi } detect_branch() { diff --git a/test/fixtures/rsa_keys/key_1.pem b/test/fixtures/rsa_keys/key_1.pem new file mode 100644 index 000000000..3da357500 --- /dev/null +++ b/test/fixtures/rsa_keys/key_1.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA2gdPJM5bWarGZ6QujfQ296l1yEQohS5fdtnxYQc+RXuS1gqZ +R/jVGHG25o4tmwyCLClyREU1CBTOCQBsg+BSehXlxNR9fiB4KaVQW9MMNa2vhHuG +f7HLdILiC+SPPTV1Bi8LCpxJowiSpnFPP4BDDeRKib7nOxll9Ln9gEpUueKKabsQ +EQKCmEJYhIz/8g5R0Qz+6VjASdejDjTEdZbr/rwyldRRjIklyeZ3lBzB/c8/51wn +HT2Dt0r9NiapxYC3oNhbE2A+4FU9pZTqS8yc3KqWZAy74snaRO9QQSednKlOJpXP +V3vwWo5CxuSNLttV7zRcrqeYOkIVNF4dQ/bHzQIDAQABAoIBADTCfglnEj4BkF92 +IHnjdgW6cTEUJUYNMba+CKY1LYF85Mx85hi/gzmWEu95yllxznJHWUpiAPJCrpUJ +EDldaDf44pAd53xE+S8CvQ5rZNH8hLOnfKWb7aL1JSRBm9PxAq+LZL2dkkgsg+hZ +FRdFv3Q2IT9x/dyUSdLNyyVnV1dfoya/7zOFc7+TwqlofznzrlBgNoAe8Lb4AN/q +itormPxskqATiq11XtP4F6eQ556eRgHCBxmktx/rRDl6f9G9dvjRQOA2qZlHQdFq +kjOZsrvItL46LdVoLPOdCYG+3HFeKoDUR1NNXEkt66eqmEhLY4MgzGUT1wqXWk7N +XowZc9UCgYEA+L5h4PhANiY5Kd+PkRI8zTlJMv8hFqLK17Q0p9eL+mAyOgXjH9so +QutJf4wU+h6ESDxH+1tCjCN307uUqT7YnT2zHf3b6GcmA+t6ewxfxOY2nJ82HENq +hK1aodnPTvRRRqCGfrx9qUHRTarTzi+2u86zH+KoMHSiuzn4VpQhg4MCgYEA4GOL +1tLR9+hyfYuMFo2CtQjp3KpJeGNKEqc33vFD05xJQX+m5THamBv8vzdVlVrMh/7j +iV85mlA7HaaP+r5DGwtonw9bqY76lYRgJJprsS5lHcRnXsDmU4Ne8RdB3dHNsT5P +n4P6v8y4jaT638iJ/qLt4e8itOBlZwS//VIglm8CgYEA7KXD3RKRlHK9A7drkOs2 +6VBM8bWEN1LdhGYvilcpFyUZ49XiBVatcS0EGdKdym/qDgc7vElQgJ7ly4y0nGfs +EXy3whrYcrxfkG8hcZuOKXeUEWHvSuhgmKWMilr8PfN2t6jVDBIrwzGY/Tk+lPUT +9o1qITW0KZVtlI5MU6JOWB0CgYAHwwnETZibxbuoIhqfcRezYXKNgop2EqEuUgB5 +wsjA2igijuLcDMRt/JHan3RjbTekAKooR1X7w4i39toGJ2y008kzr1lRXTPH1kNp +ILpW767pv7B/s5aEDwhKuK47mRVPa0Nf1jXnSpKbu7g943b6ivJFnXsK3LRFQwHN +JnkgGwKBgGUleQVd2GPr1dkqLVOF/s2aNB/+h2b1WFWwq0YTnW81OLwAcUVE4p58 +3GQgz8PCsWbNdTb9yFY5fq0fXgi0+T54FEoZWH09DrOepA433llAwI6sq7egrFdr +kKQttZMzs6ST9q/IOF4wgqSnBjjTC06vKSkNAlXJz+LMvIRMeBr0 +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/rsa_keys/key_2.pem b/test/fixtures/rsa_keys/key_2.pem new file mode 100644 index 000000000..7a8e8e670 --- /dev/null +++ b/test/fixtures/rsa_keys/key_2.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAwu0VqVGRVDW09V3zZ0+08K9HMKivIzIInO0xim3jbfVcg8r1 +sR7vNLorYAB6TDDlXYAWKx1OxUMZusbOigrpQd+5wy8VdCogDD7qk4bbZ+NjXkuD +ETzrQsGWUXe+IdeH8L0Zh0bGjbarCuA0qAeY1TEteGl+Qwo2dsrBUH7yKmWO6Mz9 +XfPshrIDOGo4QNyVfEBNGq2K9eRrQUHeAPcM2/qu4ZAZRK+VCifDZrF8ZNpoAsnS +R2mJDhOBUMvI/ZaxOc2ry4EzwcS4uBaM2wONkGWDaqO6jNAQflaX7vtzOAeJB7Dt +VKXUUcZAGN7uI3c2mG5IKGMhTYUtUdrzmqmtZwIDAQABAoIBAQCHBJfTf3dt4AGn +T9twfSp06MQj9UPS2i5THI0LONCm8qSReX0zoZzJZgbzaYFM0zWczUMNvDA6vR7O +XDTmM2acxW4zv6JZo3Ata0sqwuepDz1eLGnt/8dppxQK/ClL4bH8088h/6k6sgPJ +9cEjfpejXHwFgvT9VM6i/BBpRHVTXWuJqwpDtg+bleQNN3L3RapluDd7BGiKoCwQ +cCTKd+lxTu9gVJkbRTI/Jn3kV+rnedYxHTxVp5cU1qIabsJWBcdDz25mRHupxQsn +JbQR4+ZnRLeAsC6WJZtEJz2KjXgBaYroHbGZY3KcGW95ILqiCJoJJugbW1eABKnN +Q5k8XVspAoGBAPzGJBZuX3c0quorhMIpREmGq2vS6VCQwLhH5qayYYH1LiPDfpdq +69lOROxZodzLxBgTf5z/a5kBF+eNKvOqfZJeRTxmllxxO1MuJQuRLi/b7BHHLuyN +Eea+YwtehA0T0CbD2hydefARNDruor2BLvt/kt6qEoIFiPauTsMfXP39AoGBAMVp +8argtnB+vsk5Z7rpQ4b9gF5QxfNbA0Hpg5wUUdYrUjFr50KWt1iowj6AOVp/EYgr +xRfvOQdYODDH7R5cjgMbwvtpHo39Zwq7ewaiT1sJXnpGmCDVh+pdTHePC5OOXnxN +0USK3M4KjltjVqJo7xPPElgJvCejudD47mtHMaQzAoGBAIFQ/PVc0goyL55NVUXf +xse21cv7wtEsvOuKHT361FegD1LMmN7uHGq32BryYBSNSmzmzMqNAYbtQEV9uxOd +jVBsWg9kjFgOtcMAQIOCapahdExEEoWCRj49+H3AhN4L3Nl4KQWqqs9efdIIc8lv +ZZHU2lZ/u6g5HLDWzASW7wQhAoGAdERPRrqN+HdNWinrA9Q6JxjKL8IWs5rYsksb +biMxh5eAEwdf7oHhfd/2duUB4mCQLMjKjawgxEia33AAIS+VnBMPpQ5mJm4l79Y3 +QNL7Nbyw3gcRtdTM9aT5Ujj3MnJZB5C1PU8jeF4TNZOuBH0UwW/ld+BT5myxFXhm +wtvtSq0CgYEA19b0/7il4Em6uiLOmYUuqaUoFhUPqzjaS6OM/lRAw12coWv/8/1P +cwaNZHNMW9Me/bNH3zcOTz0lxnYp2BeRehjFYVPRuS1GU7uwqKtlL2wCPptTfAhN +aJWIplzUCTg786u+sdNZ0umWRuCLoUpsKTgP/yt4RglzEcfxAuBDljk= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/rsa_keys/key_3.pem b/test/fixtures/rsa_keys/key_3.pem new file mode 100644 index 000000000..fbd25c80f --- /dev/null +++ b/test/fixtures/rsa_keys/key_3.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA0GvzqZ3r78GLa7guGn+palKRLGru4D4jnriHgfrUAJrdLyZ5 +9d0zAA4qnS2L6YAMoPPBhBUtIV5e2sn1+rwTClWU3dm3FyBAeqdeIBKN+04AyrUc +HXYaZtOPJXCTeytzoSQE359Tq6+xwgoHlUWSWxQF51/z/PDQcUvqFjJqAtdiDchd +3CiFRtdjegyxXGnqvPmBix+vEjDytcVydfch+R1Twf6f5EL7a1jFVWNGcratYBEl +nqOWKI2fBu/WA8QlrcVW5zmtZo9aJ6IrFddQgQTxPk/mEHgCzv8tbCRI9TxiXeYH +YqxZFYBW40xbZQwGRjaYHJlIRYp9+TOynW9OZQIDAQABAoIBAQC97cIMDbdVsyAk +N6D70N5H35ofygqJGtdG6o3B6xuKuZVaREvbu4mgQUigF0Nqs5/OhJMSlGGeCOuT +oXug1Abd4gNY7++jCWb43tAtlfsAyaJ7FvPZ/SguEBhgW+hp07z5WWN/jSeoSuFI +G++xHcczbFm88XncRG8O78kQFTz5/DlQYkFXfbqpuS3BqxnrACpDCUfrUwZNYFIp +CUNq21jdifhHwlS0K3PX8A5HdOYeVnVHaE78LGE4oJVHwcokELv+PYqarWZq/a6L +vKU3yn2+4pj2WO490iGQaRKVM35vrtjdVxiWEIUiFc3Jg5fKZA3wuHXoF1N1DpPO +BO6Att55AoGBAP/nC2szmDcnU5Sh8LDeQbL+FpSBwOmFnmel5uqbjKnDzf9emPQu +NFUls1N9OGgyUq08TnmcY/7wLZzcu7Y9XOUURuYtx9nGRs4RmE2VEBhK1r7CkDIx +oOb+NtdqnPtQASAxCHszoGCFxpuV7UVoo2SRgc+M4ceX128arvBUtvdrAoGBANCA +RuO3eelkXaJoCeogEUVWXZ6QmPeYzbMD4vg2DM0ynUbReyuEIIhn+SR7tehlj5ie +4T3ixVdur6k+YUdiFhUYgXaHBJWHoHl1lrU3ZON8n7AeEk9ft6gg4L07ouj78UMZ +sArJIlU5mLnW02zbV9XryU39dIgpQREqC0bIOtVvAoGBAORv1JKq6Rt7ALJy6VCJ +5y4ogfGp7pLHk8NEpuERYDz/rLllMbbwNAk6cV17L8pb+c/pQMhwohcnQiCALxUc +q/tW4X+CqJ+vzu8PZ90Bzu9Qh2iceGpGQTNTBZPA+UeigI7DFqYcTPM9GDE1YiyO +nyUcezvSsI4i7s6gjD+/7+DnAoGABm3+QaV1z/m1XX3B2IN2pOG971bcML54kW2s +QSVBjc5ixT1OhBAGBM7YAwUBnhILtJQptAPbPBAAwMJYs5/VuH7R9zrArG/LRhOX +Oy1jIhTEw+SZgfMcscWZyJwfMPob/Yq8QAjl0yT8jbaPPIsjEUi9I3eOcWh8RjA6 +ussP7WcCgYEAm3yvJR9z6QGoQQwtDbwjyZPYOSgK9wFS/65aupi6cm/Qk2N1YaLY +q2amNrzNsIc9vQwYGEHUwogn4MieHk96V7m2f0Hx9EHCMwizU9EiS6oyiLVowTG6 +YsBgSzcpnt0Vkgil4CQks5uQoan0tubEUQ5DI79lLnb02n4o46iAYK0= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/rsa_keys/key_4.pem b/test/fixtures/rsa_keys/key_4.pem new file mode 100644 index 000000000..f72b29fb1 --- /dev/null +++ b/test/fixtures/rsa_keys/key_4.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAw6MLRbP/henX2JxwdMkQlskKghBoMyUPu9kZpUQ9yYfIm9I4 +a3gEfzef75jKLOSf+BkZulvEUGjC+VnkpV3s+OZCSq81Ykv5PHuTqbj8Cn/dEt/g +lBXxPcOBKWqa+1cDX6QVIVJsBihLB/1b64H3U96Yu9+knmXvT1Az5MFA2KtSq7HJ +O+GJNn0EMI7xwPz/atUGlMLrhzwS4UDpw9CAaRPojplJYl4K1JMCFTgTt3hJILXZ +tw1MKTeeyWzNiuQRBQJuCnqfvsBYsasIlHWfqIL/uBzcGHHCIK5ZW9luntJXyLVj +zzaF7etIJk1uddM2wnqOOaVyqbssZXGt7Tb9IQIDAQABAoIBAH5QJRUKFK8Xvp9C +0nD06NsSTtCPW1e6VCBLGf3Uw7f9DY9d+cOZp/2jooYGNnMp4gdD3ZKvcV8hZNGu +Mqx6qmhB8wdZfLRMrU1Z1Is+vqzgxZJMLiouyKXCNwDQreQd2DXGMUZkew62sUsl +UFYMge4KyL50tUr4Mb0Z4YePJxk804tcqgw0n+D0lR7ZKhSqoQpoMqEiO+27Yw7E +Txj/MKH8f/ZJ6LBLRISOdBOrxonHqqeYWchczykCwojOZc3bIlWZGhg727dFTHDC +yrj3/zsZ2hy+TQsucCFY0RljIbacmHvrF/VqfhTIhg98H0F27V/jiPGsdKhptyst +E9iQVMkCgYEA42ge4H2Wl42sRh61GOrOgzzr0WZS54bF5skMxiGGnLwnb82rwUBt +xw94PRORJbV9l+2fkxbfiW0uzornfN8OBHSB64Pcjzzbl5Qm+eaDOiuTLtakYOWQ +/ipGqw8iE4J9iRteZCo8GnMxWbTkYCporTlFDTeYguXmwR4yCXtlCbMCgYEA3DxM +7R5HMUWRe64ucdekMh742McS8q/X5jdN9iFGy0M8P1WTyspSlaPDXgjaO4XqpRqg +djkL993kCDvOAiDl6Tpdiu1iFcOaRLb19Tj1pm8sKdk6X4d10U9lFri4NVYCmvVi +yOahUYFK/k5bA+1o+KU9Pi82H36H3WNeF4evC9sCgYEAs1zNdc04uQKiTZAs0KFr +DzI+4aOuYjT35ObQr3mD/h2dkV6MSNmzfF1kPfAv/KkgjXN7+H0DBRbb40bF/MTF +/peSXZtcnJGote7Bqzu4Z2o1Ja1ga5jF+uKHaKZ//xleQIUYtzJkw4v18cZulrb8 +ZxyTrTAbl6sTjWBuoPH1qGcCgYEAsQNahR9X81dKJpGKTQAYvhw8wOfI5/zD2ArN +g62dXBRPYUxkPJM/q3xzs6oD1eG+BjQPktYpM3FKLf/7haRxhnLd6qL/uiR8Ywx3 +RkEg2EP0yDIMA+o5nSFmS8vuaxgVgf0HCBiuwnbcEuhhqRdxzp/pSIjjxI6LnzqV +zu3EmQ8CgYEAhq8Uhvw+79tK7q2PCjDbiucA0n/4a3aguuvRoEh7F93Pf6VGZmT+ +Yld54Cd4P5ATI3r5YdD+JBuvgNMOTVPCaD/WpjbJKnrpNEXtXRQD6LzAXZDNk0sF +IO9i4gjhBolRykWn10khoPdxw/34FWBP5SxU1JYk75NQXvI3TD+5xbU= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/rsa_keys/key_5.pem b/test/fixtures/rsa_keys/key_5.pem new file mode 100644 index 000000000..49342b54e --- /dev/null +++ b/test/fixtures/rsa_keys/key_5.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA0jdKtMkgqnEGO3dn4OKxtggfFDzv+ddXToO0cdPXkUgPajCo +UGPunz+A1KmkAmLY0Vwk0tkOmKK8GFHek/5zQ+1N2FHBi19fbwlJk7hzh5OiYRhu +YZi0d6LsqEMKhDk6NqIeiFmOe2YHgklVvZV0hebvHlHLgzDhYrDltSPe33UZa3MS +g2Knf4WQAjLOo2BAb+oyj/UNXeAqaMGcOr6/kAHPcODW2EGhF3H3umFLv7t/Kq5i +WPBgarbCGPR5qq9SW5ZIjS3Sz0dl105Grw8wU23CC/2IBZ5vNiu+bkmLEoh/KpX2 +YBILoLmwtVX0Qxc15CrpOi12p+/4pLR8kuEowQIDAQABAoIBAQDMDQ3AJMdHisSQ +7pvvyDzWRFXesDQE4YmG1gNOxmImTLthyW9n8UjMXbjxNOXVxxtNRdMcs8MeWECa +nsWeBEzgr7VzeBCV9/LL9kjsUgwamyzwcOWcaL0ssAJmZgUMSfx+0akvkzbiAyzg +w8ytZSihXYPYe28/ni/5O1sOFI6feenOnJ9NSmVUA24c9TTJGNQs7XRUMZ8f9wt6 +KwRmYeNDKyqH7NvLmmKoDp6m7bMDQxWArVTAoRWTVApnj35iLQtmSi8DBdw6xSzQ +fKpUe/B4iQmMNxUW7KmolOvCIS5wcYZJE+/j7xshA2GGnOpx4aC+N+w2GSX4Bz/q +OnYSpGUBAoGBAOwnSeg17xlZqmd86qdiCxg0hRtAjwrd7btYq6nkK+t9woXgcV99 +FBS3nLbk/SIdXCW8vHFJTmld60j2q2kdestYBdHznwNZJ4Ee8JhamzcC64wY7O0x +RameO/6uoKS4C3VF+Zc9CCPfZOqYujkGvSqbTjFZWuFtDp0GHDk+qEIRAoGBAOPh ++PCB2QkGgiujSPmuCT5PTuNylAug3D4ZdMRKpQb9Rnzlia1Rpdrihq+PvB2vwa+S +mB6dgb0E7M2AyEMVu5buris0mVpRdmEeLCXR8mYJ48kOslIGArEStXDetfbRaXdK +7vf4APq2d78AQYldU2fYlo754Dh/3MZIguzpqMuxAoGBAIDJqG/AQiYkFV+c62ff +e0d3FQRYv+ngQE9Eu1HKwv0Jt7VFQu8din8F56yC013wfxmBhY+Ot/mUo8VF6RNJ +ZXdSCNKINzcfPwEW+4VLHIzyxbzAty1gCqrHRdbOK4PJb05EnCqTuUW/Bg0+v4hs +GWwMCKe3IG4CCM8vzuKVPjPRAoGBANYCQtJDb3q9ZQPsTb1FxyKAQprx4Lzm7c9Y +AsPRQhhFRaxHuLtPQU5FjK1VdBoBFAl5x2iBDPVhqa348pml0E0Xi/PBav9aH61n +M5i1CUrwoL4SEj9bq61133XHgeXwlnZUpgW0H99T+zMh32pMfea5jfNqETueQMzq +DiLF8SKRAoGBAOFlU0kRZmAx3Y4rhygp1ydPBt5+zfDaGINRWEN7QWjhX2QQan3C +SnXZlP3POXLessKxdCpBDq/RqVQhLea6KJMfP3F0YbohfWHt96WjiriJ0d0ZYVhu +34aUM2UGGG0Kia9OVvftESBaXk02vrY9zU3LAVAv0eLgIADm1kpj85v7 +-----END RSA PRIVATE KEY----- diff --git a/test/pleroma/akkoma/frontend_setting_profile_test.exs b/test/pleroma/akkoma/frontend_setting_profile_test.exs new file mode 100644 index 000000000..4bb1139a8 --- /dev/null +++ b/test/pleroma/akkoma/frontend_setting_profile_test.exs @@ -0,0 +1,196 @@ +defmodule Pleroma.Akkoma.FrontendSettingsProfileTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Akkoma.FrontendSettingsProfile + + import Pleroma.Factory + + describe "changeset/2" do + test "valid" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"test" => "test"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: 1 + } + + assert %{valid?: true} = FrontendSettingsProfile.changeset(struct, attrs) + end + + test "when settings is too long" do + clear_config([:instance, :max_frontend_settings_json_chars], 10) + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"verylong" => "verylongoops"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: 1 + } + + assert %{valid?: false, errors: [settings: {"is too long", _}]} = + FrontendSettingsProfile.changeset(struct, attrs) + end + + test "when frontend name is too short" do + user = insert(:user) + frontend_name = "" + profile_name = "test" + settings = %{"test" => "test"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: 1 + } + + assert %{valid?: false, errors: [frontend_name: {"can't be blank", _}]} = + FrontendSettingsProfile.changeset(struct, attrs) + end + + test "when profile name is too short" do + user = insert(:user) + frontend_name = "test" + profile_name = "" + settings = %{"test" => "test"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: 1 + } + + assert %{valid?: false, errors: [profile_name: {"can't be blank", _}]} = + FrontendSettingsProfile.changeset(struct, attrs) + end + + test "when version is negative" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"test" => "test"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: -1 + } + + assert %{valid?: false, errors: [version: {"must be greater than %{number}", _}]} = + FrontendSettingsProfile.changeset(struct, attrs) + end + end + + describe "create_or_update/2" do + test "it should create a new record" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"test" => "test"} + + assert {:ok, %FrontendSettingsProfile{}} = + FrontendSettingsProfile.create_or_update( + user, + frontend_name, + profile_name, + settings, + 1 + ) + end + + test "it should update a record" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + + insert(:frontend_setting_profile, + user: user, + frontend_name: frontend_name, + profile_name: profile_name, + settings: %{"test" => "test"}, + version: 1 + ) + + settings = %{"test" => "test2"} + + assert {:ok, %FrontendSettingsProfile{settings: ^settings}} = + FrontendSettingsProfile.create_or_update( + user, + frontend_name, + profile_name, + settings, + 2 + ) + end + end + + describe "get_all_by_user_and_frontend_name/2" do + test "it should return all records" do + user = insert(:user) + frontend_name = "test" + + insert(:frontend_setting_profile, + user: user, + frontend_name: frontend_name, + profile_name: "profileA", + settings: %{"test" => "test"}, + version: 1 + ) + + insert(:frontend_setting_profile, + user: user, + frontend_name: frontend_name, + profile_name: "profileB", + settings: %{"test" => "test"}, + version: 1 + ) + + assert [%FrontendSettingsProfile{profile_name: "profileA"}, %{profile_name: "profileB"}] = + FrontendSettingsProfile.get_all_by_user_and_frontend_name(user, frontend_name) + end + end + + describe "get_by_user_and_frontend_name_and_profile_name/3" do + test "it should return a record" do + user = insert(:user) + frontend_name = "test" + profile_name = "profileA" + + insert(:frontend_setting_profile, + user: user, + frontend_name: frontend_name, + profile_name: profile_name, + settings: %{"test" => "test"}, + version: 1 + ) + + assert %FrontendSettingsProfile{profile_name: "profileA"} = + FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + user, + frontend_name, + profile_name + ) + end + end +end diff --git a/test/pleroma/search/meilisearch_test.exs b/test/pleroma/search/meilisearch_test.exs index 04a2d75d9..fe09c9485 100644 --- a/test/pleroma/search/meilisearch_test.exs +++ b/test/pleroma/search/meilisearch_test.exs @@ -47,7 +47,7 @@ test "indexes a local post on creation" do Jason.decode!(body) ) - json(%{updateId: 1}) + json(%{taskUid: 1}) end) {:ok, activity} = @@ -100,11 +100,11 @@ test "deletes posts from index when deleted locally" do Jason.decode!(body) ) - json(%{updateId: 1}) + json(%{taskUid: 1}) %{method: :delete, url: "http://127.0.0.1:7700/indexes/objects/documents/" <> id} -> assert String.length(id) > 1 - json(%{updateId: 2}) + json(%{taskUid: 2}) end) {:ok, activity} = diff --git a/test/pleroma/user_search_test.exs b/test/pleroma/user_search_test.exs index 69167bb0c..8634a2e2b 100644 --- a/test/pleroma/user_search_test.exs +++ b/test/pleroma/user_search_test.exs @@ -65,6 +65,14 @@ test "excludes invisible users from results" do assert found_user.id == user.id end + test "excludes deactivated users from results" do + user = insert(:user, %{nickname: "john t1000"}) + insert(:user, %{is_active: false, nickname: "john t800"}) + + [found_user] = User.search("john") + assert found_user.id == user.id + end + # Note: as in Mastodon, `is_discoverable` doesn't anyhow relate to user searchability test "includes non-discoverable users in results" do insert(:user, %{nickname: "john 3000", is_discoverable: false}) diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 645622e43..0272e3142 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -620,15 +620,15 @@ test "it blocks blacklisted email domains" do assert changeset.valid? end - test "it sets the password_hash, ap_id and PEM key" do + test "it sets the password_hash, ap_id, private key and followers collection address" do changeset = User.register_changeset(%User{}, @full_user_data) assert changeset.valid? assert is_binary(changeset.changes[:password_hash]) + assert is_binary(changeset.changes[:keys]) assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname}) assert is_binary(changeset.changes[:keys]) - assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers" end @@ -737,6 +737,13 @@ test "gets an existing user by ap_id" do freshed_user = refresh_record(user) assert freshed_user == fetched_user end + + test "gets an existing user by nickname starting with http" do + user = insert(:user, nickname: "httpssome") + {:ok, fetched_user} = User.get_or_fetch("httpssome") + + assert user == fetched_user + end end describe "get_or_fetch/1 remote users with tld, while BE is runned on subdomain" do @@ -2130,21 +2137,6 @@ test "Only includes users with no read notifications" do end end - describe "ensure_keys_present" do - test "it creates keys for a user and stores them in info" do - user = insert(:user) - refute is_binary(user.keys) - {:ok, user} = User.ensure_keys_present(user) - assert is_binary(user.keys) - end - - test "it doesn't create keys if there already are some" do - user = insert(:user, keys: "xxx") - {:ok, user} = User.ensure_keys_present(user) - assert user.keys == "xxx" - end - end - describe "get_ap_ids_by_nicknames" do test "it returns a list of AP ids for a given set of nicknames" do user = insert(:user) diff --git a/test/pleroma/web/activity_pub/views/object_view_test.exs b/test/pleroma/web/activity_pub/views/object_view_test.exs index 923515dec..9348c09be 100644 --- a/test/pleroma/web/activity_pub/views/object_view_test.exs +++ b/test/pleroma/web/activity_pub/views/object_view_test.exs @@ -81,4 +81,18 @@ test "renders an announce activity" do assert result["object"] == object.data["id"] assert result["type"] == "Announce" end + + test "renders an undo announce activity" do + note = insert(:note_activity) + user = insert(:user) + + {:ok, announce} = CommonAPI.repeat(note.id, user) + {:ok, undo} = CommonAPI.unrepeat(note.id, user) + + result = ObjectView.render("object.json", %{object: undo}) + + assert result["id"] == undo.data["id"] + assert result["object"] == announce.data["id"] + assert result["type"] == "Undo" + end end diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index e49cb99d3..5501e64d6 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -12,7 +12,6 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do test "Renders a user, including the public key" do user = insert(:user) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) @@ -55,7 +54,6 @@ test "Renders with emoji tags" do test "Does not add an avatar image if the user hasn't set one" do user = insert(:user) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) refute result["icon"] @@ -67,8 +65,6 @@ test "Does not add an avatar image if the user hasn't set one" do banner: %{"url" => [%{"href" => "https://somebanner"}]} ) - {:ok, user} = User.ensure_keys_present(user) - result = UserView.render("user.json", %{user: user}) assert result["icon"]["url"] == "https://someurl" assert result["image"]["url"] == "https://somebanner" @@ -89,7 +85,6 @@ test "renders AKAs" do describe "endpoints" do test "local users have a usable endpoints structure" do user = insert(:user) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) @@ -105,7 +100,6 @@ test "local users have a usable endpoints structure" do test "remote users have an empty endpoints structure" do user = insert(:user, local: false) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) @@ -115,7 +109,6 @@ test "remote users have an empty endpoints structure" do test "instance users do not expose oAuth endpoints" do user = insert(:user, nickname: nil, local: true) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) diff --git a/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs b/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs new file mode 100644 index 000000000..4909ef3a7 --- /dev/null +++ b/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs @@ -0,0 +1,122 @@ +defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsControllerTest do + use Pleroma.Web.ConnCase, async: true + + import Pleroma.Factory + alias Pleroma.Akkoma.FrontendSettingsProfile + + describe "GET /api/v1/akkoma/frontend_settings/:frontend_name" do + test "it returns a list of profiles" do + %{conn: conn, user: user} = oauth_access(["read"]) + + insert(:frontend_setting_profile, user: user, frontend_name: "test", profile_name: "test1") + insert(:frontend_setting_profile, user: user, frontend_name: "test", profile_name: "test2") + + response = + conn + |> get("/api/v1/akkoma/frontend_settings/test") + |> json_response_and_validate_schema(200) + + assert response == [ + %{"name" => "test1", "version" => 1}, + %{"name" => "test2", "version" => 1} + ] + end + end + + describe "GET /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" do + test "it returns 404 if not found" do + %{conn: conn} = oauth_access(["read"]) + + conn + |> get("/api/v1/akkoma/frontend_settings/unknown_frontend/unknown_profile") + |> json_response_and_validate_schema(404) + end + + test "it returns 200 if found" do + %{conn: conn, user: user} = oauth_access(["read"]) + + insert(:frontend_setting_profile, + user: user, + frontend_name: "test", + profile_name: "test1", + settings: %{"test" => "test"} + ) + + response = + conn + |> get("/api/v1/akkoma/frontend_settings/test/test1") + |> json_response_and_validate_schema(200) + + assert response == %{"settings" => %{"test" => "test"}, "version" => 1} + end + end + + describe "PUT /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" do + test "puts a config" do + %{conn: conn, user: user} = oauth_access(["write"]) + settings = %{"test" => "test2"} + + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/akkoma/frontend_settings/test/test1", %{ + "settings" => settings, + "version" => 1 + }) + |> json_response_and_validate_schema(200) + + assert response == settings + + assert %FrontendSettingsProfile{settings: ^settings} = + FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + user, + "test", + "test1" + ) + end + + test "refuses to overwrite a newer config" do + %{conn: conn, user: user} = oauth_access(["write"]) + + insert(:frontend_setting_profile, + user: user, + frontend_name: "test", + profile_name: "test1", + settings: %{"test" => "test"}, + version: 2 + ) + + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/akkoma/frontend_settings/test/test1", %{ + "settings" => %{"test" => "test2"}, + "version" => 1 + }) + |> json_response_and_validate_schema(422) + end + end + + describe "DELETE /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" do + test "deletes a config" do + %{conn: conn, user: user} = oauth_access(["write"]) + + insert(:frontend_setting_profile, + user: user, + frontend_name: "test", + profile_name: "test1", + settings: %{"test" => "test"}, + version: 2 + ) + + conn + |> delete("/api/v1/akkoma/frontend_settings/test/test1") + |> json_response_and_validate_schema(200) + + assert FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + user, + "test", + "test1" + ) == nil + end + end +end diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs index 1b8d27cda..5d7ad08ef 100644 --- a/test/pleroma/web/metadata/providers/twitter_card_test.exs +++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs @@ -39,6 +39,7 @@ test "it uses summary twittercard if post has no attachment" do "actor" => user.ap_id, "tag" => [], "id" => "https://pleroma.gov/objects/whatever", + "summary" => "", "content" => "pleroma in a nutshell" } }) @@ -54,6 +55,36 @@ test "it uses summary twittercard if post has no attachment" do ] == result end + test "it uses summary as description if post has one" do + user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") + {:ok, activity} = CommonAPI.post(user, %{status: "HI"}) + + note = + insert(:note, %{ + data: %{ + "actor" => user.ap_id, + "tag" => [], + "id" => "https://pleroma.gov/objects/whatever", + "summary" => "Public service announcement on caffeine consumption", + "content" => "cofe" + } + }) + + result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id}) + + assert [ + {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, + {:meta, + [ + property: "twitter:description", + content: "Public service announcement on caffeine consumption" + ], []}, + {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"], + []}, + {:meta, [property: "twitter:card", content: "summary"], []} + ] == result + end + test "it renders avatar not attachment if post is nsfw and unfurl_nsfw is disabled" do clear_config([Pleroma.Web.Metadata, :unfurl_nsfw], false) user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") @@ -65,6 +96,7 @@ test "it renders avatar not attachment if post is nsfw and unfurl_nsfw is disabl "actor" => user.ap_id, "tag" => [], "id" => "https://pleroma.gov/objects/whatever", + "summary" => "", "content" => "pleroma in a nutshell", "sensitive" => true, "attachment" => [ @@ -109,6 +141,7 @@ test "it renders supported types of attachments and skips unknown types" do "actor" => user.ap_id, "tag" => [], "id" => "https://pleroma.gov/objects/whatever", + "summary" => "", "content" => "pleroma in a nutshell", "attachment" => [ %{ diff --git a/test/pleroma/web/metadata/utils_test.exs b/test/pleroma/web/metadata/utils_test.exs index c99d11596..665efb9ca 100644 --- a/test/pleroma/web/metadata/utils_test.exs +++ b/test/pleroma/web/metadata/utils_test.exs @@ -8,7 +8,7 @@ defmodule Pleroma.Web.Metadata.UtilsTest do alias Pleroma.Web.Metadata.Utils describe "scrub_html_and_truncate/1" do - test "it returns text without encode HTML" do + test "it returns content text without encode HTML if summary is nil" do user = insert(:user) note = @@ -16,6 +16,7 @@ test "it returns text without encode HTML" do data: %{ "actor" => user.ap_id, "id" => "https://pleroma.gov/objects/whatever", + "summary" => nil, "content" => "Pleroma's really cool!" } }) @@ -23,6 +24,39 @@ test "it returns text without encode HTML" do assert Utils.scrub_html_and_truncate(note) == "Pleroma's really cool!" end + test "it returns context text without encode HTML if summary is empty" do + user = insert(:user) + + note = + insert(:note, %{ + data: %{ + "actor" => user.ap_id, + "id" => "https://pleroma.gov/objects/whatever", + "summary" => "", + "content" => "Pleroma's really cool!" + } + }) + + assert Utils.scrub_html_and_truncate(note) == "Pleroma's really cool!" + end + + test "it returns summary text without encode HTML if summary is filled" do + user = insert(:user) + + note = + insert(:note, %{ + data: %{ + "actor" => user.ap_id, + "id" => "https://pleroma.gov/objects/whatever", + "summary" => "Public service announcement on caffeine consumption", + "content" => "cofe" + } + }) + + assert Utils.scrub_html_and_truncate(note) == + "Public service announcement on caffeine consumption" + end + test "it does not return old content after editing" do user = insert(:user) diff --git a/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs index ba17636da..2c7264016 100644 --- a/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs @@ -11,7 +11,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupControllerTest do setup do clear_config([Pleroma.Upload, :uploader]) clear_config([Backup, :limit_days]) - oauth_access(["read:accounts"]) + oauth_access(["read:backups"]) end test "GET /api/v1/pleroma/backups", %{user: user, conn: conn} do @@ -85,7 +85,7 @@ test "POST /api/v1/pleroma/backups", %{user: _user, conn: conn} do test "Backup without email address" do user = Pleroma.Factory.insert(:user, email: nil) - %{conn: conn} = oauth_access(["read:accounts"], user: user) + %{conn: conn} = oauth_access(["read:backups"], user: user) assert is_nil(user.email) diff --git a/test/support/factory.ex b/test/support/factory.ex index 6695886dc..54d385bc4 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -10,6 +10,15 @@ defmodule Pleroma.Factory do alias Pleroma.Object alias Pleroma.User + @rsa_keys [ + "test/fixtures/rsa_keys/key_1.pem", + "test/fixtures/rsa_keys/key_2.pem", + "test/fixtures/rsa_keys/key_3.pem", + "test/fixtures/rsa_keys/key_4.pem", + "test/fixtures/rsa_keys/key_5.pem" + ] + |> Enum.map(&File.read!/1) + def participation_factory do conversation = insert(:conversation) user = insert(:user) @@ -28,6 +37,8 @@ def conversation_factory do end def user_factory(attrs \\ %{}) do + pem = Enum.random(@rsa_keys) + user = %User{ name: sequence(:name, &"Test ใƒ†ใ‚นใƒˆ User #{&1}"), email: sequence(:email, &"user#{&1}@example.com"), @@ -39,7 +50,8 @@ def user_factory(attrs \\ %{}) do last_refreshed_at: NaiveDateTime.utc_now(), notification_settings: %Pleroma.User.NotificationSetting{}, multi_factor_authentication_settings: %Pleroma.MFA.Settings{}, - ap_enabled: true + ap_enabled: true, + keys: pem } urls = @@ -651,4 +663,15 @@ def announcement_factory(params \\ %{}) do |> Map.merge(params) |> Pleroma.Announcement.add_rendered_properties() end + + def frontend_setting_profile_factory(params \\ %{}) do + %Pleroma.Akkoma.FrontendSettingsProfile{ + user: build(:user), + frontend_name: "akkoma-fe", + profile_name: "default", + settings: %{"test" => "test"}, + version: 1 + } + |> Map.merge(params) + end end