Merge remote-tracking branch 'origin/develop' into apps-api-endpoint

This commit is contained in:
Alex Gleason 2021-12-27 18:01:25 -06:00
commit f5c3d45120
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
3865 changed files with 4840 additions and 12084 deletions

View file

@ -79,7 +79,6 @@ unit-testing:
- "**/*.ex" - "**/*.ex"
- "**/*.exs" - "**/*.exs"
- "mix.lock" - "mix.lock"
retry: 2
cache: &testing_cache_policy cache: &testing_cache_policy
<<: *global_cache_policy <<: *global_cache_policy
policy: pull policy: pull
@ -94,6 +93,27 @@ unit-testing:
- mix ecto.migrate - mix ecto.migrate
- mix coveralls --preload-modules - mix coveralls --preload-modules
unit-testing-erratic:
stage: test
retry: 2
only:
changes:
- "**/*.ex"
- "**/*.exs"
- "mix.lock"
cache: &testing_cache_policy
<<: *global_cache_policy
policy: pull
services:
- name: postgres:13
alias: postgres
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
script:
- mix ecto.create
- mix ecto.migrate
- mix test --only=erratic
# Removed to fix CI issue. In this early state it wasn't adding much value anyway. # Removed to fix CI issue. In this early state it wasn't adding much value anyway.
# TODO Fix and reinstate federated testing # TODO Fix and reinstate federated testing
# federated-testing: # federated-testing:
@ -117,7 +137,6 @@ unit-testing-rum:
- "**/*.ex" - "**/*.ex"
- "**/*.exs" - "**/*.exs"
- "mix.lock" - "mix.lock"
retry: 2
cache: *testing_cache_policy cache: *testing_cache_policy
services: services:
- name: minibikini/postgres-with-rum:12 - name: minibikini/postgres-with-rum:12
@ -134,6 +153,7 @@ unit-testing-rum:
- mix test --preload-modules - mix test --preload-modules
lint: lint:
image: elixir:1.12
stage: test stage: test
only: only:
changes: changes:
@ -243,7 +263,7 @@ stop_review_app:
amd64: amd64:
stage: release stage: release
image: elixir:1.10.3 image: elixir:1.10.4
only: &release-only only: &release-only
- stable@pleroma/pleroma - stable@pleroma/pleroma
- develop@pleroma/pleroma - develop@pleroma/pleroma
@ -281,7 +301,7 @@ amd64-musl:
stage: release stage: release
artifacts: *release-artifacts artifacts: *release-artifacts
only: *release-only only: *release-only
image: elixir:1.10.3-alpine image: elixir:1.10.4-alpine
cache: *release-cache cache: *release-cache
variables: *release-variables variables: *release-variables
before_script: &before-release-musl before_script: &before-release-musl
@ -297,7 +317,7 @@ arm:
only: *release-only only: *release-only
tags: tags:
- arm32-specified - arm32-specified
image: arm32v7/elixir:1.10.3 image: arm32v7/elixir:1.10.4
cache: *release-cache cache: *release-cache
variables: *release-variables variables: *release-variables
before_script: *before-release before_script: *before-release
@ -309,7 +329,7 @@ arm-musl:
only: *release-only only: *release-only
tags: tags:
- arm32-specified - arm32-specified
image: arm32v7/elixir:1.10.3-alpine image: arm32v7/elixir:1.10.4-alpine
cache: *release-cache cache: *release-cache
variables: *release-variables variables: *release-variables
before_script: *before-release-musl before_script: *before-release-musl
@ -321,7 +341,7 @@ arm64:
only: *release-only only: *release-only
tags: tags:
- arm - arm
image: arm64v8/elixir:1.10.3 image: arm64v8/elixir:1.10.4
cache: *release-cache cache: *release-cache
variables: *release-variables variables: *release-variables
before_script: *before-release before_script: *before-release
@ -333,7 +353,7 @@ arm64-musl:
only: *release-only only: *release-only
tags: tags:
- arm - arm
image: arm64v8/elixir:1.10.3-alpine image: arm64v8/elixir:1.10.4-alpine
cache: *release-cache cache: *release-cache
variables: *release-variables variables: *release-variables
before_script: *before-release-musl before_script: *before-release-musl
@ -351,8 +371,8 @@ docker:
IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest
IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable
DOCKER_BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-amd64 DOCKER_BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64
DOCKER_BUILDX_HASH: 71a7d01439aa8c165a25b59c44d3f016fddbd98b DOCKER_BUILDX_HASH: 980e6b9655f971991fbbb5fd6cd19f1672386195
before_script: &before-docker before_script: &before-docker
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $IMAGE_TAG_SLUG || true - docker pull $IMAGE_TAG_SLUG || true

View file

