Merge remote-tracking branch 'origin/develop' into apps-api-endpoint
This commit is contained in:
commit
f5c3d45120
|
@ -79,7 +79,6 @@ unit-testing:
|
|||
- "**/*.ex"
|
||||
- "**/*.exs"
|
||||
- "mix.lock"
|
||||
retry: 2
|
||||
cache: &testing_cache_policy
|
||||
<<: *global_cache_policy
|
||||
policy: pull
|
||||
|
@ -94,6 +93,27 @@ unit-testing:
|
|||
- mix ecto.migrate
|
||||
- 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.
|
||||
# TODO Fix and reinstate federated testing
|
||||
# federated-testing:
|
||||
|
@ -117,7 +137,6 @@ unit-testing-rum:
|
|||
- "**/*.ex"
|
||||
- "**/*.exs"
|
||||
- "mix.lock"
|
||||
retry: 2
|
||||
cache: *testing_cache_policy
|
||||
services:
|
||||
- name: minibikini/postgres-with-rum:12
|
||||
|
@ -134,6 +153,7 @@ unit-testing-rum:
|
|||
- mix test --preload-modules
|
||||
|
||||
lint:
|
||||
image: elixir:1.12
|
||||
stage: test
|
||||
only:
|
||||
changes:
|
||||
|
@ -243,7 +263,7 @@ stop_review_app:
|
|||
|
||||
amd64:
|
||||
stage: release
|
||||
image: elixir:1.10.3
|
||||
image: elixir:1.10.4
|
||||
only: &release-only
|
||||
- stable@pleroma/pleroma
|
||||
- develop@pleroma/pleroma
|
||||
|
@ -281,7 +301,7 @@ amd64-musl:
|
|||
stage: release
|
||||
artifacts: *release-artifacts
|
||||
only: *release-only
|
||||
image: elixir:1.10.3-alpine
|
||||
image: elixir:1.10.4-alpine
|
||||
cache: *release-cache
|
||||
variables: *release-variables
|
||||
before_script: &before-release-musl
|
||||
|
@ -297,7 +317,7 @@ arm:
|
|||
only: *release-only
|
||||
tags:
|
||||
- arm32-specified
|
||||
image: arm32v7/elixir:1.10.3
|
||||
image: arm32v7/elixir:1.10.4
|
||||
cache: *release-cache
|
||||
variables: *release-variables
|
||||
before_script: *before-release
|
||||
|
@ -309,7 +329,7 @@ arm-musl:
|
|||
only: *release-only
|
||||
tags:
|
||||
- arm32-specified
|
||||
image: arm32v7/elixir:1.10.3-alpine
|
||||
image: arm32v7/elixir:1.10.4-alpine
|
||||
cache: *release-cache
|
||||
variables: *release-variables
|
||||
before_script: *before-release-musl
|
||||
|
@ -321,7 +341,7 @@ arm64:
|
|||
only: *release-only
|
||||
tags:
|
||||
- arm
|
||||
image: arm64v8/elixir:1.10.3
|
||||
image: arm64v8/elixir:1.10.4
|
||||
cache: *release-cache
|
||||
variables: *release-variables
|
||||
before_script: *before-release
|
||||
|
@ -333,7 +353,7 @@ arm64-musl:
|
|||
only: *release-only
|
||||
tags:
|
||||
- arm
|
||||
image: arm64v8/elixir:1.10.3-alpine
|
||||
image: arm64v8/elixir:1.10.4-alpine
|
||||
cache: *release-cache
|
||||
variables: *release-variables
|
||||
before_script: *before-release-musl
|
||||
|
@ -351,8 +371,8 @@ docker:
|
|||
IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||
IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest
|
||||
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_HASH: 71a7d01439aa8c165a25b59c44d3f016fddbd98b
|
||||
DOCKER_BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64
|
||||
DOCKER_BUILDX_HASH: 980e6b9655f971991fbbb5fd6cd19f1672386195
|
||||
before_script: &before-docker
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
- docker pull $IMAGE_TAG_SLUG || true
|
||||
|
|
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -6,19 +6,47 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Removed
|
||||
|
||||
- MastoFE
|
||||
|
||||
### Changed
|
||||
- Allow users to remove their emails if instance does not need email to register
|
||||
|
||||
### 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
|
||||
- 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
|
||||
|
||||
## Unreleased-patch
|
||||
- Mastodon API: Activity Search fallbacks on status fetching after a DB Timeout/Error
|
||||
## 2.4.1 - 2021-08-29
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -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.
|
||||
- Return OAuth token `id` (primary key) in POST `/oauth/token`.
|
||||
- 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.
|
||||
- Attachment dimensions and blurhashes are federated when available.
|
||||
- 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.
|
||||
- Remote users can no longer reappear after being 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`
|
||||
- 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
|
||||
|
@ -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.
|
||||
- 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.
|
||||
- `[: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`.
|
||||
- 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.
|
||||
|
|
|
@ -12,7 +12,7 @@ RUN apk add git gcc g++ musl-dev make cmake file-dev &&\
|
|||
mkdir release &&\
|
||||
mix release --path release
|
||||
|
||||
FROM alpine:3.11
|
||||
FROM alpine:3.14
|
||||
|
||||
ARG BUILD_DATE
|
||||
ARG VCS_REF
|
||||
|
@ -31,8 +31,7 @@ LABEL maintainer="ops@pleroma.social" \
|
|||
ARG HOME=/opt/pleroma
|
||||
ARG DATA=/var/lib/pleroma
|
||||
|
||||
RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
|
||||
apk update &&\
|
||||
RUN apk update &&\
|
||||
apk add exiftool ffmpeg imagemagick libmagic ncurses postgresql-client &&\
|
||||
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
|
||||
mkdir -p ${DATA}/uploads &&\
|
||||
|
|
|
@ -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/)
|
||||
|
||||
### 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
|
||||
While we don’t 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>.
|
||||
|
|
|
@ -394,7 +394,7 @@ defp get_actor(group, users), do: Enum.random(users[group])
|
|||
|
||||
defp other_data(actor, content) do
|
||||
%{host: host} = URI.parse(actor.ap_id)
|
||||
datetime = DateTime.utc_now()
|
||||
datetime = DateTime.utc_now() |> to_string()
|
||||
context_id = "https://#{host}/contexts/#{UUID.generate()}"
|
||||
activity_id = "https://#{host}/activities/#{UUID.generate()}"
|
||||
object_id = "https://#{host}/objects/#{UUID.generate()}"
|
||||
|
|
|
@ -99,15 +99,16 @@ defp hashtag_fetching(params, user, local_only) do
|
|||
|> Enum.map(&String.downcase(&1))
|
||||
|
||||
_activities =
|
||||
params
|
||||
|> Map.put(:type, "Create")
|
||||
|> Map.put(:local_only, local_only)
|
||||
|> Map.put(:blocking_user, user)
|
||||
|> Map.put(:muting_user, user)
|
||||
|> Map.put(:user, user)
|
||||
|> Map.put(:tag, tags)
|
||||
|> Map.put(:tag_all, tag_all)
|
||||
|> Map.put(:tag_reject, tag_reject)
|
||||
%{
|
||||
type: "Create",
|
||||
local_only: local_only,
|
||||
blocking_user: user,
|
||||
muting_user: user,
|
||||
user: user,
|
||||
tag: tags,
|
||||
tag_all: tag_all,
|
||||
tag_reject: tag_reject,
|
||||
}
|
||||
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,14 +17,14 @@ def run(_args) do
|
|||
# Let the user make 100 posts
|
||||
|
||||
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
|
||||
posts =
|
||||
users
|
||||
|> Enum.take_random(10)
|
||||
|> Enum.map(fn {:ok, random_user} ->
|
||||
{:ok, activity} = CommonAPI.post(random_user, %{"status" => "."})
|
||||
{:ok, activity} = CommonAPI.post(random_user, %{status: "."})
|
||||
activity
|
||||
end)
|
||||
|
||||
|
@ -42,7 +42,7 @@ def run(_args) do
|
|||
|> Conn.assign(:user, reading_user)
|
||||
|> 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
|
||||
},
|
||||
inputs: %{"user" => user, "no user" => nil},
|
||||
|
@ -50,7 +50,7 @@ def run(_args) do
|
|||
)
|
||||
|
||||
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(
|
||||
%{
|
||||
|
@ -60,7 +60,7 @@ def run(_args) do
|
|||
|> Conn.assign(:user, reading_user)
|
||||
|> 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
|
||||
},
|
||||
inputs: %{"user" => user, "no user" => nil},
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
# you can enable the server option below.
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
http: [port: 4001],
|
||||
url: [port: 4001],
|
||||
server: true
|
||||
url: [port: 4001]
|
||||
|
||||
# Disable captha for tests
|
||||
config :pleroma, Pleroma.Captcha,
|
||||
|
@ -44,7 +43,7 @@
|
|||
pool_size: 10
|
||||
|
||||
# Reduce hash rounds for testing
|
||||
config :pbkdf2_elixir, rounds: 1
|
||||
config :pleroma, :password, iterations: 1
|
||||
|
||||
config :tesla, adapter: Tesla.Mock
|
||||
|
||||
|
|
|
@ -139,6 +139,7 @@
|
|||
],
|
||||
protocol: "https",
|
||||
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
|
||||
live_view: [signing_salt: "U5ELgdEwTD3n1+D5s0rY0AMg8/y1STxZ3Zvsl3bWh+oBcGrYdil0rXqPMRd3Glcq"],
|
||||
signing_salt: "CqaoopA2",
|
||||
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
|
||||
pubsub_server: Pleroma.PubSub,
|
||||
|
@ -148,6 +149,8 @@
|
|||
]
|
||||
|
||||
# Configures Elixir's Logger
|
||||
config :logger, truncate: 65536
|
||||
|
||||
config :logger, :console,
|
||||
level: :debug,
|
||||
format: "\n$time $metadata[$level] $message\n",
|
||||
|
@ -253,7 +256,9 @@
|
|||
]
|
||||
],
|
||||
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,
|
||||
direct_message: [
|
||||
|
@ -321,9 +326,6 @@
|
|||
subjectLineBehavior: "email",
|
||||
theme: "pleroma-dark",
|
||||
webPushNotifications: false
|
||||
},
|
||||
masto_fe: %{
|
||||
showInstanceSpecificPanel: true
|
||||
}
|
||||
|
||||
config :pleroma, :assets,
|
||||
|
@ -352,6 +354,7 @@
|
|||
config :pleroma, :activitypub,
|
||||
unfollow_blocked: true,
|
||||
outgoing_blocks: true,
|
||||
blockers_visible: true,
|
||||
follow_handshake_timeout: 500,
|
||||
note_replies_output_limit: 5,
|
||||
sign_object_fetches: true,
|
||||
|
@ -853,6 +856,13 @@
|
|||
{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
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -936,6 +936,17 @@
|
|||
key: :show_reactions,
|
||||
type: :boolean,
|
||||
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,
|
||||
description:
|
||||
"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.",
|
||||
children: [
|
||||
%{
|
||||
|
@ -1364,25 +1375,6 @@
|
|||
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,
|
||||
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,
|
||||
type: :boolean,
|
||||
|
|
|
@ -230,6 +230,7 @@ Notes:
|
|||
### :activitypub
|
||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||
* `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
|
||||
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
||||
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
||||
|
@ -247,7 +248,7 @@ Notes:
|
|||
|
||||
### :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`
|
||||
|
||||
|
@ -258,10 +259,7 @@ config :pleroma, :frontend_configurations,
|
|||
pleroma_fe: %{
|
||||
theme: "pleroma-dark",
|
||||
# ... see /priv/static/static/config.json for the available keys.
|
||||
},
|
||||
masto_fe: %{
|
||||
showInstanceSpecificPanel: true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These settings **need to be complete**, they will override the defaults.
|
||||
|
|
|
@ -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
|
||||
|
||||
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 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
|
||||
```
|
||||
|
||||
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
|
||||
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.
|
||||
|
|
|
@ -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`
|
||||
|
||||
### 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`
|
||||
|
||||
### Retrives all latest statuses
|
||||
|
|
|
@ -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, `[]`
|
||||
|
||||
### Profile directory
|
||||
|
||||
*Added in Mastodon 3.0.0*
|
||||
|
||||
- `GET /api/v1/directory`: Returns HTTP 404
|
||||
|
||||
### Featured tags
|
||||
|
||||
*Added in Mastodon 3.0.0*
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
```
|
||||
|
|
@ -159,10 +159,12 @@ See [Admin-API](admin_api.md)
|
|||
"muting": false,
|
||||
"muting_notifications": false,
|
||||
"subscribing": true,
|
||||
"notifying": true,
|
||||
"requested": false,
|
||||
"domain_blocking": false,
|
||||
"showing_reblogs": true,
|
||||
"endorsed": false
|
||||
"endorsed": false,
|
||||
"note": ""
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -183,10 +185,12 @@ See [Admin-API](admin_api.md)
|
|||
"muting": false,
|
||||
"muting_notifications": false,
|
||||
"subscribing": false,
|
||||
"notifying": false,
|
||||
"requested": false,
|
||||
"domain_blocking": false,
|
||||
"showing_reblogs": true,
|
||||
"endorsed": false
|
||||
"endorsed": false,
|
||||
"note": ""
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
### 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).
|
||||
|
||||
### 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.
|
||||
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).
|
|
@ -9,7 +9,7 @@
|
|||
* GNU make
|
||||
* CMake
|
||||
|
||||
## Optionnal dependencies
|
||||
## Optional dependencies
|
||||
|
||||
* ImageMagick
|
||||
* FFmpeg
|
||||
|
|
|
@ -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.
|
|
@ -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 pleroma’s 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}"
|
|
@ -286,9 +286,7 @@ defp migrate_from_db(opts) do
|
|||
file = File.open!(tmp_config_path)
|
||||
|
||||
shell_info(
|
||||
"Saving database configuration settings to #{tmp_config_path}. Copy it to the #{
|
||||
Path.dirname(config_path)
|
||||
} manually."
|
||||
"Saving database configuration settings to #{tmp_config_path}. Copy it to the #{Path.dirname(config_path)} manually."
|
||||
)
|
||||
|
||||
write_config(file, tmp_config_path, opts)
|
||||
|
|
|
@ -209,7 +209,9 @@ def run(["set_text_search_config", tsconfig]) do
|
|||
new.fts_content := to_tsvector(new.data->>'content');
|
||||
RETURN new;
|
||||
END
|
||||
$$ LANGUAGE plpgsql"
|
||||
$$ LANGUAGE plpgsql",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
shell_info("Refresh RUM index")
|
||||
|
@ -219,7 +221,9 @@ def run(["set_text_search_config", tsconfig]) do
|
|||
|
||||
Ecto.Adapters.SQL.query!(
|
||||
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
|
||||
|
||||
|
|
|
@ -199,6 +199,7 @@ def run(["gen" | rest]) do
|
|||
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)
|
||||
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)
|
||||
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
|
||||
|
||||
|
@ -217,6 +218,7 @@ def run(["gen" | rest]) do
|
|||
secret: secret,
|
||||
jwt_secret: jwt_secret,
|
||||
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_private_key: Base.url_encode64(web_push_private_key, padding: false),
|
||||
db_configurable?: db_configurable?,
|
||||
|
|
|
@ -51,9 +51,7 @@ def run(["new", nickname, email | rest]) do
|
|||
A user will be created with the following information:
|
||||
- nickname: #{nickname}
|
||||
- email: #{email}
|
||||
- password: #{
|
||||
if(generated_password?, do: "[generated; a reset link will be created]", else: password)
|
||||
}
|
||||
- password: #{if(generated_password?, do: "[generated; a reset link will be created]", else: password)}
|
||||
- name: #{name}
|
||||
- bio: #{bio}
|
||||
- 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
|
||||
shell_info("Generated password reset token for #{user.nickname}")
|
||||
|
||||
IO.puts(
|
||||
"URL: #{
|
||||
Pleroma.Web.Router.Helpers.reset_password_url(
|
||||
Pleroma.Web.Endpoint,
|
||||
:reset,
|
||||
token.token
|
||||
)
|
||||
}"
|
||||
)
|
||||
IO.puts("URL: #{Pleroma.Web.Router.Helpers.reset_password_url(Pleroma.Web.Endpoint,
|
||||
:reset,
|
||||
token.token)}")
|
||||
else
|
||||
_ ->
|
||||
shell_error("No local user #{nickname}")
|
||||
|
@ -321,9 +313,7 @@ def run(["invites"]) do
|
|||
end
|
||||
|
||||
shell_info(
|
||||
"ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{
|
||||
invite.used
|
||||
}#{expire_info}#{using_info}"
|
||||
"ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{invite.used}#{expire_info}#{using_info}"
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
@ -424,9 +414,7 @@ def run(["list"]) do
|
|||
users
|
||||
|> Enum.each(fn user ->
|
||||
shell_info(
|
||||
"#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{
|
||||
user.is_locked
|
||||
}, is_active: #{user.is_active}"
|
||||
"#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{user.is_locked}, is_active: #{user.is_active}"
|
||||
)
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -302,7 +302,7 @@ def delete_all_by_object_ap_id(id) when is_binary(id) do
|
|||
|> Queries.by_object_id()
|
||||
|> Queries.exclude_type("Delete")
|
||||
|> select([u], u)
|
||||
|> Repo.delete_all()
|
||||
|> Repo.delete_all(timeout: :infinity)
|
||||
|> elem(1)
|
||||
|> Enum.find(fn
|
||||
%{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
|
||||
|
||||
def restrict_deactivated_users(query) do
|
||||
deactivated_users =
|
||||
from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
|
||||
|> Repo.all()
|
||||
deactivated_users_query = from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
|
||||
|
||||
Activity.Queries.exclude_authors(query, deactivated_users)
|
||||
from(activity in query, where: activity.actor not in subquery(deactivated_users_query))
|
||||
end
|
||||
|
||||
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
||||
|
|
|
@ -65,10 +65,17 @@ defp restrict_public(q) do
|
|||
end
|
||||
|
||||
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,
|
||||
where:
|
||||
fragment(
|
||||
"to_tsvector(?->>'content') @@ plainto_tsquery(?)",
|
||||
"to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)",
|
||||
^tsc,
|
||||
o.data,
|
||||
^search_query
|
||||
)
|
||||
|
@ -76,10 +83,17 @@ defp query_with(q, :gin, search_query, :plain) do
|
|||
end
|
||||
|
||||
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,
|
||||
where:
|
||||
fragment(
|
||||
"to_tsvector(?->>'content') @@ websearch_to_tsquery(?)",
|
||||
"to_tsvector(?::oid::regconfig, ?->>'content') @@ websearch_to_tsquery(?)",
|
||||
^tsc,
|
||||
o.data,
|
||||
^search_query
|
||||
)
|
||||
|
|
|
@ -61,6 +61,11 @@ def start(_type, _args) do
|
|||
|
||||
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 version = Pleroma.OTPVersion.version() do
|
||||
[major, minor] =
|
||||
|
|
|
@ -19,9 +19,7 @@ def on_shell(username, _pubkey, _ip, _port) do
|
|||
def on_connect(username, ip, port, method) do
|
||||
Logger.debug(fn ->
|
||||
"""
|
||||
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{
|
||||
inspect(port)
|
||||
} using #{inspect(method)}
|
||||
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{inspect(port)} using #{inspect(method)}
|
||||
"""
|
||||
end)
|
||||
end
|
||||
|
|
|
@ -21,9 +21,7 @@ def warn do
|
|||
"""
|
||||
!!!OBAN CONFIG WARNING!!!
|
||||
You are using old workers in Oban crontab settings, which were removed.
|
||||
Please, remove setting from crontab in your config file (prod.secret.exs): #{
|
||||
inspect(setting)
|
||||
}
|
||||
Please, remove setting from crontab in your config file (prod.secret.exs): #{inspect(setting)}
|
||||
"""
|
||||
|> Logger.warn()
|
||||
|
||||
|
|
|
@ -148,9 +148,7 @@ defp update({group, key, value, merged}) do
|
|||
rescue
|
||||
error ->
|
||||
error_msg =
|
||||
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
|
||||
inspect(value)
|
||||
} error: #{inspect(error)}"
|
||||
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{inspect(value)} error: #{inspect(error)}"
|
||||
|
||||
Logger.warn(error_msg)
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
mute: 2,
|
||||
reblog_mute: 3,
|
||||
notification_mute: 4,
|
||||
inverse_subscription: 5
|
||||
inverse_subscription: 5,
|
||||
suggestion_dismiss: 6
|
||||
)
|
||||
|
||||
defenum(Pleroma.FollowingRelationship.State,
|
||||
|
|
|
@ -60,9 +60,7 @@ def load do
|
|||
|
||||
if not Enum.empty?(files) do
|
||||
Logger.warn(
|
||||
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{
|
||||
Enum.join(files, ", ")
|
||||
}"
|
||||
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{Enum.join(files, ", ")}"
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -105,6 +103,7 @@ defp load_pack(pack_dir, emoji_groups) do
|
|||
pack_file = Path.join(pack_dir, "pack.json")
|
||||
|
||||
if File.exists?(pack_file) do
|
||||
Logger.info("Loading emoji pack from JSON: #{pack_file}")
|
||||
contents = Jason.decode!(File.read!(pack_file))
|
||||
|
||||
contents["files"]
|
||||
|
@ -117,14 +116,13 @@ defp load_pack(pack_dir, emoji_groups) do
|
|||
emoji_txt = Path.join(pack_dir, "emoji.txt")
|
||||
|
||||
if File.exists?(emoji_txt) do
|
||||
Logger.info("Loading emoji pack from emoji.txt: #{emoji_txt}")
|
||||
load_from_file(emoji_txt, emoji_groups)
|
||||
else
|
||||
extensions = Config.get([:emoji, :pack_extensions])
|
||||
|
||||
Logger.info(
|
||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{
|
||||
Enum.join(extensions, ", ")
|
||||
} files are emoji"
|
||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"
|
||||
)
|
||||
|
||||
make_shortcode_to_file_map(pack_dir, extensions)
|
||||
|
|
|
@ -57,9 +57,7 @@ defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
|
|||
else
|
||||
error ->
|
||||
Logger.warn(
|
||||
"Opening proxied connection to #{compose_uri_log(uri)} failed with error #{
|
||||
inspect(error)
|
||||
}"
|
||||
"Opening proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
|
||||
)
|
||||
|
||||
error
|
||||
|
@ -93,9 +91,7 @@ defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do
|
|||
else
|
||||
error ->
|
||||
Logger.warn(
|
||||
"Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{
|
||||
inspect(error)
|
||||
}"
|
||||
"Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
|
||||
)
|
||||
|
||||
error
|
||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Instances.Instance do
|
|||
alias Pleroma.Instances
|
||||
alias Pleroma.Instances.Instance
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Workers.BackgroundWorker
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
|
@ -195,4 +197,24 @@ defp scrape_favicon(%URI{} = instance_uri) do
|
|||
nil
|
||||
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
|
||||
|
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Maintenance do
|
|||
def vacuum(args) do
|
||||
case args do
|
||||
"analyze" ->
|
||||
Logger.info("Runnning VACUUM ANALYZE.")
|
||||
Logger.info("Running VACUUM ANALYZE.")
|
||||
|
||||
Repo.query!(
|
||||
"vacuum analyze;",
|
||||
|
@ -18,7 +18,7 @@ def vacuum(args) do
|
|||
)
|
||||
|
||||
"full" ->
|
||||
Logger.info("Runnning VACUUM FULL.")
|
||||
Logger.info("Running VACUUM FULL.")
|
||||
|
||||
Logger.warn(
|
||||
"Re-packing your entire database may take a while and will consume extra disk space during the process."
|
||||
|
|
|
@ -338,6 +338,26 @@ def get_log_entry_message(%ModerationLog{
|
|||
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
|
||||
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{
|
||||
data: %{
|
||||
"actor" => %{"nickname" => actor_nickname},
|
||||
|
@ -481,9 +501,7 @@ def get_log_entry_message(%ModerationLog{
|
|||
"visibility" => visibility
|
||||
}
|
||||
}) do
|
||||
"@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{
|
||||
visibility
|
||||
}'"
|
||||
"@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{visibility}'"
|
||||
end
|
||||
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
|
@ -523,9 +541,7 @@ def get_log_entry_message(%ModerationLog{
|
|||
"subject" => subjects
|
||||
}
|
||||
}) do
|
||||
"@#{actor_nickname} re-sent confirmation email for users: #{
|
||||
users_to_nicknames_string(subjects)
|
||||
}"
|
||||
"@#{actor_nickname} re-sent confirmation email for users: #{users_to_nicknames_string(subjects)}"
|
||||
end
|
||||
|
||||
def get_log_entry_message(%ModerationLog{
|
||||
|
|
|
@ -128,6 +128,7 @@ def for_user_query(user, opts \\ %{}) do
|
|||
|> where([user_actor: user_actor], user_actor.is_active)
|
||||
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|
||||
|> exclude_blocked(user, exclude_blocked_opts)
|
||||
|> exclude_blockers(user)
|
||||
|> exclude_filtered(user)
|
||||
|> exclude_visibility(opts)
|
||||
end
|
||||
|
@ -141,6 +142,17 @@ defp exclude_blocked(query, user, opts) do
|
|||
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|
||||
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
|
||||
query
|
||||
end
|
||||
|
|
|
@ -25,5 +25,6 @@ defp client do
|
|||
|
||||
defp client(Tesla.Adapter.Hackney), do: Pleroma.ReverseProxy.Client.Hackney
|
||||
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)
|
||||
end
|
||||
|
|
|
@ -12,10 +12,16 @@ defmodule Pleroma.Telemetry.Logger do
|
|||
[:pleroma, :connection_pool, :reclaim, :stop],
|
||||
[:pleroma, :connection_pool, :provision_failure],
|
||||
[:pleroma, :connection_pool, :client, :dead],
|
||||
[:pleroma, :connection_pool, :client, :add]
|
||||
[:pleroma, :connection_pool, :client, :add],
|
||||
[:pleroma, :repo, :query]
|
||||
]
|
||||
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
|
||||
|
||||
# Passing anonymous functions instead of strings to logger is intentional,
|
||||
|
@ -29,9 +35,7 @@ def handle_event(
|
|||
_
|
||||
) do
|
||||
Logger.debug(fn ->
|
||||
"Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{
|
||||
reclaim_max
|
||||
} connections"
|
||||
"Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{reclaim_max} connections"
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -73,9 +77,7 @@ def handle_event(
|
|||
_
|
||||
) do
|
||||
Logger.warn(fn ->
|
||||
"Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{
|
||||
inspect(reason)
|
||||
}"
|
||||
"Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{inspect(reason)}"
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -91,4 +93,64 @@ def handle_event(
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -124,7 +124,6 @@ defmodule Pleroma.User do
|
|||
field(:is_moderator, :boolean, default: false)
|
||||
field(:is_admin, :boolean, default: false)
|
||||
field(:show_role, :boolean, default: true)
|
||||
field(:mastofe_settings, :map, default: nil)
|
||||
field(:uri, ObjectValidators.Uri, default: nil)
|
||||
field(:hide_followers_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(:disclose_client, :boolean, default: true)
|
||||
field(:pinned_objects, :map, default: %{})
|
||||
field(:is_suggested, :boolean, default: false)
|
||||
field(:last_status_at, :naive_datetime)
|
||||
|
||||
embeds_one(
|
||||
:notification_settings,
|
||||
|
@ -1677,6 +1678,22 @@ def confirm(%User{is_confirmed: false} = user) do
|
|||
|
||||
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
|
||||
user
|
||||
|> cast(%{notification_settings: settings}, [])
|
||||
|
@ -1713,7 +1730,6 @@ def purge_user_changeset(user) do
|
|||
ap_enabled: false,
|
||||
is_moderator: false,
|
||||
is_admin: false,
|
||||
mastofe_settings: nil,
|
||||
mascot: nil,
|
||||
emoji: %{},
|
||||
pleroma_settings_store: %{},
|
||||
|
@ -2248,7 +2264,7 @@ def get_delivered_users_by_object_id(object_id) do
|
|||
def change_email(user, email) do
|
||||
user
|
||||
|> cast(%{email: email}, [:email])
|
||||
|> validate_required([:email])
|
||||
|> maybe_validate_required_email(false)
|
||||
|> unique_constraint(:email)
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> update_and_set_cache()
|
||||
|
@ -2331,13 +2347,6 @@ def mascot_update(user, url) do
|
|||
|> update_and_set_cache()
|
||||
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()
|
||||
def confirmation_changeset(user, set_confirmation: confirmed?) do
|
||||
params =
|
||||
|
@ -2483,12 +2492,24 @@ def update_last_active_at(%__MODULE__{local: true} = user) do
|
|||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def active_user_count(weeks \\ 4) do
|
||||
active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks)
|
||||
def active_user_count(days \\ 30) do
|
||||
active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
|
||||
|
||||
__MODULE__
|
||||
|> where([u], u.last_active_at >= ^active_after)
|
||||
|> where([u], u.local == true)
|
||||
|> Repo.aggregate(:count)
|
||||
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
|
||||
|
|
|
@ -46,6 +46,8 @@ defmodule Pleroma.User.Query do
|
|||
unconfirmed: boolean(),
|
||||
is_admin: boolean(),
|
||||
is_moderator: boolean(),
|
||||
is_suggested: boolean(),
|
||||
is_discoverable: boolean(),
|
||||
super_users: boolean(),
|
||||
invisible: boolean(),
|
||||
internal: boolean(),
|
||||
|
@ -167,6 +169,14 @@ defp compose_query({:unconfirmed, _}, query) do
|
|||
where(query, [u], u.is_confirmed == false)
|
||||
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
|
||||
query
|
||||
|> where([u], u.id != ^id)
|
||||
|
|
|
@ -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
|
|
@ -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}
|
||||
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(%{
|
||||
"object" => %{"inReplyTo" => reply_ap_id} = object,
|
||||
"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),
|
||||
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
|
||||
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
||||
{:ok, _actor} <- update_last_status_at_if_public(actor, activity),
|
||||
_ <- notify_and_stream(activity),
|
||||
:ok <- maybe_schedule_poll_notifications(activity),
|
||||
:ok <- maybe_federate(activity) do
|
||||
|
@ -441,6 +446,7 @@ def fetch_activities_for_context_query(context, opts) do
|
|||
|> maybe_preload_bookmarks(opts)
|
||||
|> maybe_set_thread_muted_field(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_blockers_visibility(opts)
|
||||
|> restrict_recipients(recipients, opts[:user])
|
||||
|> restrict_filtered(opts)
|
||||
|> where(
|
||||
|
@ -1028,7 +1034,10 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
|||
|
||||
from(
|
||||
[activity, object: o] in query,
|
||||
# You don't block the author
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
|
||||
|
||||
# You don't block any recipients, and didn't author the post
|
||||
where:
|
||||
fragment(
|
||||
"((not (? && ?)) or ? = ?)",
|
||||
|
@ -1037,12 +1046,18 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
|||
activity.actor,
|
||||
^user.ap_id
|
||||
),
|
||||
|
||||
# You don't block the domain of any recipients, and didn't author the post
|
||||
where:
|
||||
fragment(
|
||||
"recipients_contain_blocked_domains(?, ?) = false",
|
||||
"(recipients_contain_blocked_domains(?, ?) = false) or ? = ?",
|
||||
activity.recipients,
|
||||
^domain_blocks
|
||||
^domain_blocks,
|
||||
activity.actor,
|
||||
^user.ap_id
|
||||
),
|
||||
|
||||
# It's not a boost of a user you block
|
||||
where:
|
||||
fragment(
|
||||
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||
|
@ -1050,6 +1065,8 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
|||
activity.data,
|
||||
^blocked_ap_ids
|
||||
),
|
||||
|
||||
# You don't block the author's domain, and also don't follow the author
|
||||
where:
|
||||
fragment(
|
||||
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
|
||||
|
@ -1058,6 +1075,8 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do
|
|||
activity.actor,
|
||||
^following_ap_ids
|
||||
),
|
||||
|
||||
# Same as above, but checks the Object
|
||||
where:
|
||||
fragment(
|
||||
"(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_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
|
||||
from(
|
||||
activity in query,
|
||||
|
@ -1297,6 +1341,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> restrict_state(opts)
|
||||
|> restrict_favorited_by(opts)
|
||||
|> restrict_blocked(restrict_blocked_opts)
|
||||
|> restrict_blockers_visibility(opts)
|
||||
|> restrict_muted(restrict_muted_opts)
|
||||
|> restrict_filtered(opts)
|
||||
|> restrict_media(opts)
|
||||
|
@ -1597,9 +1642,7 @@ def maybe_handle_clashing_nickname(data) do
|
|||
%User{} = old_user <- User.get_by_nickname(nickname),
|
||||
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
|
||||
Logger.info(
|
||||
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
|
||||
data[:ap_id]
|
||||
}, renaming."
|
||||
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{data[:ap_id]}, renaming."
|
||||
)
|
||||
|
||||
old_user
|
||||
|
|
|
@ -283,15 +283,29 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
|
|||
json(conn, "ok")
|
||||
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
|
||||
def inbox(conn, params) do
|
||||
if params["type"] == "Create" && FederatingPlug.federating?() do
|
||||
def inbox(conn, %{"type" => "Create"} = params) do
|
||||
if FederatingPlug.federating?() do
|
||||
post_inbox_relayed_create(conn, params)
|
||||
else
|
||||
post_inbox_fallback(conn, params)
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json("Not federating")
|
||||
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
|
||||
Logger.debug(
|
||||
"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")
|
||||
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
|
||||
with {:ok, user} <- User.ensure_keys_present(user) do
|
||||
conn
|
||||
|
|
|
@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
|||
type: [:module, {:list, :module}],
|
||||
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.",
|
||||
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
|
||||
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF.Policy}
|
||||
},
|
||||
%{
|
||||
key: :transparency,
|
||||
|
@ -157,9 +157,7 @@ def config_descriptions(policies) do
|
|||
[description | acc]
|
||||
else
|
||||
Logger.warn(
|
||||
"#{policy} config description doesn't have one or all required keys #{
|
||||
inspect(@required_description_keys)
|
||||
}"
|
||||
"#{policy} config description doesn't have one or all required keys #{inspect(@required_description_keys)}"
|
||||
)
|
||||
|
||||
acc
|
||||
|
|
|
@ -38,9 +38,7 @@ defp steal_emoji({shortcode, url}, emoji_dir_path) do
|
|||
end
|
||||
else
|
||||
Logger.debug(
|
||||
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{
|
||||
size_limit
|
||||
} B)"
|
||||
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)"
|
||||
)
|
||||
|
||||
nil
|
||||
|
|
|
@ -23,9 +23,7 @@ defp lookup_subchain(actor) do
|
|||
def filter(%{"actor" => actor} = message) do
|
||||
with {:ok, match, subchain} <- lookup_subchain(actor) do
|
||||
Logger.debug(
|
||||
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{
|
||||
inspect(subchain)
|
||||
}"
|
||||
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}"
|
||||
)
|
||||
|
||||
MRF.filter(subchain, message)
|
||||
|
|
|
@ -213,6 +213,7 @@ def stringify_keys(%{__struct__: _} = object) do
|
|||
|
||||
def stringify_keys(object) when is_map(object) do
|
||||
object
|
||||
|> Enum.filter(fn {_, v} -> v != nil end)
|
||||
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
|
|||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
@ -14,12 +13,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
|
|
|
@ -10,19 +10,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
|
|||
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.User
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:target)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:type)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
|
|
|
@ -20,13 +20,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
|
||||
field(:context, :string)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:published, ObjectValidators.DateTime)
|
||||
end
|
||||
|
||||
|
|
|
@ -15,12 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
|
|||
@derive Jason.Encoder
|
||||
|
||||
embedded_schema do
|
||||
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: [])
|
||||
field(:type, :string)
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
end
|
||||
end
|
||||
|
||||
field(:name, :string)
|
||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
||||
field(:attributedTo, ObjectValidators.ObjectID)
|
||||
|
|
|
@ -6,10 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
|||
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.CommonValidations
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Ecto.Changeset
|
||||
|
@ -18,38 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
|||
@derive Jason.Encoder
|
||||
|
||||
embedded_schema do
|
||||
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: [])
|
||||
embeds_many(:tag, TagValidator)
|
||||
field(:type, :string)
|
||||
|
||||
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: [])
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
object_fields()
|
||||
status_object_fields()
|
||||
end
|
||||
end
|
||||
|
||||
field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
end
|
||||
|
|
|
@ -68,12 +68,14 @@ def fix_media_type(data) do
|
|||
end
|
||||
end
|
||||
|
||||
defp handle_href(href, mediaType) do
|
||||
defp handle_href(href, mediaType, data) do
|
||||
[
|
||||
%{
|
||||
"href" => href,
|
||||
"type" => "Link",
|
||||
"mediaType" => mediaType
|
||||
"mediaType" => mediaType,
|
||||
"width" => data["width"],
|
||||
"height" => data["height"]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
@ -81,10 +83,10 @@ defp handle_href(href, mediaType) do
|
|||
defp fix_url(data) do
|
||||
cond do
|
||||
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 ->
|
||||
Map.put(data, "url", handle_href(data["href"], data["mediaType"]))
|
||||
Map.put(data, "url", handle_href(data["href"], data["mediaType"], data))
|
||||
|
||||
true ->
|
||||
data
|
||||
|
|
|
@ -5,11 +5,8 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
||||
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.CommonValidations
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Ecto.Changeset
|
||||
|
@ -18,38 +15,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
|
|||
@derive Jason.Encoder
|
||||
|
||||
embedded_schema do
|
||||
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: [])
|
||||
embeds_many(:tag, TagValidator)
|
||||
field(:type, :string)
|
||||
|
||||
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: [])
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
object_fields()
|
||||
status_object_fields()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cast_and_apply(data) do
|
||||
|
|
|
@ -5,20 +5,21 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
@derive Jason.Encoder
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
|
@ -30,8 +31,8 @@ defp validate_data(cng) do
|
|||
cng
|
||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||
|> validate_inclusion(:type, ["Block"])
|
||||
|> validate_actor_presence()
|
||||
|> validate_actor_presence(field_name: :object)
|
||||
|> CommonValidations.validate_actor_presence()
|
||||
|> CommonValidations.validate_actor_presence(field_name: :object)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
|
|
|
@ -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
|
|
@ -17,11 +17,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
|
|||
@primary_key false
|
||||
|
||||
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(:actor, ObjectValidators.ObjectID)
|
||||
field(:type, :string)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
end
|
||||
|
||||
def cast_and_apply(data) do
|
||||
|
|
|
@ -20,14 +20,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:type, :string)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:bto, ObjectValidators.Recipients, default: [])
|
||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
|
||||
field(:expires_at, ObjectValidators.DateTime)
|
||||
|
||||
# Should be moved to object, done for CommonAPI.Utils.make_context
|
||||
|
|
|
@ -15,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
|
||||
field(:deleted_activity_id, ObjectValidators.ObjectID)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||
|
||||
|
@ -15,14 +14,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
|
||||
field(:context, :string)
|
||||
field(:content, :string)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
|
|
|
@ -5,11 +5,8 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
|
||||
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.CommonValidations
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Ecto.Changeset
|
||||
|
@ -19,38 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
|
|||
|
||||
# Extends from NoteValidator
|
||||
embedded_schema do
|
||||
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: [])
|
||||
embeds_many(:tag, TagValidator)
|
||||
field(:type, :string)
|
||||
|
||||
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: [])
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
object_fields()
|
||||
status_object_fields()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cast_and_apply(data) do
|
||||
|
|
|
@ -5,20 +5,20 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
|
||||
field(:state, :string, default: "pending")
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
@ -16,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
|
||||
field(:context, :string)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
|
|
|
@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
|||
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.CommonValidations
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Ecto.Changeset
|
||||
|
@ -20,35 +18,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
|||
|
||||
# Extends from NoteValidator
|
||||
embedded_schema do
|
||||
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: [])
|
||||
embeds_many(:tag, TagValidator)
|
||||
field(:type, :string)
|
||||
field(:content, :string)
|
||||
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: [])
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
object_fields()
|
||||
status_object_fields()
|
||||
end
|
||||
end
|
||||
|
||||
field(:closed, ObjectValidators.DateTime)
|
||||
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
|
|||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.User
|
||||
|
||||
import Ecto.Changeset
|
||||
|
@ -15,12 +14,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, ObjectValidators.ObjectID)
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
activity_fields()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
|
|
|
@ -13,11 +13,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
|
|||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
quote do
|
||||
unquote do
|
||||
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
|
||||
message_fields()
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
# reference, so we can always see what was actually changed by this.
|
||||
field(:object, :map)
|
||||
|
|
|
@ -63,18 +63,17 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
|
|||
date: date
|
||||
})
|
||||
|
||||
with {:ok, %{status: code}} when code in 200..299 <-
|
||||
result =
|
||||
HTTP.post(
|
||||
inbox,
|
||||
json,
|
||||
[
|
||||
{"Content-Type", "application/activity+json"},
|
||||
{"Date", date},
|
||||
{"signature", signature},
|
||||
{"digest", digest}
|
||||
]
|
||||
) do
|
||||
with {:ok, %{status: code}} = result when code in 200..299 <-
|
||||
HTTP.post(
|
||||
inbox,
|
||||
json,
|
||||
[
|
||||
{"Content-Type", "application/activity+json"},
|
||||
{"Date", date},
|
||||
{"signature", signature},
|
||||
{"digest", digest}
|
||||
]
|
||||
) do
|
||||
if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
|
||||
Instances.set_reachable(inbox)
|
||||
end
|
||||
|
|
|
@ -199,8 +199,9 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
|||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
|
||||
{: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)
|
||||
end
|
||||
|
||||
|
|
|
@ -446,7 +446,7 @@ def update_follow_state_for_all(
|
|||
|> Activity.Queries.by_type()
|
||||
|> Activity.Queries.by_actor(actor)
|
||||
|> 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)])
|
||||
|> Repo.update_all([])
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["admin:read:statuses"]}
|
||||
when action in [:list_user_statuses, :list_instance_statuses]
|
||||
when action in [:list_user_statuses]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
@ -81,24 +81,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
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
|
||||
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
|
||||
godmode = params["godmode"] == "true" || params["godmode"] == true
|
||||
|
|
|
@ -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
|
|
@ -35,7 +35,9 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
|||
:toggle_activation,
|
||||
:activate,
|
||||
: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)
|
||||
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
|
||||
{page, page_size} = page_params(params)
|
||||
filters = maybe_parse_filters(params[:filters])
|
||||
|
|
|
@ -80,6 +80,7 @@ def render("show.json", %{user: user}) do
|
|||
"tags" => user.tags || [],
|
||||
"is_confirmed" => user.is_confirmed,
|
||||
"is_approved" => user.is_approved,
|
||||
"is_suggested" => user.is_suggested,
|
||||
"url" => user.uri || user.ap_id,
|
||||
"registration_reason" => user.registration_reason,
|
||||
"actor_type" => user.actor_type,
|
||||
|
|
|
@ -226,6 +226,12 @@ def follow_operation do
|
|||
type: :boolean,
|
||||
description: "Receive this account's reblogs in home timeline? Defaults to 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
|
||||
|
||||
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
|
||||
%Operation{
|
||||
tags: ["Account actions"],
|
||||
|
@ -685,9 +714,11 @@ defp array_of_relationships do
|
|||
"blocked_by" => true,
|
||||
"muting" => false,
|
||||
"muting_notifications" => false,
|
||||
"note" => "",
|
||||
"requested" => false,
|
||||
"domain_blocking" => false,
|
||||
"subscribing" => false,
|
||||
"notifying" => false,
|
||||
"endorsed" => true
|
||||
},
|
||||
%{
|
||||
|
@ -699,9 +730,11 @@ defp array_of_relationships do
|
|||
"blocked_by" => true,
|
||||
"muting" => true,
|
||||
"muting_notifications" => false,
|
||||
"note" => "",
|
||||
"requested" => true,
|
||||
"domain_blocking" => false,
|
||||
"subscribing" => false,
|
||||
"notifying" => false,
|
||||
"endorsed" => false
|
||||
},
|
||||
%{
|
||||
|
@ -713,9 +746,11 @@ defp array_of_relationships do
|
|||
"blocked_by" => false,
|
||||
"muting" => true,
|
||||
"muting_notifications" => false,
|
||||
"note" => "",
|
||||
"requested" => false,
|
||||
"domain_blocking" => true,
|
||||
"subscribing" => true,
|
||||
"notifying" => true,
|
||||
"endorsed" => false
|
||||
}
|
||||
]
|
||||
|
@ -760,6 +795,23 @@ defp mute_request do
|
|||
}
|
||||
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
|
||||
%Schema{
|
||||
title: "ArrayOfLists",
|
||||
|
|
|
@ -216,7 +216,71 @@ def approve_operation do
|
|||
request_body(
|
||||
"Parameters",
|
||||
%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,
|
||||
properties: %{
|
||||
nicknames: %Schema{
|
||||
|
|
|
@ -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
|
|
@ -121,7 +121,10 @@ defp change_email_request do
|
|||
type: :object,
|
||||
required: [:email, :password],
|
||||
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"}
|
||||
}
|
||||
}
|
||||
|
@ -188,6 +191,7 @@ def delete_account_operation do
|
|||
parameters: [
|
||||
Operation.parameter(:password, :query, :string, "Password")
|
||||
],
|
||||
requestBody: request_body("Parameters", delete_account_request(), required: false),
|
||||
responses: %{
|
||||
200 =>
|
||||
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})}
|
||||
}
|
||||
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
|
||||
|
|
|
@ -194,9 +194,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
"id" => "9tKi3esbG7OQgZ2920",
|
||||
"muting" => false,
|
||||
"muting_notifications" => false,
|
||||
"note" => "",
|
||||
"requested" => false,
|
||||
"showing_reblogs" => true,
|
||||
"subscribing" => false
|
||||
"subscribing" => false,
|
||||
"notifying" => false
|
||||
},
|
||||
"settings_store" => %{
|
||||
"pleroma-fe" => %{}
|
||||
|
|
|
@ -22,9 +22,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
|
|||
id: FlakeID,
|
||||
muting: %Schema{type: :boolean},
|
||||
muting_notifications: %Schema{type: :boolean},
|
||||
note: %Schema{type: :string},
|
||||
requested: %Schema{type: :boolean},
|
||||
showing_reblogs: %Schema{type: :boolean},
|
||||
subscribing: %Schema{type: :boolean}
|
||||
subscribing: %Schema{type: :boolean},
|
||||
notifying: %Schema{type: :boolean}
|
||||
},
|
||||
example: %{
|
||||
"blocked_by" => false,
|
||||
|
@ -36,9 +38,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
|
|||
"id" => "9tKi3esbG7OQgZ2920",
|
||||
"muting" => false,
|
||||
"muting_notifications" => false,
|
||||
"note" => "",
|
||||
"requested" => false,
|
||||
"showing_reblogs" => true,
|
||||
"subscribing" => false
|
||||
"subscribing" => false,
|
||||
"notifying" => false
|
||||
}
|
||||
})
|
||||
end
|
||||
|
|
|
@ -282,9 +282,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
"id" => "9toJCsKN7SmSf3aj5c",
|
||||
"muting" => false,
|
||||
"muting_notifications" => false,
|
||||
"note" => "",
|
||||
"requested" => false,
|
||||
"showing_reblogs" => true,
|
||||
"subscribing" => false
|
||||
"subscribing" => false,
|
||||
"notifying" => false
|
||||
},
|
||||
"skip_thread_containment" => false,
|
||||
"tags" => []
|
||||
|
|
|
@ -487,9 +487,7 @@ def remove_mute(user_id, activity_id) do
|
|||
else
|
||||
{what, result} = error ->
|
||||
Logger.warn(
|
||||
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{
|
||||
activity_id
|
||||
}"
|
||||
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}"
|
||||
)
|
||||
|
||||
{:error, error}
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.Endpoint do
|
|||
alias Pleroma.Config
|
||||
|
||||
socket("/socket", Pleroma.Web.UserSocket)
|
||||
socket("/live", Phoenix.LiveView.Socket)
|
||||
|
||||
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ defmodule Pleroma.Web.Feed.UserController 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
|
||||
Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
|
||||
else
|
||||
_ -> Pleroma.Web.Fallback.RedirectController.redirector(conn, nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserNote
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
|
@ -53,7 +54,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
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)
|
||||
|
||||
|
@ -79,7 +84,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
|
||||
|
||||
@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(
|
||||
RateLimiter,
|
||||
|
@ -435,6 +440,16 @@ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
|||
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"
|
||||
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
|
||||
case User.get_cached_by_nickname(uri) do
|
||||
|
|
|
@ -25,8 +25,6 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
|||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
@local_mastodon_name "Mastodon-Local"
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
|
||||
|
||||
@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)
|
||||
|
||||
with cs <- App.register_changeset(%App{}, app_attrs),
|
||||
false <- cs.changes[:client_name] == @local_mastodon_name,
|
||||
{:ok, app} <- Repo.insert(cs) do
|
||||
render(conn, "show.json", app: app)
|
||||
end
|
||||
|
|
|
@ -7,77 +7,12 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
|
|||
|
||||
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
|
||||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
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"
|
||||
def password_reset(conn, params) do
|
||||
nickname_or_email = params["email"] || params["nickname"]
|
||||
|
@ -86,23 +21,4 @@ def password_reset(conn, params) do
|
|||
|
||||
json_response(conn, :no_content, "")
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -17,6 +17,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
|
||||
require Logger
|
||||
|
||||
@search_limit 40
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
# 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],
|
||||
following: params[:following],
|
||||
limit: params[:limit],
|
||||
limit: min(params[:limit], @search_limit),
|
||||
offset: params[:offset],
|
||||
type: params[:type],
|
||||
author: get_author(params),
|
||||
|
|
|
@ -4,11 +4,16 @@
|
|||
|
||||
defmodule Pleroma.Web.MastodonAPI.SuggestionController do
|
||||
use Pleroma.Web, :controller
|
||||
import Ecto.Query
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserRelationship
|
||||
|
||||
require Logger
|
||||
|
||||
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
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
|
@ -26,7 +31,90 @@ def index_operation do
|
|||
}
|
||||
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"
|
||||
def index(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
|
||||
|
|
|
@ -24,6 +24,7 @@ def follow(follower, followed, params \\ %{}) do
|
|||
with {:ok, follower, _followed, _} <- result do
|
||||
options = cast_params(params)
|
||||
set_reblogs_visibility(options[:reblogs], result)
|
||||
set_subscription(options[:notify], result)
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
@ -36,6 +37,16 @@ defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do
|
|||
CommonAPI.show_reblogs(follower, followed)
|
||||
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())
|
||||
def get_followers(user, params \\ %{}) do
|
||||
user
|
||||
|
@ -73,7 +84,8 @@ defp cast_params(params) do
|
|||
exclude_visibilities: {:array, :string},
|
||||
reblogs: :boolean,
|
||||
with_muted: :boolean,
|
||||
account_ap_id: :string
|
||||
account_ap_id: :string,
|
||||
notify: :boolean
|
||||
}
|
||||
|
||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserNote
|
||||
alias Pleroma.UserRelationship
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
|
@ -101,6 +102,15 @@ def render(
|
|||
User.following?(target, reading_user)
|
||||
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
|
||||
%{
|
||||
id: to_string(target.id),
|
||||
|
@ -138,14 +148,8 @@ def render(
|
|||
target,
|
||||
&User.muted_notifications?(&1, &2)
|
||||
),
|
||||
subscribing:
|
||||
UserRelationship.exists?(
|
||||
user_relationships,
|
||||
:inverse_subscription,
|
||||
target,
|
||||
reading_user,
|
||||
&User.subscribed_to?(&2, &1)
|
||||
),
|
||||
subscribing: subscribing,
|
||||
notifying: subscribing,
|
||||
requested: follow_state == :follow_pending,
|
||||
domain_blocking: User.blocks_domain?(reading_user, target),
|
||||
showing_reblogs:
|
||||
|
@ -156,7 +160,12 @@ def render(
|
|||
target,
|
||||
&User.muting_reblogs?(&1, &2)
|
||||
),
|
||||
endorsed: false
|
||||
endorsed: false,
|
||||
note:
|
||||
UserNote.show(
|
||||
reading_user,
|
||||
target
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -261,6 +270,7 @@ defp do_render("show.json", %{user: user} = opts) do
|
|||
actor_type: user.actor_type
|
||||
}
|
||||
},
|
||||
last_status_at: user.last_status_at,
|
||||
|
||||
# Pleroma extensions
|
||||
# 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,
|
||||
also_known_as: user.also_known_as,
|
||||
is_confirmed: user.is_confirmed,
|
||||
is_suggested: user.is_suggested,
|
||||
tags: user.tags,
|
||||
hide_followers_count: user.hide_followers_count,
|
||||
hide_follows_count: user.hide_follows_count,
|
||||
|
|
|
@ -45,7 +45,8 @@ def render("show.json", _) do
|
|||
features: features(),
|
||||
federation: federation(),
|
||||
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()},
|
||||
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||
|
@ -59,6 +60,7 @@ def features do
|
|||
"mastodon_api",
|
||||
"mastodon_api_streaming",
|
||||
"polls",
|
||||
"v2_suggestions",
|
||||
"pleroma_explicit_addressing",
|
||||
"shareable_emoji_packs",
|
||||
"multifetch",
|
||||
|
@ -83,7 +85,13 @@ def features do
|
|||
"safe_dm_mentions"
|
||||
end,
|
||||
"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)
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -49,9 +49,7 @@ def init(%{qs: qs} = req, state) do
|
|||
|
||||
def websocket_init(state) do
|
||||
Logger.debug(
|
||||
"#{__MODULE__} accepted websocket connection for user #{
|
||||
(state.user || %{id: "anonymous"}).id
|
||||
}, topic #{state.topic}"
|
||||
"#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}"
|
||||
)
|
||||
|
||||
Streamer.add_socket(state.topic, state.user)
|
||||
|
@ -106,9 +104,7 @@ def terminate(_reason, _req, []), do: :ok
|
|||
|
||||
def terminate(reason, _req, state) do
|
||||
Logger.debug(
|
||||
"#{__MODULE__} terminating websocket connection for user #{
|
||||
(state.user || %{id: "anonymous"}).id
|
||||
}, topic #{state.topic || "?"}: #{inspect(reason)}"
|
||||
"#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic || "?"}: #{inspect(reason)}"
|
||||
)
|
||||
|
||||
Streamer.remove_socket(state.topic)
|
||||
|
|
|
@ -35,7 +35,9 @@ def get_nodeinfo("2.0") do
|
|||
openRegistrations: Config.get([:instance, :registrations_open]),
|
||||
usage: %{
|
||||
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)
|
||||
},
|
||||
|
@ -67,7 +69,8 @@ def get_nodeinfo("2.0") do
|
|||
mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
|
||||
features: features,
|
||||
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
|
||||
|
|
|
@ -597,9 +597,6 @@ def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested
|
|||
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 get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
|
||||
|
|
|
@ -151,7 +151,9 @@ def index2(%{assigns: %{user: user}} = conn, params) do
|
|||
index_query(user, params)
|
||||
|> Pagination.fetch_paginated(params)
|
||||
|
||||
render(conn, "index.json", chats: chats)
|
||||
conn
|
||||
|> add_link_headers(chats)
|
||||
|> render("index.json", chats: chats)
|
||||
end
|
||||
|
||||
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
Loading…
Reference in New Issue