@ -6,19 +6,47 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased ## Unreleased
### Removed
- MastoFE
### Changed ### Changed
- Allow users to remove their emails if instance does not need email to register
### Added ### Added
- `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object
- Experimental support for Finch. Put `config :tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch}` in your secrets file to use it. Reverse Proxy will still use Hackney.
- AdminAPI: allow moderators to manage reports, users, invites, and custom emojis
- AdminAPI: restrict moderators to access sensitive data: change user credentials, get password reset token, read private statuses and chats, etc
### Fixed ### Fixed
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies - Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
- Handle Reject for already-accepted Follows properly
- Display OpenGraph data on alternative notice routes.
### Removed ### Removed
## Unreleased-patch ## 2.4.1 - 2021-08-29
- Mastodon API: Activity Search fallbacks on status fetching after a DB Timeout/Error
## 2.4.0 - 2021-08-xx ### Changed
- Make `mix pleroma.database set_text_search_config` run concurrently and indefinitely
### Added
- AdminAPI: Missing configuration description for StealEmojiPolicy
### Fixed
- MastodonAPI: Stream out Create activities
- MRF ObjectAgePolicy: Fix pattern matching on "published"
- TwitterAPI: Make `change_password` and `change_email` require params on body instead of query
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
- AdminAPI: Fix rendering reports containing a `nil` object
- Mastodon API: Activity Search fallbacks on status fetching after a DB Timeout/Error
- Mastodon API: Fix crash in Streamer related to reblogging
- AdminAPI: List available frontends when `static/frontends` folder is missing
- Make activity search properly use language-aware GIN indexes
- AdminAPI: Fix suggestions for MRF Policies
## 2.4.0 - 2021-08-08
### Changed ### Changed
@ -37,6 +65,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded. - MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
- Return OAuth token `id` (primary key) in POST `/oauth/token`. - Return OAuth token `id` (primary key) in POST `/oauth/token`.
- AdminAPI: return `created_at` date with users. - AdminAPI: return `created_at` date with users.
- AdminAPI: add DELETE `/api/v1/pleroma/admin/instances/:instance` to delete all content from a remote instance.
- `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time. - `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time.
- Attachment dimensions and blurhashes are federated when available. - Attachment dimensions and blurhashes are federated when available.
- Mastodon API: support `poll` notification. - Mastodon API: support `poll` notification.
@ -47,6 +76,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Checking activated Upload Filters for required commands. - Checking activated Upload Filters for required commands.
- Remote users can no longer reappear after being deleted. - Remote users can no longer reappear after being deleted.
- Deactivated users may now be deleted. - Deactivated users may now be deleted.
- Deleting an activity with a lot of likes/boosts no longer causes a database timeout.
- Mix task `pleroma.database prune_objects` - Mix task `pleroma.database prune_objects`
- Fixed rendering of JSON errors on ActivityPub endpoints. - Fixed rendering of JSON errors on ActivityPub endpoints.
- Linkify: Parsing crash with URLs ending in unbalanced closed paren, no path separator, and no query parameters - Linkify: Parsing crash with URLs ending in unbalanced closed paren, no path separator, and no query parameters
@ -111,6 +141,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Support pagination of blocks and mutes. - Support pagination of blocks and mutes.
- Account backup. - Account backup.
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance. - Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
- `[:activitypub, :blockers_visible]` config to control visibility of blockers.
- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`. - Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`.
- The site title is now injected as a `title` tag like preloads or metadata. - The site title is now injected as a `title` tag like preloads or metadata.
- Password reset tokens now are not accepted after a certain age. - Password reset tokens now are not accepted after a certain age.

View file

@ -12,7 +12,7 @@ RUN apk add git gcc g++ musl-dev make cmake file-dev &&\
mkdir release &&\ mkdir release &&\
mix release --path release mix release --path release
FROM alpine:3.11 FROM alpine:3.14
ARG BUILD_DATE ARG BUILD_DATE
ARG VCS_REF ARG VCS_REF
@ -31,8 +31,7 @@ LABEL maintainer="ops@pleroma.social" \
ARG HOME=/opt/pleroma ARG HOME=/opt/pleroma
ARG DATA=/var/lib/pleroma ARG DATA=/var/lib/pleroma
RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ RUN apk update &&\
apk update &&\
apk add exiftool ffmpeg imagemagick libmagic ncurses postgresql-client &&\ apk add exiftool ffmpeg imagemagick libmagic ncurses postgresql-client &&\
adduser --system --shell /bin/false --home ${HOME} pleroma &&\ adduser --system --shell /bin/false --home ${HOME} pleroma &&\
mkdir -p ${DATA}/uploads &&\ mkdir -p ${DATA}/uploads &&\

View file

@ -30,7 +30,7 @@ If your platform is not supported, or you just want to be able to edit the sourc
- [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/) - [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/)
### OS/Distro packages ### OS/Distro packages
Currently Pleroma is not packaged by any OS/Distros, but if you want to package it for one, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**. Currently Pleroma is packaged for [YunoHost](https://yunohost.org). If you want to package Pleroma for any OS/Distros, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**.
### Docker ### Docker
While we dont provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>. While we dont provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.

View file

@ -394,7 +394,7 @@ defp get_actor(group, users), do: Enum.random(users[group])
defp other_data(actor, content) do defp other_data(actor, content) do
%{host: host} = URI.parse(actor.ap_id) %{host: host} = URI.parse(actor.ap_id)
datetime = DateTime.utc_now() datetime = DateTime.utc_now() |> to_string()
context_id = "https://#{host}/contexts/#{UUID.generate()}" context_id = "https://#{host}/contexts/#{UUID.generate()}"
activity_id = "https://#{host}/activities/#{UUID.generate()}" activity_id = "https://#{host}/activities/#{UUID.generate()}"
object_id = "https://#{host}/objects/#{UUID.generate()}" object_id = "https://#{host}/objects/#{UUID.generate()}"

View file

@ -99,15 +99,16 @@ defp hashtag_fetching(params, user, local_only) do
|> Enum.map(&String.downcase(&1)) |> Enum.map(&String.downcase(&1))
_activities = _activities =
params %{
|> Map.put(:type, "Create") type: "Create",
|> Map.put(:local_only, local_only) local_only: local_only,
|> Map.put(:blocking_user, user) blocking_user: user,
|> Map.put(:muting_user, user) muting_user: user,
|> Map.put(:user, user) user: user,
|> Map.put(:tag, tags) tag: tags,
|> Map.put(:tag_all, tag_all) tag_all: tag_all,
|> Map.put(:tag_reject, tag_reject) tag_reject: tag_reject,
}
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities() |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
end end
end end

View file

@ -17,14 +17,14 @@ def run(_args) do
# Let the user make 100 posts # Let the user make 100 posts
1..100 1..100
|> Enum.each(fn i -> CommonAPI.post(user, %{"status" => to_string(i)}) end) |> Enum.each(fn i -> CommonAPI.post(user, %{status: to_string(i)}) end)
# Let 10 random users post # Let 10 random users post
posts = posts =
users users
|> Enum.take_random(10) |> Enum.take_random(10)
|> Enum.map(fn {:ok, random_user} -> |> Enum.map(fn {:ok, random_user} ->
{:ok, activity} = CommonAPI.post(random_user, %{"status" => "."}) {:ok, activity} = CommonAPI.post(random_user, %{status: "."})
activity activity
end) end)
@ -42,7 +42,7 @@ def run(_args) do
|> Conn.assign(:user, reading_user) |> Conn.assign(:user, reading_user)
|> Conn.assign(:skip_link_headers, true) |> Conn.assign(:skip_link_headers, true)
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id}) Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{id: user.id})
end end
}, },
inputs: %{"user" => user, "no user" => nil}, inputs: %{"user" => user, "no user" => nil},
@ -50,7 +50,7 @@ def run(_args) do
) )
users users
|> Enum.each(fn {:ok, follower, user} -> Pleroma.User.follow(follower, user) end) |> Enum.each(fn {:ok, follower} -> Pleroma.User.follow(follower, user) end)
Benchee.run( Benchee.run(
%{ %{
@ -60,7 +60,7 @@ def run(_args) do
|> Conn.assign(:user, reading_user) |> Conn.assign(:user, reading_user)
|> Conn.assign(:skip_link_headers, true) |> Conn.assign(:skip_link_headers, true)
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id}) Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{id: user.id})
end end
}, },
inputs: %{"user" => user, "no user" => nil}, inputs: %{"user" => user, "no user" => nil},

View file

@ -4,8 +4,7 @@
# you can enable the server option below. # you can enable the server option below.
config :pleroma, Pleroma.Web.Endpoint, config :pleroma, Pleroma.Web.Endpoint,
http: [port: 4001], http: [port: 4001],
url: [port: 4001], url: [port: 4001]
server: true
# Disable captha for tests # Disable captha for tests
config :pleroma, Pleroma.Captcha, config :pleroma, Pleroma.Captcha,
@ -44,7 +43,7 @@
pool_size: 10 pool_size: 10
# Reduce hash rounds for testing # Reduce hash rounds for testing
config :pbkdf2_elixir, rounds: 1 config :pleroma, :password, iterations: 1
config :tesla, adapter: Tesla.Mock config :tesla, adapter: Tesla.Mock

View file

@ -139,6 +139,7 @@
], ],
protocol: "https", protocol: "https",
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl", secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
live_view: [signing_salt: "U5ELgdEwTD3n1+D5s0rY0AMg8/y1STxZ3Zvsl3bWh+oBcGrYdil0rXqPMRd3Glcq"],
signing_salt: "CqaoopA2", signing_salt: "CqaoopA2",
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)], render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
pubsub_server: Pleroma.PubSub, pubsub_server: Pleroma.PubSub,
@ -148,6 +149,8 @@
] ]
# Configures Elixir's Logger # Configures Elixir's Logger
config :logger, truncate: 65536
config :logger, :console, config :logger, :console,
level: :debug, level: :debug,
format: "\n$time $metadata[$level] $message\n", format: "\n$time $metadata[$level] $message\n",
@ -253,7 +256,9 @@
] ]
], ],
show_reactions: true, show_reactions: true,
password_reset_token_validity: 60 * 60 * 24 password_reset_token_validity: 60 * 60 * 24,
profile_directory: true,
privileged_staff: false
config :pleroma, :welcome, config :pleroma, :welcome,
direct_message: [ direct_message: [
@ -321,9 +326,6 @@
subjectLineBehavior: "email", subjectLineBehavior: "email",
theme: "pleroma-dark", theme: "pleroma-dark",
webPushNotifications: false webPushNotifications: false
},
masto_fe: %{
showInstanceSpecificPanel: true
} }
config :pleroma, :assets, config :pleroma, :assets,
@ -352,6 +354,7 @@
config :pleroma, :activitypub, config :pleroma, :activitypub,
unfollow_blocked: true, unfollow_blocked: true,
outgoing_blocks: true, outgoing_blocks: true,
blockers_visible: true,
follow_handshake_timeout: 500, follow_handshake_timeout: 500,
note_replies_output_limit: 5, note_replies_output_limit: 5,
sign_object_fetches: true, sign_object_fetches: true,
@ -853,6 +856,13 @@
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]} {Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]}
] ]
config :pleroma, :telemetry,
slow_queries_logging: [
enabled: false,
min_duration: 500_000,
exclude_sources: [nil, "oban_jobs"]
]
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View file

@ -936,6 +936,17 @@
key: :show_reactions, key: :show_reactions,
type: :boolean, type: :boolean,
description: "Let favourites and emoji reactions be viewed through the API." description: "Let favourites and emoji reactions be viewed through the API."
},
%{
key: :profile_directory,
type: :boolean,
description: "Enable profile directory."
},
%{
key: :privileged_staff,
type: :boolean,
description:
"Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
} }
] ]
}, },
@ -1164,7 +1175,7 @@
type: :group, type: :group,
description: description:
"This form can be used to configure a keyword list that keeps the configuration data for any " <> "This form can be used to configure a keyword list that keeps the configuration data for any " <>
"kind of frontend. By default, settings for pleroma_fe and masto_fe are configured. If you want to " <> "kind of frontend. By default, settings for pleroma_fe are configured. If you want to " <>
"add your own configuration your settings all fields must be complete.", "add your own configuration your settings all fields must be complete.",
children: [ children: [
%{ %{
@ -1364,25 +1375,6 @@
suggestions: ["pleroma-dark"] suggestions: ["pleroma-dark"]
} }
] ]
},
%{
key: :masto_fe,
label: "Masto FE",
type: :map,
description: "Settings for Masto FE",
suggestions: [
%{
showInstanceSpecificPanel: true
}
],
children: [
%{
key: :showInstanceSpecificPanel,
label: "Show instance specific panel",
type: :boolean,
description: "Whenether to show the instance's specific panel"
}
]
} }
] ]
}, },
@ -1689,6 +1681,11 @@
type: :boolean, type: :boolean,
description: "Whether to federate blocks to other instances" description: "Whether to federate blocks to other instances"
}, },
%{
key: :blockers_visible,
type: :boolean,
description: "Whether a user can see someone who has blocked them"
},
%{ %{
key: :sign_object_fetches, key: :sign_object_fetches,
type: :boolean, type: :boolean,

View file

@ -230,6 +230,7 @@ Notes:
### :activitypub ### :activitypub
* `unfollow_blocked`: Whether blocks result in people getting unfollowed * `unfollow_blocked`: Whether blocks result in people getting unfollowed
* `outgoing_blocks`: Whether to federate blocks to other instances * `outgoing_blocks`: Whether to federate blocks to other instances
* `blockers_visible`: Whether a user can see the posts of users who blocked them
* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question * `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
* `sign_object_fetches`: Sign object fetches with HTTP signatures * `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches * `authorized_fetch_mode`: Require HTTP signatures for AP fetches
@ -247,7 +248,7 @@ Notes:
### :frontend_configurations ### :frontend_configurations
This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` and `masto_fe` are configured. You can find the documentation for `pleroma_fe` configuration into [Pleroma-FE configuration and customization for instance administrators](/frontend/CONFIGURATION/#options). This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` are configured. You can find the documentation for `pleroma_fe` configuration into [Pleroma-FE configuration and customization for instance administrators](/frontend/CONFIGURATION/#options).
Frontends can access these settings at `/api/v1/pleroma/frontend_configurations` Frontends can access these settings at `/api/v1/pleroma/frontend_configurations`
@ -258,10 +259,7 @@ config :pleroma, :frontend_configurations,
pleroma_fe: %{ pleroma_fe: %{
theme: "pleroma-dark", theme: "pleroma-dark",
# ... see /priv/static/static/config.json for the available keys. # ... see /priv/static/static/config.json for the available keys.
}, }
masto_fe: %{
showInstanceSpecificPanel: true
}
``` ```
These settings **need to be complete**, they will override the defaults. These settings **need to be complete**, they will override the defaults.

View file

@ -5,7 +5,7 @@ Pleroma's full text search feature is powered by PostgreSQL's native [text searc
## Setup and test the new search config ## Setup and test the new search config
In most cases, you would need an extension installed to support parsing CJK text. Here are a few extension you may choose from, or you are more than welcome to share additional ones you found working for you with the rest of Pleroma community. In most cases, you would need an extension installed to support parsing CJK text. Here are a few extensions you may choose from, or you are more than welcome to share additional ones you found working for you with the rest of Pleroma community.
* [a generic n-gram parser](https://github.com/huangjimmy/pg_cjk_parser) supports Simplifed/Traditional Chinese, Japanese, and Korean * [a generic n-gram parser](https://github.com/huangjimmy/pg_cjk_parser) supports Simplifed/Traditional Chinese, Japanese, and Korean
* [a Korean parser](https://github.com/i0seph/textsearch_ko) based on mecab * [a Korean parser](https://github.com/i0seph/textsearch_ko) based on mecab
@ -34,7 +34,7 @@ Check output of the query, and see if it matches your expectation.
mix pleroma.database set_text_search_config YOUR.CONFIG mix pleroma.database set_text_search_config YOUR.CONFIG
``` ```
Note: index update may take a while. Note: index update may take a while, and it can be done while the instance is up and running, so you may restart db connection as soon as you see `Recreate index` in task output.
## Restart database connection ## Restart database connection
Since some changes above will only apply with a new database connection, you will have to restart either Pleroma or PostgreSQL process, or use `pg_terminate_backend` SQL command without restarting either. Since some changes above will only apply with a new database connection, you will have to restart either Pleroma or PostgreSQL process, or use `pg_terminate_backend` SQL command without restarting either.

View file

@ -261,6 +261,46 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
} }
``` ```
## `PATCH /api/v1/pleroma/admin/users/suggest`
### Suggest a user
Adds the user(s) to follower recommendations.
- Params:
- `nicknames`: nicknames array
- Response:
```json
{
users: [
{
// user object
}
]
}
```
## `PATCH /api/v1/pleroma/admin/users/unsuggest`
### Unsuggest a user
Removes the user(s) from follower recommendations.
- Params:
- `nicknames`: nicknames array
- Response:
```json
{
users: [
{
// user object
}
]
}
```
## `GET /api/v1/pleroma/admin/users/:nickname_or_id` ## `GET /api/v1/pleroma/admin/users/:nickname_or_id`
### Retrive the details of a user ### Retrive the details of a user
@ -319,6 +359,22 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
} }
``` ```
## `DELETE /api/v1/pleroma/admin/instances/:instance`
### Delete all users and activities from a remote instance
Note: this will trigger a job to remove instance content in the background.
It may take some time.
- Params:
- `instance`: remote instance host
- Response:
- The `instance` name as a string
```json
"lain.com"
```
## `GET /api/v1/pleroma/admin/statuses` ## `GET /api/v1/pleroma/admin/statuses`
### Retrives all latest statuses ### Retrives all latest statuses

View file

@ -383,12 +383,6 @@ Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer feat
- `GET /api/v1/endorsements`: Returns an empty array, `[]` - `GET /api/v1/endorsements`: Returns an empty array, `[]`
### Profile directory
*Added in Mastodon 3.0.0*
- `GET /api/v1/directory`: Returns HTTP 404
### Featured tags ### Featured tags
*Added in Mastodon 3.0.0* *Added in Mastodon 3.0.0*

View file

@ -0,0 +1,347 @@
# Nodeinfo
See also [the Nodeinfo standard](https://nodeinfo.diaspora.software/).
## `/.well-known/nodeinfo`
### The well-known path
* Method: `GET`
* Authentication: not required
* Params: none
* Response: JSON
* Example response:
```json
{
"links":[
{
"href":"https://example.com/nodeinfo/2.0.json",
"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0"
},
{
"href":"https://example.com/nodeinfo/2.1.json",
"rel":"http://nodeinfo.diaspora.software/ns/schema/2.1"
}
]
}
```
## `/nodeinfo/2.0.json`
### Nodeinfo 2.0
* Method: `GET`
* Authentication: not required
* Params: none
* Response: JSON
* Example response:
```json
{
"metadata":{
"accountActivationRequired":false,
"features":[
"pleroma_api",
"mastodon_api",
"mastodon_api_streaming",
"polls",
"pleroma_explicit_addressing",
"shareable_emoji_packs",
"multifetch",
"pleroma:api/v1/notifications:include_types_filter",
"chat",
"shout",
"relay",
"pleroma_emoji_reactions",
"pleroma_chat_messages"
],
"federation":{
"enabled":true,
"exclusions":false,
"mrf_hashtag":{
"federated_timeline_removal":[
],
"reject":[
],
"sensitive":[
"nsfw"
]
},
"mrf_object_age":{
"actions":[
"delist",
"strip_followers"
],
"threshold":604800
},
"mrf_policies":[
"ObjectAgePolicy",
"TagPolicy",
"HashtagPolicy"
],
"quarantined_instances":[
]
},
"fieldsLimits":{
"maxFields":10,
"maxRemoteFields":20,
"nameLength":512,
"valueLength":2048
},
"invitesEnabled":false,
"mailerEnabled":false,
"nodeDescription":"Pleroma: An efficient and flexible fediverse server",
"nodeName":"Example",
"pollLimits":{
"max_expiration":31536000,
"max_option_chars":200,
"max_options":20,
"min_expiration":0
},
"postFormats":[
"text/plain",
"text/html",
"text/markdown",
"text/bbcode"
],
"private":false,
"restrictedNicknames":[
".well-known",
"~",
"about",
"activities",
"api",
"auth",
"check_password",
"dev",
"friend-requests",
"inbox",
"internal",
"main",
"media",
"nodeinfo",
"notice",
"oauth",
"objects",
"ostatus_subscribe",
"pleroma",
"proxy",
"push",
"registration",
"relay",
"settings",
"status",
"tag",
"user-search",
"user_exists",
"users",
"web",
"verify_credentials",
"update_credentials",
"relationships",
"search",
"confirmation_resend",
"mfa"
],
"skipThreadContainment":true,
"staffAccounts":[
"https://example.com/users/admin",
"https://example.com/users/staff"
],
"suggestions":{
"enabled":false
},
"uploadLimits":{
"avatar":2000000,
"background":4000000,
"banner":4000000,
"general":16000000
}
},
"openRegistrations":true,
"protocols":[
"activitypub"
],
"services":{
"inbound":[
],
"outbound":[
]
},
"software":{
"name":"pleroma",
"version":"2.4.1"
},
"usage":{
"localPosts":27,
"users":{
"activeHalfyear":129,
"activeMonth":70,
"total":235
}
},
"version":"2.0"
}
```
## `/nodeinfo/2.1.json`
### Nodeinfo 2.1
* Method: `GET`
* Authentication: not required
* Params: none
* Response: JSON
* Example response:
```json
{
"metadata":{
"accountActivationRequired":false,
"features":[
"pleroma_api",
"mastodon_api",
"mastodon_api_streaming",
"polls",
"pleroma_explicit_addressing",
"shareable_emoji_packs",
"multifetch",
"pleroma:api/v1/notifications:include_types_filter",
"chat",
"shout",
"relay",
"pleroma_emoji_reactions",
"pleroma_chat_messages"
],
"federation":{
"enabled":true,
"exclusions":false,
"mrf_hashtag":{
"federated_timeline_removal":[
],
"reject":[
],
"sensitive":[
"nsfw"
]
},
"mrf_object_age":{
"actions":[
"delist",
"strip_followers"
],
"threshold":604800
},
"mrf_policies":[
"ObjectAgePolicy",
"TagPolicy",
"HashtagPolicy"
],
"quarantined_instances":[
]
},
"fieldsLimits":{
"maxFields":10,
"maxRemoteFields":20,
"nameLength":512,
"valueLength":2048
},
"invitesEnabled":false,
"mailerEnabled":false,
"nodeDescription":"Pleroma: An efficient and flexible fediverse server",
"nodeName":"Example",
"pollLimits":{
"max_expiration":31536000,
"max_option_chars":200,
"max_options":20,
"min_expiration":0
},
"postFormats":[
"text/plain",
"text/html",
"text/markdown",
"text/bbcode"
],
"private":false,
"restrictedNicknames":[
".well-known",
"~",
"about",
"activities",
"api",
"auth",
"check_password",
"dev",
"friend-requests",
"inbox",
"internal",
"main",
"media",
"nodeinfo",
"notice",
"oauth",
"objects",
"ostatus_subscribe",
"pleroma",
"proxy",
"push",
"registration",
"relay",
"settings",
"status",
"tag",
"user-search",
"user_exists",
"users",
"web",
"verify_credentials",
"update_credentials",
"relationships",
"search",
"confirmation_resend",
"mfa"
],
"skipThreadContainment":true,
"staffAccounts":[
"https://example.com/users/admin",
"https://example.com/users/staff"
],
"suggestions":{
"enabled":false
},
"uploadLimits":{
"avatar":2000000,
"background":4000000,
"banner":4000000,
"general":16000000
}
},
"openRegistrations":true,
"protocols":[
"activitypub"
],
"services":{
"inbound":[
],
"outbound":[
]
},
"software":{
"name":"pleroma",
"repository":"https://git.pleroma.social/pleroma/pleroma",
"version":"2.4.1"
},
"usage":{
"localPosts":27,
"users":{
"activeHalfyear":129,
"activeMonth":70,
"total":235
}
},
"version":"2.1"
}
```

View file

@ -159,10 +159,12 @@ See [Admin-API](admin_api.md)
"muting": false, "muting": false,
"muting_notifications": false, "muting_notifications": false,
"subscribing": true, "subscribing": true,
"notifying": true,
"requested": false, "requested": false,
"domain_blocking": false, "domain_blocking": false,
"showing_reblogs": true, "showing_reblogs": true,
"endorsed": false "endorsed": false,
"note": ""
} }
``` ```
@ -183,10 +185,12 @@ See [Admin-API](admin_api.md)
"muting": false, "muting": false,
"muting_notifications": false, "muting_notifications": false,
"subscribing": false, "subscribing": false,
"notifying": false,
"requested": false, "requested": false,
"domain_blocking": false, "domain_blocking": false,
"showing_reblogs": true, "showing_reblogs": true,
"endorsed": false "endorsed": false,
"note": ""
} }
``` ```

View file

@ -16,11 +16,4 @@ Installation instructions can be found in the installation section of these docs
Great! Now you can explore the fediverse! Open the login page for your Pleroma instance (e.g. <https://pleroma.soykaf.com>) and login with your username and password. (If you don't have an account yet, click on Register) Great! Now you can explore the fediverse! Open the login page for your Pleroma instance (e.g. <https://pleroma.soykaf.com>) and login with your username and password. (If you don't have an account yet, click on Register)
### Pleroma-FE ### Pleroma-FE
The default front-end used by Pleroma is Pleroma-FE. You can find more information on what it is and how to use it in the [Introduction to Pleroma-FE](../frontend). The default front-end used by Pleroma is Pleroma-FE. You can find more information on what it is and how to use it in the [Introduction to Pleroma-FE](../frontend).
### Mastodon interface
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too!
Just add a "/web" after your instance url (e.g. <https://pleroma.soykaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC!
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 Pleroma.

View file

@ -9,7 +9,7 @@
* GNU make * GNU make
* CMake * CMake
## Optionnal dependencies ## Optional dependencies
* ImageMagick * ImageMagick
* FFmpeg * FFmpeg

View file

@ -0,0 +1,9 @@
# Installing on Yunohost
[YunoHost](https://yunohost.org) is a server operating system aimed at self-hosting. The YunoHost community maintains a package of Pleroma which allows you to install Pleroma on YunoHost. You can install it via the normal way through the admin web interface, or through the CLI. More information can be found at [the repo of the package](https://github.com/YunoHost-Apps/pleroma_ynh).
## Questions
Questions and problems related to the YunoHost parts can be done through the [regular YunoHost channels](https://yunohost.org/en/help).
For questions about Pleroma, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.

View file

@ -1,48 +0,0 @@
#!/bin/sh
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
project_id="74"
project_branch="rebase/glitch-soc"
static_dir="instance/static"
# For bundling:
# project_branch="pleroma"
# static_dir="priv/static"
if [ ! -d "${static_dir}" ]
then
echo "Error: ${static_dir} directory is missing, are you sure you are running this script at the root of pleromas repository?"
exit 1
fi
last_modified="$(curl --fail -s -I 'https://git.pleroma.social/api/v4/projects/'${project_id}'/jobs/artifacts/'${project_branch}'/download?job=build' | grep '^Last-Modified:' | cut -d: -f2-)"
echo "branch:${project_branch}"
echo "Last-Modified:${last_modified}"
artifact="mastofe.zip"
if [ "${last_modified}x" = "x" ]
then
echo "ERROR: Couldn't get the modification date of the latest build archive, maybe it expired, exiting..."
exit 1
fi
if [ -e mastofe.timestamp ] && [ "$(cat mastofe.timestamp)" = "${last_modified}" ]
then
echo "MastoFE is up-to-date, exiting..."
exit 0
fi
curl --fail -c - "https://git.pleroma.social/api/v4/projects/${project_id}/jobs/artifacts/${project_branch}/download?job=build" -o "${artifact}" || exit
# TODO: Update the emoji as well
rm -fr "${static_dir}/sw.js" "${static_dir}/packs" || exit
unzip -q "${artifact}" || exit
cp public/assets/sw.js "${static_dir}/sw.js" || exit
cp -r public/packs "${static_dir}/packs" || exit
echo "${last_modified}" > mastofe.timestamp
rm -fr public
rm -i "${artifact}"

View file

@ -286,9 +286,7 @@ defp migrate_from_db(opts) do
file = File.open!(tmp_config_path) file = File.open!(tmp_config_path)
shell_info( shell_info(
"Saving database configuration settings to #{tmp_config_path}. Copy it to the #{ "Saving database configuration settings to #{tmp_config_path}. Copy it to the #{Path.dirname(config_path)} manually."
Path.dirname(config_path)
} manually."
) )
write_config(file, tmp_config_path, opts) write_config(file, tmp_config_path, opts)

View file

@ -209,7 +209,9 @@ def run(["set_text_search_config", tsconfig]) do
new.fts_content := to_tsvector(new.data->>'content'); new.fts_content := to_tsvector(new.data->>'content');
RETURN new; RETURN new;
END END
$$ LANGUAGE plpgsql" $$ LANGUAGE plpgsql",
[],
timeout: :infinity
) )
shell_info("Refresh RUM index") shell_info("Refresh RUM index")
@ -219,7 +221,9 @@ def run(["set_text_search_config", tsconfig]) do
Ecto.Adapters.SQL.query!( Ecto.Adapters.SQL.query!(
Pleroma.Repo, Pleroma.Repo,
"CREATE INDEX objects_fts ON objects USING gin(to_tsvector('#{tsconfig}', data->>'content')); " "CREATE INDEX CONCURRENTLY objects_fts ON objects USING gin(to_tsvector('#{tsconfig}', data->>'content')); ",
[],
timeout: :infinity
) )
end end

View file

@ -199,6 +199,7 @@ def run(["gen" | rest]) do
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8) signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
lv_signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1) {web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates" template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
@ -217,6 +218,7 @@ def run(["gen" | rest]) do
secret: secret, secret: secret,
jwt_secret: jwt_secret, jwt_secret: jwt_secret,
signing_salt: signing_salt, signing_salt: signing_salt,
lv_signing_salt: lv_signing_salt,
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false), web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false), web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
db_configurable?: db_configurable?, db_configurable?: db_configurable?,

View file

@ -51,9 +51,7 @@ def run(["new", nickname, email | rest]) do
A user will be created with the following information: A user will be created with the following information:
- nickname: #{nickname} - nickname: #{nickname}
- email: #{email} - email: #{email}
- password: #{ - password: #{if(generated_password?, do: "[generated; a reset link will be created]", else: password)}
if(generated_password?, do: "[generated; a reset link will be created]", else: password)
}
- name: #{name} - name: #{name}
- bio: #{bio} - bio: #{bio}
- moderator: #{if(moderator?, do: "true", else: "false")} - moderator: #{if(moderator?, do: "true", else: "false")}
@ -114,15 +112,9 @@ def run(["reset_password", nickname]) do
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
shell_info("Generated password reset token for #{user.nickname}") shell_info("Generated password reset token for #{user.nickname}")
IO.puts( IO.puts("URL: #{Pleroma.Web.Router.Helpers.reset_password_url(Pleroma.Web.Endpoint,
"URL: #{ :reset,
Pleroma.Web.Router.Helpers.reset_password_url( token.token)}")
Pleroma.Web.Endpoint,
:reset,
token.token
)
}"
)
else else
_ -> _ ->
shell_error("No local user #{nickname}") shell_error("No local user #{nickname}")
@ -321,9 +313,7 @@ def run(["invites"]) do
end end
shell_info( shell_info(
"ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{ "ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{invite.used}#{expire_info}#{using_info}"
invite.used
}#{expire_info}#{using_info}"
) )
end) end)
end end
@ -424,9 +414,7 @@ def run(["list"]) do
users users
|> Enum.each(fn user -> |> Enum.each(fn user ->
shell_info( shell_info(
"#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{ "#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{user.is_locked}, is_active: #{user.is_active}"
user.is_locked
}, is_active: #{user.is_active}"
) )
end) end)
end) end)

View file

@ -302,7 +302,7 @@ def delete_all_by_object_ap_id(id) when is_binary(id) do
|> Queries.by_object_id() |> Queries.by_object_id()
|> Queries.exclude_type("Delete") |> Queries.exclude_type("Delete")
|> select([u], u) |> select([u], u)
|> Repo.delete_all() |> Repo.delete_all(timeout: :infinity)
|> elem(1) |> elem(1)
|> Enum.find(fn |> Enum.find(fn
%{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id %{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id
@ -362,11 +362,9 @@ def following_requests_for_actor(%User{ap_id: ap_id}) do
end end
def restrict_deactivated_users(query) do def restrict_deactivated_users(query) do
deactivated_users = deactivated_users_query = from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
|> Repo.all()
Activity.Queries.exclude_authors(query, deactivated_users) from(activity in query, where: activity.actor not in subquery(deactivated_users_query))
end end
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search

View file

@ -65,10 +65,17 @@ defp restrict_public(q) do
end end
defp query_with(q, :gin, search_query, :plain) do defp query_with(q, :gin, search_query, :plain) do
%{rows: [[tsc]]} =
Ecto.Adapters.SQL.query!(
Pleroma.Repo,
"select current_setting('default_text_search_config')::regconfig::oid;"
)
from([a, o] in q, from([a, o] in q,
where: where:
fragment( fragment(
"to_tsvector(?->>'content') @@ plainto_tsquery(?)", "to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)",
^tsc,
o.data, o.data,
^search_query ^search_query
) )
@ -76,10 +83,17 @@ defp query_with(q, :gin, search_query, :plain) do
end end
defp query_with(q, :gin, search_query, :websearch) do defp query_with(q, :gin, search_query, :websearch) do
%{rows: [[tsc]]} =
Ecto.Adapters.SQL.query!(
Pleroma.Repo,
"select current_setting('default_text_search_config')::regconfig::oid;"
)
from([a, o] in q, from([a, o] in q,
where: where:
fragment( fragment(
"to_tsvector(?->>'content') @@ websearch_to_tsquery(?)", "to_tsvector(?::oid::regconfig, ?->>'content') @@ websearch_to_tsquery(?)",
^tsc,
o.data, o.data,
^search_query ^search_query
) )

View file

@ -61,6 +61,11 @@ def start(_type, _args) do
adapter = Application.get_env(:tesla, :adapter) adapter = Application.get_env(:tesla, :adapter)
if match?({Tesla.Adapter.Finch, _}, adapter) do
Logger.info("Starting Finch")
Finch.start_link(name: MyFinch)
end
if adapter == Tesla.Adapter.Gun do if adapter == Tesla.Adapter.Gun do
if version = Pleroma.OTPVersion.version() do if version = Pleroma.OTPVersion.version() do
[major, minor] = [major, minor] =

View file

@ -19,9 +19,7 @@ def on_shell(username, _pubkey, _ip, _port) do
def on_connect(username, ip, port, method) do def on_connect(username, ip, port, method) do
Logger.debug(fn -> Logger.debug(fn ->
""" """
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{ Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{inspect(port)} using #{inspect(method)}
inspect(port)
} using #{inspect(method)}
""" """
end) end)
end end

View file

@ -21,9 +21,7 @@ def warn do
""" """
!!!OBAN CONFIG WARNING!!! !!!OBAN CONFIG WARNING!!!
You are using old workers in Oban crontab settings, which were removed. You are using old workers in Oban crontab settings, which were removed.
Please, remove setting from crontab in your config file (prod.secret.exs): #{ Please, remove setting from crontab in your config file (prod.secret.exs): #{inspect(setting)}
inspect(setting)
}
""" """
|> Logger.warn() |> Logger.warn()

View file

@ -148,9 +148,7 @@ defp update({group, key, value, merged}) do
rescue rescue
error -> error ->
error_msg = error_msg =
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{ "updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{inspect(value)} error: #{inspect(error)}"
inspect(value)
} error: #{inspect(error)}"
Logger.warn(error_msg) Logger.warn(error_msg)

View file

@ -9,7 +9,8 @@
mute: 2, mute: 2,
reblog_mute: 3, reblog_mute: 3,
notification_mute: 4, notification_mute: 4,
inverse_subscription: 5 inverse_subscription: 5,
suggestion_dismiss: 6
) )
defenum(Pleroma.FollowingRelationship.State, defenum(Pleroma.FollowingRelationship.State,

View file

@ -60,9 +60,7 @@ def load do
if not Enum.empty?(files) do if not Enum.empty?(files) do
Logger.warn( Logger.warn(
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{ "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{Enum.join(files, ", ")}"
Enum.join(files, ", ")
}"
) )
end end
@ -105,6 +103,7 @@ defp load_pack(pack_dir, emoji_groups) do
pack_file = Path.join(pack_dir, "pack.json") pack_file = Path.join(pack_dir, "pack.json")
if File.exists?(pack_file) do if File.exists?(pack_file) do
Logger.info("Loading emoji pack from JSON: #{pack_file}")
contents = Jason.decode!(File.read!(pack_file)) contents = Jason.decode!(File.read!(pack_file))
contents["files"] contents["files"]
@ -117,14 +116,13 @@ defp load_pack(pack_dir, emoji_groups) do
emoji_txt = Path.join(pack_dir, "emoji.txt") emoji_txt = Path.join(pack_dir, "emoji.txt")
if File.exists?(emoji_txt) do if File.exists?(emoji_txt) do
Logger.info("Loading emoji pack from emoji.txt: #{emoji_txt}")
load_from_file(emoji_txt, emoji_groups) load_from_file(emoji_txt, emoji_groups)
else else
extensions = Config.get([:emoji, :pack_extensions]) extensions = Config.get([:emoji, :pack_extensions])
Logger.info( Logger.info(
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{ "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"
Enum.join(extensions, ", ")
} files are emoji"
) )
make_shortcode_to_file_map(pack_dir, extensions) make_shortcode_to_file_map(pack_dir, extensions)

View file

@ -57,9 +57,7 @@ defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
else else
error -> error ->
Logger.warn( Logger.warn(
"Opening proxied connection to #{compose_uri_log(uri)} failed with error #{ "Opening proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
inspect(error)
}"
) )
error error
@ -93,9 +91,7 @@ defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do
else else
error -> error ->
Logger.warn( Logger.warn(
"Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{ "Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
inspect(error)
}"
) )
error error

View file

@ -8,6 +8,8 @@ defmodule Pleroma.Instances.Instance do
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Instances.Instance alias Pleroma.Instances.Instance
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Workers.BackgroundWorker
use Ecto.Schema use Ecto.Schema
@ -195,4 +197,24 @@ defp scrape_favicon(%URI{} = instance_uri) do
nil nil
end end
end end
@doc """
Deletes all users from an instance in a background task, thus also deleting
all of those users' activities and notifications.
"""
def delete_users_and_activities(host) when is_binary(host) do
BackgroundWorker.enqueue("delete_instance", %{"host" => host})
end
def perform(:delete_instance, host) when is_binary(host) do
User.Query.build(%{nickname: "@#{host}"})
|> Repo.chunk_stream(100, :batches)
|> Stream.each(fn users ->
users
|> Enum.each(fn user ->
User.perform(:delete, user)
end)
end)
|> Stream.run()
end
end end

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Maintenance do
def vacuum(args) do def vacuum(args) do
case args do case args do
"analyze" -> "analyze" ->
Logger.info("Runnning VACUUM ANALYZE.") Logger.info("Running VACUUM ANALYZE.")
Repo.query!( Repo.query!(
"vacuum analyze;", "vacuum analyze;",
@ -18,7 +18,7 @@ def vacuum(args) do
) )
"full" -> "full" ->
Logger.info("Runnning VACUUM FULL.") Logger.info("Running VACUUM FULL.")
Logger.warn( Logger.warn(
"Re-packing your entire database may take a while and will consume extra disk space during the process." "Re-packing your entire database may take a while and will consume extra disk space during the process."

View file

@ -338,6 +338,26 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}" "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
end end
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "add_suggestion",
"subject" => users
}
}) do
"@#{actor_nickname} added suggested users: #{users_to_nicknames_string(users)}"
end
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "remove_suggestion",
"subject" => users
}
}) do
"@#{actor_nickname} removed suggested users: #{users_to_nicknames_string(users)}"
end
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},
@ -481,9 +501,7 @@ def get_log_entry_message(%ModerationLog{
"visibility" => visibility "visibility" => visibility
} }
}) do }) do
"@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{ "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{visibility}'"
visibility
}'"
end end
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
@ -523,9 +541,7 @@ def get_log_entry_message(%ModerationLog{
"subject" => subjects "subject" => subjects
} }
}) do }) do
"@#{actor_nickname} re-sent confirmation email for users: #{ "@#{actor_nickname} re-sent confirmation email for users: #{users_to_nicknames_string(subjects)}"
users_to_nicknames_string(subjects)
}"
end end
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{

View file

@ -128,6 +128,7 @@ def for_user_query(user, opts \\ %{}) do
|> where([user_actor: user_actor], user_actor.is_active) |> where([user_actor: user_actor], user_actor.is_active)
|> exclude_notification_muted(user, exclude_notification_muted_opts) |> exclude_notification_muted(user, exclude_notification_muted_opts)
|> exclude_blocked(user, exclude_blocked_opts) |> exclude_blocked(user, exclude_blocked_opts)
|> exclude_blockers(user)
|> exclude_filtered(user) |> exclude_filtered(user)
|> exclude_visibility(opts) |> exclude_visibility(opts)
end end
@ -141,6 +142,17 @@ defp exclude_blocked(query, user, opts) do
|> FollowingRelationship.keep_following_or_not_domain_blocked(user) |> FollowingRelationship.keep_following_or_not_domain_blocked(user)
end end
defp exclude_blockers(query, user) do
if Pleroma.Config.get([:activitypub, :blockers_visible]) == true do
query
else
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
query
|> where([n, a], a.actor not in ^blocker_ap_ids)
end
end
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
query query
end end

View file

@ -25,5 +25,6 @@ defp client do
defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney
defp client(Tesla.Adapter.Gun), do: Pleroma.ReverseProxy.Client.Tesla defp client(Tesla.Adapter.Gun), do: Pleroma.ReverseProxy.Client.Tesla
defp client({Tesla.Adapter.Finch, _}), do: Pleroma.ReverseProxy.Client.Hackney
defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client) defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client)
end end

View file

@ -12,10 +12,16 @@ defmodule Pleroma.Telemetry.Logger do
[:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :reclaim, :stop],
[:pleroma, :connection_pool, :provision_failure], [:pleroma, :connection_pool, :provision_failure],
[:pleroma, :connection_pool, :client, :dead], [:pleroma, :connection_pool, :client, :dead],
[:pleroma, :connection_pool, :client, :add] [:pleroma, :connection_pool, :client, :add],
[:pleroma, :repo, :query]
] ]
def attach do def attach do
:telemetry.attach_many("pleroma-logger", @events, &handle_event/4, []) :telemetry.attach_many(
"pleroma-logger",
@events,
&Pleroma.Telemetry.Logger.handle_event/4,
[]
)
end end
# Passing anonymous functions instead of strings to logger is intentional, # Passing anonymous functions instead of strings to logger is intentional,
@ -29,9 +35,7 @@ def handle_event(
_ _
) do ) do
Logger.debug(fn -> Logger.debug(fn ->
"Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{ "Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{reclaim_max} connections"
reclaim_max
} connections"
end) end)
end end
@ -73,9 +77,7 @@ def handle_event(
_ _
) do ) do
Logger.warn(fn -> Logger.warn(fn ->
"Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{ "Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{inspect(reason)}"
inspect(reason)
}"
end) end)
end end
@ -91,4 +93,64 @@ def handle_event(
end end
def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok
def handle_event(
[:pleroma, :repo, :query] = _name,
%{query_time: query_time} = measurements,
%{source: source} = metadata,
config
) do
logging_config = Pleroma.Config.get([:telemetry, :slow_queries_logging], [])
if logging_config[:enabled] &&
logging_config[:min_duration] &&
query_time > logging_config[:min_duration] and
(is_nil(logging_config[:exclude_sources]) or
source not in logging_config[:exclude_sources]) do
log_slow_query(measurements, metadata, config)
else
:ok
end
end
defp log_slow_query(
%{query_time: query_time} = _measurements,
%{source: _source, query: query, params: query_params, repo: repo} = _metadata,
_config
) do
sql_explain =
with {:ok, %{rows: explain_result_rows}} <-
repo.query("EXPLAIN " <> query, query_params, log: false) do
Enum.map_join(explain_result_rows, "\n", & &1)
end
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
pleroma_stacktrace =
Enum.filter(stacktrace, fn
{__MODULE__, _, _, _} ->
false
{mod, _, _, _} ->
mod
|> to_string()
|> String.starts_with?("Elixir.Pleroma.")
end)
Logger.warn(fn ->
"""
Slow query!
Total time: #{round(query_time / 1_000)} ms
#{query}
#{inspect(query_params, limit: :infinity)}
#{sql_explain}
#{Exception.format_stacktrace(pleroma_stacktrace)}
"""
end)
end
end end

View file

@ -124,7 +124,6 @@ defmodule Pleroma.User do
field(:is_moderator, :boolean, default: false) field(:is_moderator, :boolean, default: false)
field(:is_admin, :boolean, default: false) field(:is_admin, :boolean, default: false)
field(:show_role, :boolean, default: true) field(:show_role, :boolean, default: true)
field(:mastofe_settings, :map, default: nil)
field(:uri, ObjectValidators.Uri, default: nil) field(:uri, ObjectValidators.Uri, default: nil)
field(:hide_followers_count, :boolean, default: false) field(:hide_followers_count, :boolean, default: false)
field(:hide_follows_count, :boolean, default: false) field(:hide_follows_count, :boolean, default: false)
@ -149,6 +148,8 @@ defmodule Pleroma.User do
field(:last_active_at, :naive_datetime) field(:last_active_at, :naive_datetime)
field(:disclose_client, :boolean, default: true) field(:disclose_client, :boolean, default: true)
field(:pinned_objects, :map, default: %{}) field(:pinned_objects, :map, default: %{})
field(:is_suggested, :boolean, default: false)
field(:last_status_at, :naive_datetime)
embeds_one( embeds_one(
:notification_settings, :notification_settings,
@ -1677,6 +1678,22 @@ def confirm(%User{is_confirmed: false} = user) do
def confirm(%User{} = user), do: {:ok, user} def confirm(%User{} = user), do: {:ok, user}
def set_suggestion(users, is_suggested) when is_list(users) do
Repo.transaction(fn ->
Enum.map(users, fn user ->
with {:ok, user} <- set_suggestion(user, is_suggested), do: user
end)
end)
end
def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user}
def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do
user
|> change(is_suggested: is_suggested)
|> update_and_set_cache()
end
def update_notification_settings(%User{} = user, settings) do def update_notification_settings(%User{} = user, settings) do
user user
|> cast(%{notification_settings: settings}, []) |> cast(%{notification_settings: settings}, [])
@ -1713,7 +1730,6 @@ def purge_user_changeset(user) do
ap_enabled: false, ap_enabled: false,
is_moderator: false, is_moderator: false,
is_admin: false, is_admin: false,
mastofe_settings: nil,
mascot: nil, mascot: nil,
emoji: %{}, emoji: %{},
pleroma_settings_store: %{}, pleroma_settings_store: %{},
@ -2248,7 +2264,7 @@ def get_delivered_users_by_object_id(object_id) do
def change_email(user, email) do def change_email(user, email) do
user user
|> cast(%{email: email}, [:email]) |> cast(%{email: email}, [:email])
|> validate_required([:email]) |> maybe_validate_required_email(false)
|> unique_constraint(:email) |> unique_constraint(:email)
|> validate_format(:email, @email_regex) |> validate_format(:email, @email_regex)
|> update_and_set_cache() |> update_and_set_cache()
@ -2331,13 +2347,6 @@ def mascot_update(user, url) do
|> update_and_set_cache() |> update_and_set_cache()
end end
def mastodon_settings_update(user, settings) do
user
|> cast(%{mastofe_settings: settings}, [:mastofe_settings])
|> validate_required([:mastofe_settings])
|> update_and_set_cache()
end
@spec confirmation_changeset(User.t(), keyword()) :: Changeset.t() @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
def confirmation_changeset(user, set_confirmation: confirmed?) do def confirmation_changeset(user, set_confirmation: confirmed?) do
params = params =
@ -2483,12 +2492,24 @@ def update_last_active_at(%__MODULE__{local: true} = user) do
|> update_and_set_cache() |> update_and_set_cache()
end end
def active_user_count(weeks \\ 4) do def active_user_count(days \\ 30) do
active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks) active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
__MODULE__ __MODULE__
|> where([u], u.last_active_at >= ^active_after) |> where([u], u.last_active_at >= ^active_after)
|> where([u], u.local == true) |> where([u], u.local == true)
|> Repo.aggregate(:count) |> Repo.aggregate(:count)
end end
def update_last_status_at(user) do
User
|> where(id: ^user.id)
|> update([u], set: [last_status_at: fragment("NOW()")])
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end
end end

View file

@ -46,6 +46,8 @@ defmodule Pleroma.User.Query do
unconfirmed: boolean(), unconfirmed: boolean(),
is_admin: boolean(), is_admin: boolean(),
is_moderator: boolean(), is_moderator: boolean(),
is_suggested: boolean(),
is_discoverable: boolean(),
super_users: boolean(), super_users: boolean(),
invisible: boolean(), invisible: boolean(),
internal: boolean(), internal: boolean(),
@ -167,6 +169,14 @@ defp compose_query({:unconfirmed, _}, query) do
where(query, [u], u.is_confirmed == false) where(query, [u], u.is_confirmed == false)
end end
defp compose_query({:is_suggested, bool}, query) do
where(query, [u], u.is_suggested == ^bool)
end
defp compose_query({:is_discoverable, bool}, query) do
where(query, [u], u.is_discoverable == ^bool)
end
defp compose_query({:followers, %User{id: id}}, query) do defp compose_query({:followers, %User{id: id}}, query) do
query query
|> where([u], u.id != ^id) |> where([u], u.id != ^id)

52
lib/pleroma/user_note.ex Normal file
View file

@ -0,0 +1,52 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UserNote do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserNote
schema "user_notes" do
belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
field(:comment, :string)
timestamps()
end
def changeset(%UserNote{} = user_note, params \\ %{}) do
user_note
|> cast(params, [:source_id, :target_id, :comment])
|> validate_required([:source_id, :target_id])
end
def show(%User{} = source, %User{} = target) do
with %UserNote{} = note <-
UserNote
|> where(source_id: ^source.id, target_id: ^target.id)
|> Repo.one() do
note.comment
else
_ -> ""
end
end
def create(%User{} = source, %User{} = target, comment) do
%UserNote{}
|> changeset(%{
source_id: source.id,
target_id: target.id,
comment: comment
})
|> Repo.insert(
on_conflict: {:replace, [:comment]},
conflict_target: [:source_id, :target_id]
)
end
end

View file

@ -81,6 +81,10 @@ def decrease_note_count_if_public(actor, object) do
if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor} if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
end end
def update_last_status_at_if_public(actor, object) do
if is_public?(object), do: User.update_last_status_at(actor), else: {:ok, actor}
end
defp increase_replies_count_if_reply(%{ defp increase_replies_count_if_reply(%{
"object" => %{"inReplyTo" => reply_ap_id} = object, "object" => %{"inReplyTo" => reply_ap_id} = object,
"type" => "Create" "type" => "Create"
@ -288,6 +292,7 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
_ <- increase_replies_count_if_reply(create_data), _ <- increase_replies_count_if_reply(create_data),
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity}, {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity), {:ok, _actor} <- increase_note_count_if_public(actor, activity),
{:ok, _actor} <- update_last_status_at_if_public(actor, activity),
_ <- notify_and_stream(activity), _ <- notify_and_stream(activity),
:ok <- maybe_schedule_poll_notifications(activity), :ok <- maybe_schedule_poll_notifications(activity),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
@ -441,6 +446,7 @@ def fetch_activities_for_context_query(context, opts) do
|> maybe_preload_bookmarks(opts) |> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts) |> maybe_set_thread_muted_field(opts)
|> restrict_blocked(opts) |> restrict_blocked(opts)
|> restrict_blockers_visibility(opts)
|> restrict_recipients(recipients, opts[:user]) |> restrict_recipients(recipients, opts[:user])
|> restrict_filtered(opts) |> restrict_filtered(opts)
|> where( |> where(
@ -1028,7 +1034,10 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
from( from(
[activity, object: o] in query, [activity, object: o] in query,
# You don't block the author
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids), where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
# You don't block any recipients, and didn't author the post
where: where:
fragment( fragment(
"((not (? && ?)) or ? = ?)", "((not (? && ?)) or ? = ?)",
@ -1037,12 +1046,18 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
activity.actor, activity.actor,
^user.ap_id ^user.ap_id
), ),
# You don't block the domain of any recipients, and didn't author the post
where: where:
fragment( fragment(
"recipients_contain_blocked_domains(?, ?) = false", "(recipients_contain_blocked_domains(?, ?) = false) or ? = ?",
activity.recipients, activity.recipients,
^domain_blocks ^domain_blocks,
activity.actor,
^user.ap_id
), ),
# It's not a boost of a user you block
where: where:
fragment( fragment(
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)", "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
@ -1050,6 +1065,8 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
activity.data, activity.data,
^blocked_ap_ids ^blocked_ap_ids
), ),
# You don't block the author's domain, and also don't follow the author
where: where:
fragment( fragment(
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)", "(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
@ -1058,6 +1075,8 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
activity.actor, activity.actor,
^following_ap_ids ^following_ap_ids
), ),
# Same as above, but checks the Object
where: where:
fragment( fragment(
"(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)", "(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
@ -1071,6 +1090,31 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
defp restrict_blocked(query, _), do: query defp restrict_blocked(query, _), do: query
defp restrict_blockers_visibility(query, %{blocking_user: %User{} = user}) do
if Config.get([:activitypub, :blockers_visible]) == true do
query
else
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
from(
activity in query,
# The author doesn't block you
where: fragment("not (? = ANY(?))", activity.actor, ^blocker_ap_ids),
# It's not a boost of a user that blocks you
where:
fragment(
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
activity.data,
activity.data,
^blocker_ap_ids
)
)
end
end
defp restrict_blockers_visibility(query, _), do: query
defp restrict_unlisted(query, %{restrict_unlisted: true}) do defp restrict_unlisted(query, %{restrict_unlisted: true}) do
from( from(
activity in query, activity in query,
@ -1297,6 +1341,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_state(opts) |> restrict_state(opts)
|> restrict_favorited_by(opts) |> restrict_favorited_by(opts)
|> restrict_blocked(restrict_blocked_opts) |> restrict_blocked(restrict_blocked_opts)
|> restrict_blockers_visibility(opts)
|> restrict_muted(restrict_muted_opts) |> restrict_muted(restrict_muted_opts)
|> restrict_filtered(opts) |> restrict_filtered(opts)
|> restrict_media(opts) |> restrict_media(opts)
@ -1597,9 +1642,7 @@ def maybe_handle_clashing_nickname(data) do
%User{} = old_user <- User.get_by_nickname(nickname), %User{} = old_user <- User.get_by_nickname(nickname),
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do {_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
Logger.info( Logger.info(
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{ "Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{data[:ap_id]}, renaming."
data[:ap_id]
}, renaming."
) )
old_user old_user

View file

@ -283,15 +283,29 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
json(conn, "ok") json(conn, "ok")
end end
def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do
conn
|> put_status(:bad_request)
|> json("Invalid HTTP Signature")
end
# POST /relay/inbox -or- POST /internal/fetch/inbox # POST /relay/inbox -or- POST /internal/fetch/inbox
def inbox(conn, params) do def inbox(conn, %{"type" => "Create"} = params) do
if params["type"] == "Create" && FederatingPlug.federating?() do if FederatingPlug.federating?() do
post_inbox_relayed_create(conn, params) post_inbox_relayed_create(conn, params)
else else
post_inbox_fallback(conn, params) conn
|> put_status(:bad_request)
|> json("Not federating")
end end
end end
def inbox(conn, _params) do
conn
|> put_status(:bad_request)
|> json("error, missing HTTP Signature")
end
defp post_inbox_relayed_create(conn, params) do defp post_inbox_relayed_create(conn, params) do
Logger.debug( Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source" "Signature missing or not from author, relayed Create message, fetching object from source"
@ -302,23 +316,6 @@ defp post_inbox_relayed_create(conn, params) do
json(conn, "ok") json(conn, "ok")
end end
defp post_inbox_fallback(conn, params) do
headers = Enum.into(conn.req_headers, %{})
if headers["signature"] && params["actor"] &&
String.contains?(headers["signature"], params["actor"]) do
Logger.debug(
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
)
Logger.debug(inspect(conn.req_headers))
end
conn
|> put_status(:bad_request)
|> json(dgettext("errors", "error"))
end
defp represent_service_actor(%User{} = user, conn) do defp represent_service_actor(%User{} = user, conn) do
with {:ok, user} <- User.ensure_keys_present(user) do with {:ok, user} <- User.ensure_keys_present(user) do
conn conn

View file

@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
type: [:module, {:list, :module}], type: [:module, {:list, :module}],
description: description:
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.", "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF} suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF.Policy}
}, },
%{ %{
key: :transparency, key: :transparency,
@ -157,9 +157,7 @@ def config_descriptions(policies) do
[description | acc] [description | acc]
else else
Logger.warn( Logger.warn(
"#{policy} config description doesn't have one or all required keys #{ "#{policy} config description doesn't have one or all required keys #{inspect(@required_description_keys)}"
inspect(@required_description_keys)
}"
) )
acc acc

View file

@ -38,9 +38,7 @@ defp steal_emoji({shortcode, url}, emoji_dir_path) do
end end
else else
Logger.debug( Logger.debug(
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{ "MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)"
size_limit
} B)"
) )
nil nil

View file

@ -23,9 +23,7 @@ defp lookup_subchain(actor) do
def filter(%{"actor" => actor} = message) do def filter(%{"actor" => actor} = message) do
with {:ok, match, subchain} <- lookup_subchain(actor) do with {:ok, match, subchain} <- lookup_subchain(actor) do
Logger.debug( Logger.debug(
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{ "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}"
inspect(subchain)
}"
) )
MRF.filter(subchain, message) MRF.filter(subchain, message)

View file

@ -213,6 +213,7 @@ def stringify_keys(%{__struct__: _} = object) do
def stringify_keys(object) when is_map(object) do def stringify_keys(object) when is_map(object) do
object object
|> Enum.filter(fn {_, v} -> v != nil end)
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end) |> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
end end

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@ -14,12 +13,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:type, :string) unquote do
field(:object, ObjectValidators.ObjectID) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:actor, ObjectValidators.ObjectID) message_fields()
field(:to, ObjectValidators.Recipients, default: []) activity_fields()
field(:cc, ObjectValidators.Recipients, default: []) end
end
end end
def cast_data(data) do def cast_data(data) do

View file

@ -10,19 +10,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
require Pleroma.Constants require Pleroma.Constants
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.User alias Pleroma.User
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:target) field(:target)
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID) quote do
field(:type) unquote do
field(:to, ObjectValidators.Recipients, default: []) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:cc, ObjectValidators.Recipients, default: []) message_fields()
activity_fields()
end
end
end end
def cast_and_validate(data) do def cast_and_validate(data) do

View file

@ -20,13 +20,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:type, :string) unquote do
field(:object, ObjectValidators.ObjectID) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:actor, ObjectValidators.ObjectID) message_fields()
activity_fields()
end
end
field(:context, :string) field(:context, :string)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:published, ObjectValidators.DateTime) field(:published, ObjectValidators.DateTime)
end end

View file

@ -15,12 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
@derive Jason.Encoder @derive Jason.Encoder
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:to, ObjectValidators.Recipients, default: []) unquote do
field(:cc, ObjectValidators.Recipients, default: []) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:bto, ObjectValidators.Recipients, default: []) message_fields()
field(:bcc, ObjectValidators.Recipients, default: []) end
field(:type, :string) end
field(:name, :string) field(:name, :string)
field(:inReplyTo, ObjectValidators.ObjectID) field(:inReplyTo, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID) field(:attributedTo, ObjectValidators.ObjectID)

View file

@ -6,10 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset import Ecto.Changeset
@ -18,38 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
@derive Jason.Encoder @derive Jason.Encoder
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:to, ObjectValidators.Recipients, default: []) unquote do
field(:cc, ObjectValidators.Recipients, default: []) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:bto, ObjectValidators.Recipients, default: []) message_fields()
field(:bcc, ObjectValidators.Recipients, default: []) object_fields()
embeds_many(:tag, TagValidator) status_object_fields()
field(:type, :string) end
end
field(:name, :string)
field(:summary, :string)
field(:content, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
field(:replies, {:array, ObjectValidators.ObjectID}, default: []) field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
end end

View file

@ -68,12 +68,14 @@ def fix_media_type(data) do
end end
end end
defp handle_href(href, mediaType) do defp handle_href(href, mediaType, data) do
[ [
%{ %{
"href" => href, "href" => href,
"type" => "Link", "type" => "Link",
"mediaType" => mediaType "mediaType" => mediaType,
"width" => data["width"],
"height" => data["height"]
} }
] ]
end end
@ -81,10 +83,10 @@ defp handle_href(href, mediaType) do
defp fix_url(data) do defp fix_url(data) do
cond do cond do
is_binary(data["url"]) -> is_binary(data["url"]) ->
Map.put(data, "url", handle_href(data["url"], data["mediaType"])) Map.put(data, "url", handle_href(data["url"], data["mediaType"], data))
is_binary(data["href"]) and data["url"] == nil -> is_binary(data["href"]) and data["url"] == nil ->
Map.put(data, "url", handle_href(data["href"], data["mediaType"])) Map.put(data, "url", handle_href(data["href"], data["mediaType"], data))
true -> true ->
data data

View file

@ -5,11 +5,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset import Ecto.Changeset
@ -18,38 +15,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
@derive Jason.Encoder @derive Jason.Encoder
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:to, ObjectValidators.Recipients, default: []) unquote do
field(:cc, ObjectValidators.Recipients, default: []) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:bto, ObjectValidators.Recipients, default: []) message_fields()
field(:bcc, ObjectValidators.Recipients, default: []) object_fields()
embeds_many(:tag, TagValidator) status_object_fields()
field(:type, :string) end
end
field(:name, :string)
field(:summary, :string)
field(:content, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
end end
def cast_and_apply(data) do def cast_and_apply(data) do

View file

@ -5,20 +5,21 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
import Ecto.Changeset import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false @primary_key false
@derive Jason.Encoder
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:type, :string) unquote do
field(:actor, ObjectValidators.ObjectID) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:to, ObjectValidators.Recipients, default: []) message_fields()
field(:cc, ObjectValidators.Recipients, default: []) activity_fields()
field(:object, ObjectValidators.ObjectID) end
end
end end
def cast_data(data) do def cast_data(data) do
@ -30,8 +31,8 @@ defp validate_data(cng) do
cng cng
|> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Block"]) |> validate_inclusion(:type, ["Block"])
|> validate_actor_presence() |> CommonValidations.validate_actor_presence()
|> validate_actor_presence(field_name: :object) |> CommonValidations.validate_actor_presence(field_name: :object)
end end
def cast_and_validate(data) do def cast_and_validate(data) do

View file

@ -0,0 +1,68 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
# Activities and Objects, except (Create)ChatMessage
defmacro message_fields do
quote bind_quoted: binding() do
field(:type, :string)
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
end
end
defmacro activity_fields do
quote bind_quoted: binding() do
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID)
end
end
# All objects except Answer and CHatMessage
defmacro object_fields do
quote bind_quoted: binding() do
field(:content, :string)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
embeds_many(:attachment, AttachmentValidator)
end
end
# Basically objects that aren't ChatMessage and Answer
defmacro status_object_fields do
quote bind_quoted: binding() do
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
embeds_many(:tag, TagValidator)
field(:name, :string)
field(:summary, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
field(:sensitive, :boolean, default: false)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
end
end
end

View file

@ -17,11 +17,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
activity_fields()
end
end
field(:id, ObjectValidators.ObjectID, primary_key: true) field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:actor, ObjectValidators.ObjectID)
field(:type, :string) field(:type, :string)
field(:to, ObjectValidators.Recipients, default: []) field(:to, ObjectValidators.Recipients, default: [])
field(:object, ObjectValidators.ObjectID)
end end
def cast_and_apply(data) do def cast_and_apply(data) do

View file

@ -20,14 +20,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:actor, ObjectValidators.ObjectID) unquote do
field(:type, :string) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:to, ObjectValidators.Recipients, default: []) message_fields()
field(:cc, ObjectValidators.Recipients, default: []) activity_fields()
field(:bto, ObjectValidators.Recipients, default: []) end
field(:bcc, ObjectValidators.Recipients, default: []) end
field(:object, ObjectValidators.ObjectID)
field(:expires_at, ObjectValidators.DateTime) field(:expires_at, ObjectValidators.DateTime)
# Should be moved to object, done for CommonAPI.Utils.make_context # Should be moved to object, done for CommonAPI.Utils.make_context

View file

@ -15,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:type, :string) unquote do
field(:actor, ObjectValidators.ObjectID) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:to, ObjectValidators.Recipients, default: []) message_fields()
field(:cc, ObjectValidators.Recipients, default: []) activity_fields()
end
end
field(:deleted_activity_id, ObjectValidators.ObjectID) field(:deleted_activity_id, ObjectValidators.ObjectID)
field(:object, ObjectValidators.ObjectID)
end end
def cast_data(data) do def cast_data(data) do

View file

@ -5,7 +5,6 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
@ -15,14 +14,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:type, :string) unquote do
field(:object, ObjectValidators.ObjectID) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:actor, ObjectValidators.ObjectID) message_fields()
activity_fields()
end
end
field(:context, :string) field(:context, :string)
field(:content, :string) field(:content, :string)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
end end
def cast_and_validate(data) do def cast_and_validate(data) do

View file

@ -5,11 +5,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset import Ecto.Changeset
@ -19,38 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
# Extends from NoteValidator # Extends from NoteValidator
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:to, ObjectValidators.Recipients, default: []) unquote do
field(:cc, ObjectValidators.Recipients, default: []) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:bto, ObjectValidators.Recipients, default: []) message_fields()
field(:bcc, ObjectValidators.Recipients, default: []) object_fields()
embeds_many(:tag, TagValidator) status_object_fields()
field(:type, :string) end
end
field(:name, :string)
field(:summary, :string)
field(:content, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
end end
def cast_and_apply(data) do def cast_and_apply(data) do

View file

@ -5,20 +5,20 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:type, :string) unquote do
field(:actor, ObjectValidators.ObjectID) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:to, ObjectValidators.Recipients, default: []) message_fields()
field(:cc, ObjectValidators.Recipients, default: []) activity_fields()
field(:object, ObjectValidators.ObjectID) end
end
field(:state, :string, default: "pending") field(:state, :string, default: "pending")
end end

View file

@ -5,7 +5,6 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@ -16,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:type, :string) unquote do
field(:object, ObjectValidators.ObjectID) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:actor, ObjectValidators.ObjectID) message_fields()
activity_fields()
end
end
field(:context, :string) field(:context, :string)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
end end
def cast_and_validate(data) do def cast_and_validate(data) do

View file

@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset import Ecto.Changeset
@ -20,35 +18,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
# Extends from NoteValidator # Extends from NoteValidator
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:to, ObjectValidators.Recipients, default: []) unquote do
field(:cc, ObjectValidators.Recipients, default: []) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:bto, ObjectValidators.Recipients, default: []) message_fields()
field(:bcc, ObjectValidators.Recipients, default: []) object_fields()
embeds_many(:tag, TagValidator) status_object_fields()
field(:type, :string) end
field(:content, :string) end
field(:context, :string)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:summary, :string)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
field(:closed, ObjectValidators.DateTime) field(:closed, ObjectValidators.DateTime)
field(:voters, {:array, ObjectValidators.ObjectID}, default: []) field(:voters, {:array, ObjectValidators.ObjectID}, default: [])

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.User alias Pleroma.User
import Ecto.Changeset import Ecto.Changeset
@ -15,12 +14,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:type, :string) unquote do
field(:object, ObjectValidators.ObjectID) import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
field(:actor, ObjectValidators.ObjectID) message_fields()
field(:to, ObjectValidators.Recipients, default: []) activity_fields()
field(:cc, ObjectValidators.Recipients, default: []) end
end
end end
def cast_and_validate(data) do def cast_and_validate(data) do

View file

@ -13,11 +13,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
@primary_key false @primary_key false
embedded_schema do embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true) quote do
field(:type, :string) unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
end
end
field(:actor, ObjectValidators.ObjectID) field(:actor, ObjectValidators.ObjectID)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
# In this case, we save the full object in this activity instead of just a # In this case, we save the full object in this activity instead of just a
# reference, so we can always see what was actually changed by this. # reference, so we can always see what was actually changed by this.
field(:object, :map) field(:object, :map)

View file

@ -63,18 +63,17 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
date: date date: date
}) })
with {:ok, %{status: code}} when code in 200..299 <- with {:ok, %{status: code}} = result when code in 200..299 <-
result = HTTP.post(
HTTP.post( inbox,
inbox, json,
json, [
[ {"Content-Type", "application/activity+json"},
{"Content-Type", "application/activity+json"}, {"Date", date},
{"Date", date}, {"signature", signature},
{"signature", signature}, {"digest", digest}
{"digest", digest} ]
] ) do
) do
if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
Instances.set_reachable(inbox) Instances.set_reachable(inbox)
end end

View file

@ -199,8 +199,9 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, notifications} = Notification.create_notifications(activity, do_send: false) {:ok, notifications} = Notification.create_notifications(activity, do_send: false)
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object) {:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
{:ok, _user} = ActivityPub.update_last_status_at_if_public(user, object)
if in_reply_to = object.data["inReplyTo"] && object.data["type"] != "Answer" do if in_reply_to = object.data["type"] != "Answer" && object.data["inReplyTo"] do
Object.increase_replies_count(in_reply_to) Object.increase_replies_count(in_reply_to)
end end

View file

@ -446,7 +446,7 @@ def update_follow_state_for_all(
|> Activity.Queries.by_type() |> Activity.Queries.by_type()
|> Activity.Queries.by_actor(actor) |> Activity.Queries.by_actor(actor)
|> Activity.Queries.by_object_id(object) |> Activity.Queries.by_object_id(object)
|> where(fragment("data->>'state' = 'pending'")) |> where(fragment("data->>'state' = 'pending'") or fragment("data->>'state' = 'accept'"))
|> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)]) |> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)])
|> Repo.update_all([]) |> Repo.update_all([])

View file

@ -49,7 +49,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["admin:read:statuses"]} %{scopes: ["admin:read:statuses"]}
when action in [:list_user_statuses, :list_instance_statuses] when action in [:list_user_statuses]
) )
plug( plug(
@ -81,24 +81,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action_fallback(AdminAPI.FallbackController) action_fallback(AdminAPI.FallbackController)
def list_instance_statuses(conn, %{"instance" => instance} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params)
result =
ActivityPub.fetch_statuses(nil, %{
instance: instance,
limit: page_size,
offset: (page - 1) * page_size,
exclude_reblogs: not with_reblogs,
total: true
})
conn
|> put_view(AdminAPI.StatusView)
|> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
end
def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
godmode = params["godmode"] == "true" || params["godmode"] == true godmode = params["godmode"] == "true" || params["godmode"] == true

View file

@ -0,0 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.InstanceController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 3]
alias Pleroma.Instances.Instance
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
@default_page_size 50
plug(
OAuthScopesPlug,
%{scopes: ["admin:read:statuses"]}
when action in [:list_statuses]
)
plug(
OAuthScopesPlug,
%{scopes: ["admin:write:accounts", "admin:write:statuses"]}
when action in [:delete]
)
action_fallback(AdminAPI.FallbackController)
def list_statuses(conn, %{"instance" => instance} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params)
result =
ActivityPub.fetch_statuses(nil, %{
instance: instance,
limit: page_size,
offset: (page - 1) * page_size,
exclude_reblogs: not with_reblogs,
total: true
})
conn
|> put_view(AdminAPI.StatusView)
|> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
end
def delete(conn, %{"instance" => instance}) do
with {:ok, _job} <- Instance.delete_users_and_activities(instance) do
json(conn, instance)
end
end
defp page_params(params) do
{
fetch_integer_param(params, "page", 1),
fetch_integer_param(params, "page_size", @default_page_size)
}
end
end

View file

@ -35,7 +35,9 @@ defmodule Pleroma.Web.AdminAPI.UserController do
:toggle_activation, :toggle_activation,
:activate, :activate,
:deactivate, :deactivate,
:approve :approve,
:suggest,
:unsuggest
] ]
) )
@ -239,6 +241,32 @@ def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = c
render(conn, "index.json", users: updated_users) render(conn, "index.json", users: updated_users)
end end
def suggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.set_suggestion(users, true)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "add_suggestion"
})
render(conn, "index.json", users: updated_users)
end
def unsuggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.set_suggestion(users, false)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "remove_suggestion"
})
render(conn, "index.json", users: updated_users)
end
def index(conn, params) do def index(conn, params) do
{page, page_size} = page_params(params) {page, page_size} = page_params(params)
filters = maybe_parse_filters(params[:filters]) filters = maybe_parse_filters(params[:filters])

View file

@ -80,6 +80,7 @@ def render("show.json", %{user: user}) do
"tags" => user.tags || [], "tags" => user.tags || [],
"is_confirmed" => user.is_confirmed, "is_confirmed" => user.is_confirmed,
"is_approved" => user.is_approved, "is_approved" => user.is_approved,
"is_suggested" => user.is_suggested,
"url" => user.uri || user.ap_id, "url" => user.uri || user.ap_id,
"registration_reason" => user.registration_reason, "registration_reason" => user.registration_reason,
"actor_type" => user.actor_type, "actor_type" => user.actor_type,

View file

@ -226,6 +226,12 @@ def follow_operation do
type: :boolean, type: :boolean,
description: "Receive this account's reblogs in home timeline? Defaults to true.", description: "Receive this account's reblogs in home timeline? Defaults to true.",
default: true default: true
},
notify: %Schema{
type: :boolean,
description:
"Receive notifications for all statuses posted by the account? Defaults to false.",
default: false
} }
} }
}, },
@ -328,6 +334,29 @@ def unblock_operation do
} }
end end
def note_operation do
%Operation{
tags: ["Account actions"],
summary: "Set a private note about a user.",
operationId: "AccountController.note",
security: [%{"oAuth" => ["follow", "write:accounts"]}],
requestBody: request_body("Parameters", note_request()),
description: "Create a note for the given account.",
parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
Operation.parameter(
:comment,
:query,
%Schema{type: :string},
"Account note body"
)
],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
def follow_by_uri_operation do def follow_by_uri_operation do
%Operation{ %Operation{
tags: ["Account actions"], tags: ["Account actions"],
@ -685,9 +714,11 @@ defp array_of_relationships do
"blocked_by" => true, "blocked_by" => true,
"muting" => false, "muting" => false,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"domain_blocking" => false, "domain_blocking" => false,
"subscribing" => false, "subscribing" => false,
"notifying" => false,
"endorsed" => true "endorsed" => true
}, },
%{ %{
@ -699,9 +730,11 @@ defp array_of_relationships do
"blocked_by" => true, "blocked_by" => true,
"muting" => true, "muting" => true,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => true, "requested" => true,
"domain_blocking" => false, "domain_blocking" => false,
"subscribing" => false, "subscribing" => false,
"notifying" => false,
"endorsed" => false "endorsed" => false
}, },
%{ %{
@ -713,9 +746,11 @@ defp array_of_relationships do
"blocked_by" => false, "blocked_by" => false,
"muting" => true, "muting" => true,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"domain_blocking" => true, "domain_blocking" => true,
"subscribing" => true, "subscribing" => true,
"notifying" => true,
"endorsed" => false "endorsed" => false
} }
] ]
@ -760,6 +795,23 @@ defp mute_request do
} }
end end
defp note_request do
%Schema{
title: "AccountNoteRequest",
description: "POST body for adding a note for an account",
type: :object,
properties: %{
comment: %Schema{
type: :string,
description: "Account note body"
}
},
example: %{
"comment" => "Example note"
}
}
end
defp array_of_lists do defp array_of_lists do
%Schema{ %Schema{
title: "ArrayOfLists", title: "ArrayOfLists",

View file

@ -216,7 +216,71 @@ def approve_operation do
request_body( request_body(
"Parameters", "Parameters",
%Schema{ %Schema{
description: "POST body for deleting multiple users", description: "POST body for approving multiple users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :object,
properties: %{user: %Schema{type: :array, items: user()}}
}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def suggest_operation do
%Operation{
tags: ["User administration"],
summary: "Suggest multiple users",
operationId: "AdminAPI.UserController.suggest",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for adding multiple suggested users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :object,
properties: %{user: %Schema{type: :array, items: user()}}
}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def unsuggest_operation do
%Operation{
tags: ["User administration"],
summary: "Unsuggest multiple users",
operationId: "AdminAPI.UserController.unsuggest",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for removing multiple suggested users",
type: :object, type: :object,
properties: %{ properties: %{
nicknames: %Schema{ nicknames: %Schema{

View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.DirectoryOperation do
alias OpenApiSpex.Operation
alias Pleroma.Web.ApiSpec.AccountOperation
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["Directory"],
summary: "Profile directory",
operationId: "DirectoryController.index",
parameters:
[
Operation.parameter(
:order,
:query,
:string,
"Order by recent activity or account creation",
required: nil
),
Operation.parameter(:local, :query, BooleanLike, "Include local users only")
] ++ pagination_params(),
responses: %{
200 =>
Operation.response("Accounts", "application/json", AccountOperation.array_of_accounts()),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
end

View file

@ -121,7 +121,10 @@ defp change_email_request do
type: :object, type: :object,
required: [:email, :password], required: [:email, :password],
properties: %{ properties: %{
email: %Schema{type: :string, description: "New email"}, email: %Schema{
type: :string,
description: "New email. Set to blank to remove the user's email."
},
password: %Schema{type: :string, description: "Current password"} password: %Schema{type: :string, description: "Current password"}
} }
} }
@ -188,6 +191,7 @@ def delete_account_operation do
parameters: [ parameters: [
Operation.parameter(:password, :query, :string, "Password") Operation.parameter(:password, :query, :string, "Password")
], ],
requestBody: request_body("Parameters", delete_account_request(), required: false),
responses: %{ responses: %{
200 => 200 =>
Operation.response("Success", "application/json", %Schema{ Operation.response("Success", "application/json", %Schema{
@ -234,4 +238,48 @@ def remote_subscribe_operation do
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})} responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
} }
end end
def remote_interaction_operation do
%Operation{
tags: ["Accounts"],
summary: "Remote interaction",
operationId: "UtilController.remote_interaction",
requestBody: request_body("Parameters", remote_interaction_request(), required: true),
responses: %{
200 =>
Operation.response("Remote interaction URL", "application/json", %Schema{type: :object})
}
}
end
defp remote_interaction_request do
%Schema{
title: "RemoteInteractionRequest",
description: "POST body for remote interaction",
type: :object,
required: [:ap_id, :profile],
properties: %{
ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"},
profile: %Schema{type: :string, description: "Remote profile webfinger"}
}
}
end
defp delete_account_request do
%Schema{
title: "AccountDeleteRequest",
description: "POST body for deleting one's own account",
type: :object,
properties: %{
password: %Schema{
type: :string,
description: "The user's own password for confirmation.",
format: :password
}
},
example: %{
"password" => "prettyp0ony1313"
}
}
end
end end

View file

@ -194,9 +194,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"id" => "9tKi3esbG7OQgZ2920", "id" => "9tKi3esbG7OQgZ2920",
"muting" => false, "muting" => false,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"showing_reblogs" => true, "showing_reblogs" => true,
"subscribing" => false "subscribing" => false,
"notifying" => false
}, },
"settings_store" => %{ "settings_store" => %{
"pleroma-fe" => %{} "pleroma-fe" => %{}

View file

@ -22,9 +22,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
id: FlakeID, id: FlakeID,
muting: %Schema{type: :boolean}, muting: %Schema{type: :boolean},
muting_notifications: %Schema{type: :boolean}, muting_notifications: %Schema{type: :boolean},
note: %Schema{type: :string},
requested: %Schema{type: :boolean}, requested: %Schema{type: :boolean},
showing_reblogs: %Schema{type: :boolean}, showing_reblogs: %Schema{type: :boolean},
subscribing: %Schema{type: :boolean} subscribing: %Schema{type: :boolean},
notifying: %Schema{type: :boolean}
}, },
example: %{ example: %{
"blocked_by" => false, "blocked_by" => false,
@ -36,9 +38,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
"id" => "9tKi3esbG7OQgZ2920", "id" => "9tKi3esbG7OQgZ2920",
"muting" => false, "muting" => false,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"showing_reblogs" => true, "showing_reblogs" => true,
"subscribing" => false "subscribing" => false,
"notifying" => false
} }
}) })
end end

View file

@ -282,9 +282,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"id" => "9toJCsKN7SmSf3aj5c", "id" => "9toJCsKN7SmSf3aj5c",
"muting" => false, "muting" => false,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"showing_reblogs" => true, "showing_reblogs" => true,
"subscribing" => false "subscribing" => false,
"notifying" => false
}, },
"skip_thread_containment" => false, "skip_thread_containment" => false,
"tags" => [] "tags" => []

View file

@ -487,9 +487,7 @@ def remove_mute(user_id, activity_id) do
else else
{what, result} = error -> {what, result} = error ->
Logger.warn( Logger.warn(
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{ "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}"
activity_id
}"
) )
{:error, error} {:error, error}

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.Endpoint do
alias Pleroma.Config alias Pleroma.Config
socket("/socket", Pleroma.Web.UserSocket) socket("/socket", Pleroma.Web.UserSocket)
socket("/live", Phoenix.LiveView.Socket)
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint]) plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])

View file

@ -18,6 +18,8 @@ defmodule Pleroma.Web.Feed.UserController do
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user}) Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
else
_ -> Pleroma.Web.Fallback.RedirectController.redirector(conn, nil)
end end
end end

View file

@ -0,0 +1,14 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ManifestController do
use Pleroma.Web, :controller
plug(:skip_auth when action == :show)
@doc "GET /manifest.json"
def show(conn, _params) do
render(conn, "manifest.json")
end
end

View file

@ -1,61 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastoFEController do
use Pleroma.Web, :controller
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AuthController
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
# Note: :index action handles attempt of unauthenticated access to private instance with redirect
plug(:skip_public_check when action == :index)
plug(
OAuthScopesPlug,
%{scopes: ["read"], fallback: :proceed_unauthenticated}
when action == :index
)
plug(:skip_auth when action == :manifest)
@doc "GET /web/*path"
def index(conn, _params) do
with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
{:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
conn
|> put_layout(false)
|> render("index.html",
token: token.token,
user: user,
custom_emojis: Pleroma.Emoji.get_all()
)
else
_ ->
conn
|> put_session(:return_to, conn.request_path)
|> redirect(to: "/web/login")
end
end
@doc "GET /web/manifest.json"
def manifest(conn, _params) do
render(conn, "manifest.json")
end
@doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
with {:ok, _} <- User.mastodon_settings_update(user, settings) do
json(conn, %{})
else
e ->
conn
|> put_status(:internal_server_error)
|> json(%{error: inspect(e)})
end
end
end

View file

@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Maps alias Pleroma.Maps
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserNote
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Pipeline
@ -53,7 +54,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
when action in [:verify_credentials, :endorsements, :identity_proofs] when action in [:verify_credentials, :endorsements, :identity_proofs]
) )
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials) plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]}
when action in [:update_credentials, :note]
)
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
@ -79,7 +84,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute]) plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
@relationship_actions [:follow, :unfollow] @relationship_actions [:follow, :unfollow]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a @needs_account ~W(followers following lists follow unfollow mute unmute block unblock note)a
plug( plug(
RateLimiter, RateLimiter,
@ -435,6 +440,16 @@ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
end end
end end
@doc "POST /api/v1/accounts/:id/note"
def note(
%{assigns: %{user: noter, account: target}, body_params: %{comment: comment}} = conn,
_params
) do
with {:ok, _user_note} <- UserNote.create(noter, target, comment) do
render(conn, "relationship.json", user: noter, target: target)
end
end
@doc "POST /api/v1/follows" @doc "POST /api/v1/follows"
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do case User.get_cached_by_nickname(uri) do

View file

@ -25,8 +25,6 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
@local_mastodon_name "Mastodon-Local"
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
@doc "POST /api/v1/apps" @doc "POST /api/v1/apps"
@ -47,7 +45,6 @@ def create(%{assigns: %{user: user}, body_params: params} = conn, _params) do
|> Maps.put_if_present(:user_id, user_id) |> Maps.put_if_present(:user_id, user_id)
with cs <- App.register_changeset(%App{}, app_attrs), with cs <- App.register_changeset(%App{}, app_attrs),
false <- cs.changes[:client_name] == @local_mastodon_name,
{:ok, app} <- Repo.insert(cs) do {:ok, app} <- Repo.insert(cs) do
render(conn, "show.json", app: app) render(conn, "show.json", app: app)
end end

View file

@ -7,77 +7,12 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
import Pleroma.Web.ControllerHelper, only: [json_response: 3] import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Helpers.AuthHelper
alias Pleroma.Helpers.UriHelper
alias Pleroma.User
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.TwitterAPI
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset) plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
@local_mastodon_name "Mastodon-Local"
@doc "GET /web/login"
# Local Mastodon FE login callback action
def login(conn, %{"code" => auth_token} = params) do
with {:ok, app} <- local_mastofe_app(),
{:ok, auth} <- Authorization.get_by_token(app, auth_token),
{:ok, oauth_token} <- Token.exchange_token(app, auth) do
redirect_to =
conn
|> local_mastodon_post_login_path()
|> UriHelper.modify_uri_params(%{"access_token" => oauth_token.token})
conn
|> AuthHelper.put_session_token(oauth_token.token)
|> redirect(to: redirect_to)
else
_ -> redirect_to_oauth_form(conn, params)
end
end
def login(conn, params) do
with %{assigns: %{user: %User{}, token: %Token{app_id: app_id}}} <- conn,
{:ok, %{id: ^app_id}} <- local_mastofe_app() do
redirect(conn, to: local_mastodon_post_login_path(conn))
else
_ -> redirect_to_oauth_form(conn, params)
end
end
defp redirect_to_oauth_form(conn, _params) do
with {:ok, app} <- local_mastofe_app() do
path =
Routes.o_auth_path(conn, :authorize,
response_type: "code",
client_id: app.client_id,
redirect_uri: ".",
scope: Enum.join(app.scopes, " ")
)
redirect(conn, to: path)
end
end
@doc "DELETE /auth/sign_out"
def logout(conn, _) do
conn =
with %{assigns: %{token: %Token{} = oauth_token}} <- conn,
session_token = AuthHelper.get_session_token(conn),
{:ok, %Token{token: ^session_token}} <- RevokeToken.revoke(oauth_token) do
AuthHelper.delete_session_token(conn)
else
_ -> conn
end
redirect(conn, to: "/")
end
@doc "POST /auth/password" @doc "POST /auth/password"
def password_reset(conn, params) do def password_reset(conn, params) do
nickname_or_email = params["email"] || params["nickname"] nickname_or_email = params["email"] || params["nickname"]
@ -86,23 +21,4 @@ def password_reset(conn, params) do
json_response(conn, :no_content, "") json_response(conn, :no_content, "")
end end
defp local_mastodon_post_login_path(conn) do
case get_session(conn, :return_to) do
nil ->
Routes.masto_fe_path(conn, :index, ["getting-started"])
return_to ->
delete_session(conn, :return_to)
return_to
end
end
@spec local_mastofe_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
def local_mastofe_app do
App.get_or_make(
%{client_name: @local_mastodon_name, redirect_uris: "."},
["read", "write", "follow", "push", "admin"]
)
end
end end

View file

@ -0,0 +1,82 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.DirectoryController do
use Pleroma.Web, :controller
import Ecto.Query
alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.MastodonAPI.AccountView
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:skip_auth when action == "index")
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DirectoryOperation
@doc "GET /api/v1/directory"
def index(%{assigns: %{user: user}} = conn, params) do
with true <- Pleroma.Config.get([:instance, :profile_directory]) do
limit = Map.get(params, :limit, 20) |> min(80)
users =
User.Query.build(%{is_discoverable: true, invisible: false, limit: limit})
|> order_by_creation_date(params)
|> exclude_remote(params)
|> exclude_user(user)
|> exclude_relationships(user, [:block, :mute])
|> Pagination.fetch_paginated(params, :offset)
conn
|> put_view(AccountView)
|> render("index.json", for: user, users: users, as: :user)
else
_ -> json(conn, [])
end
end
defp order_by_creation_date(query, %{order: "new"}) do
query
end
defp order_by_creation_date(query, _params) do
query
|> order_by([u], desc_nulls_last: u.last_status_at)
end
defp exclude_remote(query, %{local: true}) do
where(query, [u], u.local == true)
end
defp exclude_remote(query, _params) do
query
end
defp exclude_user(query, %User{id: user_id}) do
where(query, [u], u.id != ^user_id)
end
defp exclude_user(query, _user) do
query
end
defp exclude_relationships(query, %User{id: user_id}, relationship_types) do
query
|> join(:left, [u], r in UserRelationship,
as: :user_relationships,
on:
r.target_id == u.id and r.source_id == ^user_id and
r.relationship_type in ^relationship_types
)
|> where([user_relationships: r], is_nil(r.target_id))
end
defp exclude_relationships(query, _user, _relationship_types) do
query
end
end

View file

@ -17,6 +17,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
require Logger require Logger
@search_limit 40
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
# Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)
@ -77,7 +79,7 @@ defp search_options(params, user) do
[ [
resolve: params[:resolve], resolve: params[:resolve],
following: params[:following], following: params[:following],
limit: params[:limit], limit: min(params[:limit], @search_limit),
offset: params[:offset], offset: params[:offset],
type: params[:type], type: params[:type],
author: get_author(params), author: get_author(params),

View file

@ -4,11 +4,16 @@
defmodule Pleroma.Web.MastodonAPI.SuggestionController do defmodule Pleroma.Web.MastodonAPI.SuggestionController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Ecto.Query
alias Pleroma.FollowingRelationship
alias Pleroma.User
alias Pleroma.UserRelationship
require Logger require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index) plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action in [:index, :index2])
plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["write"]} when action in [:dismiss])
def open_api_operation(action) do def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation") operation = String.to_existing_atom("#{action}_operation")
@ -26,7 +31,90 @@ def index_operation do
} }
end end
def index2_operation do
%OpenApiSpex.Operation{
tags: ["Suggestions"],
summary: "Follow suggestions",
operationId: "SuggestionController.index2",
responses: %{
200 => Pleroma.Web.ApiSpec.Helpers.empty_array_response()
}
}
end
def dismiss_operation do
%OpenApiSpex.Operation{
tags: ["Suggestions"],
summary: "Remove a suggestion",
operationId: "SuggestionController.dismiss",
parameters: [
OpenApiSpex.Operation.parameter(
:account_id,
:path,
%OpenApiSpex.Schema{type: :string},
"Account to dismiss",
required: true
)
],
responses: %{
200 => Pleroma.Web.ApiSpec.Helpers.empty_object_response()
}
}
end
@doc "GET /api/v1/suggestions" @doc "GET /api/v1/suggestions"
def index(conn, params), def index(conn, params),
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params) do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
@doc "GET /api/v2/suggestions"
def index2(%{assigns: %{user: user}} = conn, params) do
limit = Map.get(params, :limit, 40) |> min(80)
users =
%{is_suggested: true, invisible: false, limit: limit}
|> User.Query.build()
|> exclude_user(user)
|> exclude_relationships(user, [:block, :mute, :suggestion_dismiss])
|> exclude_following(user)
|> Pleroma.Repo.all()
render(conn, "index.json", %{
users: users,
source: :staff,
for: user,
skip_visibility_check: true
})
end
defp exclude_user(query, %User{id: user_id}) do
where(query, [u], u.id != ^user_id)
end
defp exclude_relationships(query, %User{id: user_id}, relationship_types) do
query
|> join(:left, [u], r in UserRelationship,
as: :user_relationships,
on:
r.target_id == u.id and r.source_id == ^user_id and
r.relationship_type in ^relationship_types
)
|> where([user_relationships: r], is_nil(r.target_id))
end
defp exclude_following(query, %User{id: user_id}) do
query
|> join(:left, [u], r in FollowingRelationship,
as: :following_relationships,
on: r.following_id == u.id and r.follower_id == ^user_id and r.state == :follow_accept
)
|> where([following_relationships: r], is_nil(r.following_id))
end
@doc "DELETE /api/v1/suggestions/:account_id"
def dismiss(%{assigns: %{user: source}} = conn, %{account_id: user_id}) do
with %User{} = target <- User.get_cached_by_id(user_id),
{:ok, _} <- UserRelationship.create(:suggestion_dismiss, source, target) do
json(conn, %{})
end
end
end end

View file

@ -24,6 +24,7 @@ def follow(follower, followed, params \\ %{}) do
with {:ok, follower, _followed, _} <- result do with {:ok, follower, _followed, _} <- result do
options = cast_params(params) options = cast_params(params)
set_reblogs_visibility(options[:reblogs], result) set_reblogs_visibility(options[:reblogs], result)
set_subscription(options[:notify], result)
{:ok, follower} {:ok, follower}
end end
end end
@ -36,6 +37,16 @@ defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do
CommonAPI.show_reblogs(follower, followed) CommonAPI.show_reblogs(follower, followed)
end end
defp set_subscription(true, {:ok, follower, followed, _}) do
User.subscribe(follower, followed)
end
defp set_subscription(false, {:ok, follower, followed, _}) do
User.unsubscribe(follower, followed)
end
defp set_subscription(_, _), do: {:ok, nil}
@spec get_followers(User.t(), map()) :: list(User.t()) @spec get_followers(User.t(), map()) :: list(User.t())
def get_followers(user, params \\ %{}) do def get_followers(user, params \\ %{}) do
user user
@ -73,7 +84,8 @@ defp cast_params(params) do
exclude_visibilities: {:array, :string}, exclude_visibilities: {:array, :string},
reblogs: :boolean, reblogs: :boolean,
with_muted: :boolean, with_muted: :boolean,
account_ap_id: :string account_ap_id: :string,
notify: :boolean
} }
changeset = cast({%{}, param_types}, params, Map.keys(param_types)) changeset = cast({%{}, param_types}, params, Map.keys(param_types))

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
alias Pleroma.FollowingRelationship alias Pleroma.FollowingRelationship
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserNote
alias Pleroma.UserRelationship alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
@ -101,6 +102,15 @@ def render(
User.following?(target, reading_user) User.following?(target, reading_user)
end end
subscribing =
UserRelationship.exists?(
user_relationships,
:inverse_subscription,
target,
reading_user,
&User.subscribed_to?(&2, &1)
)
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
%{ %{
id: to_string(target.id), id: to_string(target.id),
@ -138,14 +148,8 @@ def render(
target, target,
&User.muted_notifications?(&1, &2) &User.muted_notifications?(&1, &2)
), ),
subscribing: subscribing: subscribing,
UserRelationship.exists?( notifying: subscribing,
user_relationships,
:inverse_subscription,
target,
reading_user,
&User.subscribed_to?(&2, &1)
),
requested: follow_state == :follow_pending, requested: follow_state == :follow_pending,
domain_blocking: User.blocks_domain?(reading_user, target), domain_blocking: User.blocks_domain?(reading_user, target),
showing_reblogs: showing_reblogs:
@ -156,7 +160,12 @@ def render(
target, target,
&User.muting_reblogs?(&1, &2) &User.muting_reblogs?(&1, &2)
), ),
endorsed: false endorsed: false,
note:
UserNote.show(
reading_user,
target
)
} }
end end
@ -261,6 +270,7 @@ defp do_render("show.json", %{user: user} = opts) do
actor_type: user.actor_type actor_type: user.actor_type
} }
}, },
last_status_at: user.last_status_at,
# Pleroma extensions # Pleroma extensions
# Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub # Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub
@ -269,6 +279,7 @@ defp do_render("show.json", %{user: user} = opts) do
ap_id: user.ap_id, ap_id: user.ap_id,
also_known_as: user.also_known_as, also_known_as: user.also_known_as,
is_confirmed: user.is_confirmed, is_confirmed: user.is_confirmed,
is_suggested: user.is_suggested,
tags: user.tags, tags: user.tags,
hide_followers_count: user.hide_followers_count, hide_followers_count: user.hide_followers_count,
hide_follows_count: user.hide_follows_count, hide_follows_count: user.hide_follows_count,

View file

@ -45,7 +45,8 @@ def render("show.json", _) do
features: features(), features: features(),
federation: federation(), federation: federation(),
fields_limits: fields_limits(), fields_limits: fields_limits(),
post_formats: Config.get([:instance, :allowed_post_formats]) post_formats: Config.get([:instance, :allowed_post_formats]),
privileged_staff: Config.get([:instance, :privileged_staff])
}, },
stats: %{mau: Pleroma.User.active_user_count()}, stats: %{mau: Pleroma.User.active_user_count()},
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
@ -59,6 +60,7 @@ def features do
"mastodon_api", "mastodon_api",
"mastodon_api_streaming", "mastodon_api_streaming",
"polls", "polls",
"v2_suggestions",
"pleroma_explicit_addressing", "pleroma_explicit_addressing",
"shareable_emoji_packs", "shareable_emoji_packs",
"multifetch", "multifetch",
@ -83,7 +85,13 @@ def features do
"safe_dm_mentions" "safe_dm_mentions"
end, end,
"pleroma_emoji_reactions", "pleroma_emoji_reactions",
"pleroma_chat_messages" "pleroma_chat_messages",
if Config.get([:instance, :show_reactions]) do
"exposable_reactions"
end,
if Config.get([:instance, :profile_directory]) do
"profile_directory"
end
] ]
|> Enum.filter(& &1) |> Enum.filter(& &1)
end end

View file

@ -0,0 +1,28 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SuggestionView do
use Pleroma.Web, :view
alias Pleroma.Web.MastodonAPI.AccountView
@source_types [:staff, :global, :past_interactions]
def render("index.json", %{users: users} = opts) do
Enum.map(users, fn user ->
opts =
opts
|> Map.put(:user, user)
|> Map.delete(:users)
render("show.json", opts)
end)
end
def render("show.json", %{source: source, user: _user} = opts) when source in @source_types do
%{
source: source,
account: AccountView.render("show.json", opts)
}
end
end

View file

@ -49,9 +49,7 @@ def init(%{qs: qs} = req, state) do
def websocket_init(state) do def websocket_init(state) do
Logger.debug( Logger.debug(
"#{__MODULE__} accepted websocket connection for user #{ "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}"
(state.user || %{id: "anonymous"}).id
}, topic #{state.topic}"
) )
Streamer.add_socket(state.topic, state.user) Streamer.add_socket(state.topic, state.user)
@ -106,9 +104,7 @@ def terminate(_reason, _req, []), do: :ok
def terminate(reason, _req, state) do def terminate(reason, _req, state) do
Logger.debug( Logger.debug(
"#{__MODULE__} terminating websocket connection for user #{ "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic || "?"}: #{inspect(reason)}"
(state.user || %{id: "anonymous"}).id
}, topic #{state.topic || "?"}: #{inspect(reason)}"
) )
Streamer.remove_socket(state.topic) Streamer.remove_socket(state.topic)

View file

@ -35,7 +35,9 @@ def get_nodeinfo("2.0") do
openRegistrations: Config.get([:instance, :registrations_open]), openRegistrations: Config.get([:instance, :registrations_open]),
usage: %{ usage: %{
users: %{ users: %{
total: Map.get(stats, :user_count, 0) total: Map.get(stats, :user_count, 0),
activeMonth: Pleroma.User.active_user_count(30),
activeHalfyear: Pleroma.User.active_user_count(180)
}, },
localPosts: Map.get(stats, :status_count, 0) localPosts: Map.get(stats, :status_count, 0)
}, },
@ -67,7 +69,8 @@ def get_nodeinfo("2.0") do
mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false), mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
features: features, features: features,
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false) skipThreadContainment: Config.get([:instance, :skip_thread_containment], false),
privilegedStaff: Config.get([:instance, :privileged_staff])
} }
} }
end end

View file

@ -597,9 +597,6 @@ def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested
end end
end end
# Special case: Local MastodonFE
defp redirect_uri(%Plug.Conn{} = conn, "."), do: Routes.auth_url(conn, :login)
defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id) defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)

View file

@ -151,7 +151,9 @@ def index2(%{assigns: %{user: user}} = conn, params) do
index_query(user, params) index_query(user, params)
|> Pagination.fetch_paginated(params) |> Pagination.fetch_paginated(params)
render(conn, "index.json", chats: chats) conn
|> add_link_headers(chats)
|> render("index.json", chats: chats)
end end
defp index_query(%{id: user_id} = user, params) do defp index_query(%{id: user_id} = user, params) do

Some files were not shown because too many files have changed in this diff Show more