Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
FloatingGhost 2021-12-30 18:05:22 +00:00
commit 12a171844b
88 changed files with 2257 additions and 138 deletions

View file

@ -1,4 +1,4 @@
image: elixir:1.9.4 image: git.pleroma.social:5050/pleroma/pleroma/ci-base
variables: &global_variables variables: &global_variables
POSTGRES_DB: pleroma_test POSTGRES_DB: pleroma_test
@ -26,12 +26,7 @@ stages:
before_script: before_script:
- echo $MIX_ENV - echo $MIX_ENV
- rm -rf _build/*/lib/pleroma - rm -rf _build/*/lib/pleroma
- apt-get update && apt-get install -y cmake
- mix local.hex --force
- mix local.rebar --force
- mix deps.get - mix deps.get
- apt-get -qq update
- apt-get install -y libmagic-dev
after_script: after_script:
- rm -rf _build/*/lib/pleroma - rm -rf _build/*/lib/pleroma
@ -79,7 +74,6 @@ unit-testing:
- "**/*.ex" - "**/*.ex"
- "**/*.exs" - "**/*.exs"
- "mix.lock" - "mix.lock"
retry: 2
cache: &testing_cache_policy cache: &testing_cache_policy
<<: *global_cache_policy <<: *global_cache_policy
policy: pull policy: pull
@ -89,11 +83,31 @@ unit-testing:
alias: postgres alias: postgres
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
script: script:
- apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
- mix ecto.create - mix ecto.create
- mix ecto.migrate - mix ecto.migrate
- mix coveralls --preload-modules - mix coveralls --preload-modules
unit-testing-erratic:
stage: test
retry: 2
only:
changes:
- "**/*.ex"
- "**/*.exs"
- "mix.lock"
cache: &testing_cache_policy
<<: *global_cache_policy
policy: pull
services:
- name: postgres:13
alias: postgres
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
script:
- mix ecto.create
- mix ecto.migrate
- mix test --only=erratic
# Removed to fix CI issue. In this early state it wasn't adding much value anyway. # Removed to fix CI issue. In this early state it wasn't adding much value anyway.
# TODO Fix and reinstate federated testing # TODO Fix and reinstate federated testing
# federated-testing: # federated-testing:
@ -117,7 +131,6 @@ unit-testing-rum:
- "**/*.ex" - "**/*.ex"
- "**/*.exs" - "**/*.exs"
- "mix.lock" - "mix.lock"
retry: 2
cache: *testing_cache_policy cache: *testing_cache_policy
services: services:
- name: minibikini/postgres-with-rum:12 - name: minibikini/postgres-with-rum:12
@ -127,7 +140,6 @@ unit-testing-rum:
<<: *global_variables <<: *global_variables
RUM_ENABLED: "true" RUM_ENABLED: "true"
script: script:
- apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
- mix ecto.create - mix ecto.create
- mix ecto.migrate - mix ecto.migrate
- "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" - "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
@ -142,6 +154,10 @@ lint:
- "**/*.exs" - "**/*.exs"
- "mix.lock" - "mix.lock"
cache: *testing_cache_policy cache: *testing_cache_policy
before_script:
- mix local.hex --force
- mix local.rebar --force
- mix deps.get
script: script:
- mix format --check-formatted - mix format --check-formatted
@ -165,8 +181,13 @@ cycles:
- "**/*.exs" - "**/*.exs"
- "mix.lock" - "mix.lock"
cache: {} cache: {}
script: before_script:
- mix local.hex --force
- mix local.rebar --force
- mix deps.get - mix deps.get
- apt-get update
- apt-get install cmake libmagic-dev -y
script:
- mix compile - mix compile
- mix xref graph --format cycles --label compile | awk '{print $0} END{exit ($0 != "No cycles found")}' - mix xref graph --format cycles --label compile | awk '{print $0} END{exit ($0 != "No cycles found")}'

View file

@ -15,9 +15,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added ### Added
- `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object - `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
- PleromaAPI: Add remote follow API endpoint at `POST /api/v1/pleroma/remote_interaction`
- MastoAPI: Add `GET /api/v1/accounts/lookup`
- MastoAPI: Profile Directory support
- MastoAPI: Support v2 Suggestions (handpicked accounts only)
- Ability to log slow Ecto queries by configuring `:pleroma, :telemetry, :slow_queries_logging`
- Added Phoenix LiveDashboard at `/phoenix/live_dashboard`
- Added `/manifest.json` for progressive web apps.
### Fixed ### Fixed
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies - Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
- Handle Reject for already-accepted Follows properly
- Display OpenGraph data on alternative notice routes.
- Fix replies count for remote replies
- ChatAPI: Add link headers
- Limited number of search results to 40 to prevent DoS attacks
- ActivityPub: fixed federation of attachment dimensions
- Fixed benchmarks
- Elixir 1.13 support
- Fixed crash when pinned_objects is nil
- Fixed slow timelines when there are a lot of deactivated users
- Fixed account deletion API
### Removed ### Removed

View file

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

7
ci/Dockerfile Normal file
View file

@ -0,0 +1,7 @@
FROM elixir:1.9.4
RUN apt-get update &&\
apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\
mix local.hex --force &&\
mix local.rebar --force

1
ci/build_and_push.sh Executable file
View file

@ -0,0 +1 @@
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:latest --push .

View file

@ -139,6 +139,7 @@
], ],
protocol: "https", protocol: "https",
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl", secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
live_view: [signing_salt: "U5ELgdEwTD3n1+D5s0rY0AMg8/y1STxZ3Zvsl3bWh+oBcGrYdil0rXqPMRd3Glcq"],
signing_salt: "CqaoopA2", signing_salt: "CqaoopA2",
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)], render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
pubsub_server: Pleroma.PubSub, pubsub_server: Pleroma.PubSub,
@ -148,6 +149,8 @@
] ]
# Configures Elixir's Logger # Configures Elixir's Logger
config :logger, truncate: 65536
config :logger, :console, config :logger, :console,
level: :debug, level: :debug,
format: "\n$time $metadata[$level] $message\n", format: "\n$time $metadata[$level] $message\n",
@ -254,7 +257,9 @@
] ]
], ],
show_reactions: true, show_reactions: true,
password_reset_token_validity: 60 * 60 * 24 password_reset_token_validity: 60 * 60 * 24,
profile_directory: true,
privileged_staff: false
config :pleroma, :welcome, config :pleroma, :welcome,
direct_message: [ direct_message: [
@ -854,6 +859,13 @@
config :pleroma, :search, provider: Pleroma.Search.Builtin config :pleroma, :search, provider: Pleroma.Search.Builtin
config :pleroma, :telemetry,
slow_queries_logging: [
enabled: false,
min_duration: 500_000,
exclude_sources: [nil, "oban_jobs"]
]
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View file

@ -936,6 +936,17 @@
key: :show_reactions, key: :show_reactions,
type: :boolean, type: :boolean,
description: "Let favourites and emoji reactions be viewed through the API." description: "Let favourites and emoji reactions be viewed through the API."
},
%{
key: :profile_directory,
type: :boolean,
description: "Enable profile directory."
},
%{
key: :privileged_staff,
type: :boolean,
description:
"Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
} }
] ]
}, },

View file

@ -261,6 +261,46 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
} }
``` ```
## `PATCH /api/v1/pleroma/admin/users/suggest`
### Suggest a user
Adds the user(s) to follower recommendations.
- Params:
- `nicknames`: nicknames array
- Response:
```json
{
users: [
{
// user object
}
]
}
```
## `PATCH /api/v1/pleroma/admin/users/unsuggest`
### Unsuggest a user
Removes the user(s) from follower recommendations.
- Params:
- `nicknames`: nicknames array
- Response:
```json
{
users: [
{
// user object
}
]
}
```
## `GET /api/v1/pleroma/admin/users/:nickname_or_id` ## `GET /api/v1/pleroma/admin/users/:nickname_or_id`
### Retrive the details of a user ### Retrive the details of a user

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -103,6 +103,7 @@ defp load_pack(pack_dir, emoji_groups) do
pack_file = Path.join(pack_dir, "pack.json") pack_file = Path.join(pack_dir, "pack.json")
if File.exists?(pack_file) do if File.exists?(pack_file) do
Logger.info("Loading emoji pack from JSON: #{pack_file}")
contents = Jason.decode!(File.read!(pack_file)) contents = Jason.decode!(File.read!(pack_file))
contents["files"] contents["files"]
@ -115,6 +116,7 @@ defp load_pack(pack_dir, emoji_groups) do
emoji_txt = Path.join(pack_dir, "emoji.txt") emoji_txt = Path.join(pack_dir, "emoji.txt")
if File.exists?(emoji_txt) do if File.exists?(emoji_txt) do
Logger.info("Loading emoji pack from emoji.txt: #{emoji_txt}")
load_from_file(emoji_txt, emoji_groups) load_from_file(emoji_txt, emoji_groups)
else else
extensions = Config.get([:emoji, :pack_extensions]) extensions = Config.get([:emoji, :pack_extensions])

View file

@ -338,6 +338,26 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}" "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
end end
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "add_suggestion",
"subject" => users
}
}) do
"@#{actor_nickname} added suggested users: #{users_to_nicknames_string(users)}"
end
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "remove_suggestion",
"subject" => users
}
}) do
"@#{actor_nickname} removed suggested users: #{users_to_nicknames_string(users)}"
end
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},

View file

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

View file

@ -12,10 +12,16 @@ defmodule Pleroma.Telemetry.Logger do
[:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :reclaim, :stop],
[:pleroma, :connection_pool, :provision_failure], [:pleroma, :connection_pool, :provision_failure],
[:pleroma, :connection_pool, :client, :dead], [:pleroma, :connection_pool, :client, :dead],
[:pleroma, :connection_pool, :client, :add] [:pleroma, :connection_pool, :client, :add],
[:pleroma, :repo, :query]
] ]
def attach do def attach do
:telemetry.attach_many("pleroma-logger", @events, &handle_event/4, []) :telemetry.attach_many(
"pleroma-logger",
@events,
&Pleroma.Telemetry.Logger.handle_event/4,
[]
)
end end
# Passing anonymous functions instead of strings to logger is intentional, # Passing anonymous functions instead of strings to logger is intentional,
@ -87,4 +93,64 @@ def handle_event(
end end
def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok
def handle_event(
[:pleroma, :repo, :query] = _name,
%{query_time: query_time} = measurements,
%{source: source} = metadata,
config
) do
logging_config = Pleroma.Config.get([:telemetry, :slow_queries_logging], [])
if logging_config[:enabled] &&
logging_config[:min_duration] &&
query_time > logging_config[:min_duration] and
(is_nil(logging_config[:exclude_sources]) or
source not in logging_config[:exclude_sources]) do
log_slow_query(measurements, metadata, config)
else
:ok
end
end
defp log_slow_query(
%{query_time: query_time} = _measurements,
%{source: _source, query: query, params: query_params, repo: repo} = _metadata,
_config
) do
sql_explain =
with {:ok, %{rows: explain_result_rows}} <-
repo.query("EXPLAIN " <> query, query_params, log: false) do
Enum.map_join(explain_result_rows, "\n", & &1)
end
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
pleroma_stacktrace =
Enum.filter(stacktrace, fn
{__MODULE__, _, _, _} ->
false
{mod, _, _, _} ->
mod
|> to_string()
|> String.starts_with?("Elixir.Pleroma.")
end)
Logger.warn(fn ->
"""
Slow query!
Total time: #{round(query_time / 1_000)} ms
#{query}
#{inspect(query_params, limit: :infinity)}
#{sql_explain}
#{Exception.format_stacktrace(pleroma_stacktrace)}
"""
end)
end
end end

View file

@ -148,6 +148,8 @@ defmodule Pleroma.User do
field(:last_active_at, :naive_datetime) field(:last_active_at, :naive_datetime)
field(:disclose_client, :boolean, default: true) field(:disclose_client, :boolean, default: true)
field(:pinned_objects, :map, default: %{}) field(:pinned_objects, :map, default: %{})
field(:is_suggested, :boolean, default: false)
field(:last_status_at, :naive_datetime)
embeds_one( embeds_one(
:notification_settings, :notification_settings,
@ -1677,6 +1679,22 @@ def confirm(%User{is_confirmed: false} = user) do
def confirm(%User{} = user), do: {:ok, user} def confirm(%User{} = user), do: {:ok, user}
def set_suggestion(users, is_suggested) when is_list(users) do
Repo.transaction(fn ->
Enum.map(users, fn user ->
with {:ok, user} <- set_suggestion(user, is_suggested), do: user
end)
end)
end
def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user}
def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do
user
|> change(is_suggested: is_suggested)
|> update_and_set_cache()
end
def update_notification_settings(%User{} = user, settings) do def update_notification_settings(%User{} = user, settings) do
user user
|> cast(%{notification_settings: settings}, []) |> cast(%{notification_settings: settings}, [])
@ -2483,4 +2501,16 @@ def active_user_count(days \\ 30) do
|> where([u], u.local == true) |> where([u], u.local == true)
|> Repo.aggregate(:count) |> Repo.aggregate(:count)
end end
def update_last_status_at(user) do
User
|> where(id: ^user.id)
|> update([u], set: [last_status_at: fragment("NOW()")])
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end
end end

View file

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

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

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

View file

@ -81,6 +81,10 @@ def decrease_note_count_if_public(actor, object) do
if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor} if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
end end
def update_last_status_at_if_public(actor, object) do
if is_public?(object), do: User.update_last_status_at(actor), else: {:ok, actor}
end
defp increase_replies_count_if_reply(%{ defp increase_replies_count_if_reply(%{
"object" => %{"inReplyTo" => reply_ap_id} = object, "object" => %{"inReplyTo" => reply_ap_id} = object,
"type" => "Create" "type" => "Create"
@ -288,6 +292,7 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
_ <- increase_replies_count_if_reply(create_data), _ <- increase_replies_count_if_reply(create_data),
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity}, {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity), {:ok, _actor} <- increase_note_count_if_public(actor, activity),
{:ok, _actor} <- update_last_status_at_if_public(actor, activity),
_ <- notify_and_stream(activity), _ <- notify_and_stream(activity),
:ok <- maybe_schedule_poll_notifications(activity), :ok <- maybe_schedule_poll_notifications(activity),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
alias OpenApiSpex.Operation alias OpenApiSpex.Operation
alias OpenApiSpex.Schema alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.Helpers
alias Pleroma.Web.ApiSpec.Schemas.App
@spec open_api_operation(atom) :: Operation.t() @spec open_api_operation(atom) :: Operation.t()
def open_api_operation(action) do def open_api_operation(action) do
@ -22,7 +23,7 @@ def create_operation do
operationId: "AppController.create", operationId: "AppController.create",
requestBody: Helpers.request_body("Parameters", create_request(), required: true), requestBody: Helpers.request_body("Parameters", create_request(), required: true),
responses: %{ responses: %{
200 => Operation.response("App", "application/json", create_response()), 200 => Operation.response("App", "application/json", App),
422 => 422 =>
Operation.response( Operation.response(
"Unprocessable Entity", "Unprocessable Entity",
@ -119,30 +120,4 @@ defp create_request do
} }
} }
end end
defp create_response do
%Schema{
title: "AppCreateResponse",
description: "Response schema for an app",
type: :object,
properties: %{
id: %Schema{type: :string},
name: %Schema{type: :string},
client_id: %Schema{type: :string},
client_secret: %Schema{type: :string},
redirect_uri: %Schema{type: :string},
vapid_key: %Schema{type: :string},
website: %Schema{type: :string, nullable: true}
},
example: %{
"id" => "123",
"name" => "My App",
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
"vapid_key" =>
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
"website" => "https://myapp.com/"
}
}
end
end end

View file

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

View file

@ -0,0 +1,31 @@
# 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.PleromaAppOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.App
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
@spec index_operation() :: Operation.t()
def index_operation do
%Operation{
tags: ["Applications"],
summary: "List applications",
description: "List the OAuth applications for the current user",
operationId: "AppController.index",
responses: %{
200 => Operation.response("Array of App", "application/json", array_of_apps())
}
}
end
defp array_of_apps do
%Schema{type: :array, items: App, example: [App.schema().example]}
end
end

View file

@ -239,6 +239,32 @@ def remote_subscribe_operation do
} }
end end
def remote_interaction_operation do
%Operation{
tags: ["Accounts"],
summary: "Remote interaction",
operationId: "UtilController.remote_interaction",
requestBody: request_body("Parameters", remote_interaction_request(), required: true),
responses: %{
200 =>
Operation.response("Remote interaction URL", "application/json", %Schema{type: :object})
}
}
end
defp remote_interaction_request do
%Schema{
title: "RemoteInteractionRequest",
description: "POST body for remote interaction",
type: :object,
required: [:ap_id, :profile],
properties: %{
ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"},
profile: %Schema{type: :string, description: "Remote profile webfinger"}
}
}
end
defp delete_account_request do defp delete_account_request do
%Schema{ %Schema{
title: "AccountDeleteRequest", title: "AccountDeleteRequest",

View file

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

View file

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

View file

@ -0,0 +1,33 @@
# 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.Schemas.App do
alias OpenApiSpex.Schema
require OpenApiSpex
OpenApiSpex.schema(%{
title: "App",
description: "Response schema for an app",
type: :object,
properties: %{
id: %Schema{type: :string},
name: %Schema{type: :string},
client_id: %Schema{type: :string},
client_secret: %Schema{type: :string},
redirect_uri: %Schema{type: :string},
vapid_key: %Schema{type: :string},
website: %Schema{type: :string, nullable: true}
},
example: %{
"id" => "123",
"name" => "My App",
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
"vapid_key" =>
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
"website" => "https://myapp.com/"
}
})
end

View file

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

View file

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

View file

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

View file

@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Maps alias Pleroma.Maps
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserNote
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Pipeline
@ -31,7 +32,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:skip_auth when action == :create) plug(:skip_auth when action in [:create, :lookup])
plug(:skip_public_check when action in [:show, :statuses]) plug(:skip_public_check when action in [:show, :statuses])
@ -53,7 +54,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
when action in [:verify_credentials, :endorsements, :identity_proofs] when action in [:verify_credentials, :endorsements, :identity_proofs]
) )
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials) plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]}
when action in [:update_credentials, :note]
)
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
@ -79,7 +84,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute]) plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
@relationship_actions [:follow, :unfollow] @relationship_actions [:follow, :unfollow]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a @needs_account ~W(followers following lists follow unfollow mute unmute block unblock note)a
plug( plug(
RateLimiter, RateLimiter,
@ -435,6 +440,16 @@ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
end end
end end
@doc "POST /api/v1/accounts/:id/note"
def note(
%{assigns: %{user: noter, account: target}, body_params: %{comment: comment}} = conn,
_params
) do
with {:ok, _user_note} <- UserNote.create(noter, target, comment) do
render(conn, "relationship.json", user: noter, target: target)
end
end
@doc "POST /api/v1/follows" @doc "POST /api/v1/follows"
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do case User.get_cached_by_nickname(uri) do
@ -477,6 +492,18 @@ def blocks(%{assigns: %{user: user}} = conn, params) do
|> render("index.json", users: users, for: user, as: :user) |> render("index.json", users: users, for: user, as: :user)
end end
@doc "GET /api/v1/accounts/lookup"
def lookup(conn, %{acct: nickname} = _params) do
with %User{} = user <- User.get_by_nickname(nickname) do
render(conn, "show.json",
user: user,
skip_visibility_check: true
)
else
error -> user_visibility_error(conn, error)
end
end
@doc "GET /api/v1/endorsements" @doc "GET /api/v1/endorsements"
def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params) def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params)

View file

@ -10,7 +10,9 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Maps
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Scopes alias Pleroma.Web.OAuth.Scopes
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
@ -26,11 +28,13 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
@doc "POST /api/v1/apps" @doc "POST /api/v1/apps"
def create(%{body_params: params} = conn, _params) do def create(%{body_params: params} = conn, _params) do
scopes = Scopes.fetch_scopes(params, ["read"]) scopes = Scopes.fetch_scopes(params, ["read"])
user_id = get_user_id(conn)
app_attrs = app_attrs =
params params
|> Map.take([:client_name, :redirect_uris, :website]) |> Map.take([:client_name, :redirect_uris, :website])
|> Map.put(:scopes, scopes) |> Map.put(:scopes, scopes)
|> Maps.put_if_present(:user_id, user_id)
with cs <- App.register_changeset(%App{}, app_attrs), with cs <- App.register_changeset(%App{}, app_attrs),
{:ok, app} <- Repo.insert(cs) do {:ok, app} <- Repo.insert(cs) do
@ -38,6 +42,9 @@ def create(%{body_params: params} = conn, _params) do
end end
end end
defp get_user_id(%{assigns: %{user: %User{id: user_id}}}), do: user_id
defp get_user_id(_conn), do: nil
@doc """ @doc """
GET /api/v1/apps/verify_credentials GET /api/v1/apps/verify_credentials
Gets compact non-secret representation of the app. Supports app tokens and user tokens. Gets compact non-secret representation of the app. Supports app tokens and user tokens.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -69,7 +69,8 @@ def get_nodeinfo("2.0") do
mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false), mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
features: features, features: features,
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false) skipThreadContainment: Config.get([:instance, :skip_thread_containment], false),
privilegedStaff: Config.get([:instance, :privileged_staff])
} }
} }
end end

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.App do
import Ecto.Changeset import Ecto.Changeset
import Ecto.Query import Ecto.Query
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User
@type t :: %__MODULE__{} @type t :: %__MODULE__{}
@ -19,6 +20,8 @@ defmodule Pleroma.Web.OAuth.App do
field(:client_secret, :string) field(:client_secret, :string)
field(:trusted, :boolean, default: false) field(:trusted, :boolean, default: false)
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all) has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all)
has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all) has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all)
@ -27,7 +30,7 @@ defmodule Pleroma.Web.OAuth.App do
@spec changeset(t(), map()) :: Ecto.Changeset.t() @spec changeset(t(), map()) :: Ecto.Changeset.t()
def changeset(struct, params) do def changeset(struct, params) do
cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted]) cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted, :user_id])
end end
@spec register_changeset(t(), map()) :: Ecto.Changeset.t() @spec register_changeset(t(), map()) :: Ecto.Changeset.t()
@ -129,6 +132,12 @@ def search(params) do
{:ok, Repo.all(query), count} {:ok, Repo.all(query), count}
end end
@spec get_user_apps(User.t()) :: {:ok, [t()], non_neg_integer()}
def get_user_apps(%User{id: user_id}) do
from(a in __MODULE__, where: a.user_id == ^user_id)
|> Repo.all()
end
@spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} @spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
def destroy(id) do def destroy(id) do
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do

View file

@ -0,0 +1,23 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.AppController do
use Pleroma.Web, :controller
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(OAuthScopesPlug, %{scopes: ["follow", "read"]} when action in [:index])
plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAppOperation
@doc "GET /api/v1/pleroma/apps"
def index(%{assigns: %{user: user}} = conn, _params) do
with apps <- App.get_user_apps(user) do
render(conn, "index.json", %{apps: apps})
end
end
end

View file

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

View file

@ -0,0 +1,11 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.AppView do
use Pleroma.Web, :view
def render("index.json", %{apps: apps}) do
render_many(apps, Pleroma.Web.MastodonAPI.AppView, "show.json")
end
end

View file

@ -0,0 +1,36 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug do
@moduledoc """
Ensures staff are privileged enough to do certain tasks.
"""
import Pleroma.Web.TranslationHelpers
import Plug.Conn
alias Pleroma.Config
alias Pleroma.User
def init(options) do
options
end
def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _), do: conn
def call(%{assigns: %{user: %User{is_moderator: true}}} = conn, _) do
if Config.get!([:instance, :privileged_staff]) do
conn
else
conn
|> render_error(:forbidden, "User is not an admin.")
|> halt()
end
end
def call(conn, _) do
conn
|> render_error(:forbidden, "User is not a staff member.")
|> halt()
end
end

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.Router do defmodule Pleroma.Web.Router do
use Pleroma.Web, :router use Pleroma.Web, :router
import Phoenix.LiveDashboard.Router
pipeline :accepts_html do pipeline :accepts_html do
plug(:accepts, ["html"]) plug(:accepts, ["html"])
@ -100,6 +101,10 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.IdempotencyPlug) plug(Pleroma.Web.Plugs.IdempotencyPlug)
end end
pipeline :require_privileged_staff do
plug(Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug)
end
pipeline :require_admin do pipeline :require_admin do
plug(Pleroma.Web.Plugs.UserIsAdminPlug) plug(Pleroma.Web.Plugs.UserIsAdminPlug)
end end
@ -150,6 +155,7 @@ defmodule Pleroma.Web.Router do
get("/emoji", UtilController, :emoji) get("/emoji", UtilController, :emoji)
get("/captcha", UtilController, :captcha) get("/captcha", UtilController, :captcha)
get("/healthcheck", UtilController, :healthcheck) get("/healthcheck", UtilController, :healthcheck)
post("/remote_interaction", UtilController, :remote_interaction)
end end
scope "/api/v1/pleroma", Pleroma.Web do scope "/api/v1/pleroma", Pleroma.Web do
@ -157,12 +163,11 @@ defmodule Pleroma.Web.Router do
post("/uploader_callback/:upload_path", UploaderController, :callback) post("/uploader_callback/:upload_path", UploaderController, :callback)
end end
# AdminAPI: only admins can perform these actions
scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through([:admin_api, :require_admin]) pipe_through([:admin_api, :require_admin])
put("/users/disable_mfa", AdminAPIController, :disable_mfa) put("/users/disable_mfa", AdminAPIController, :disable_mfa)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
get("/users/:nickname/permission_group", AdminAPIController, :right_get) get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get) get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
@ -185,35 +190,19 @@ defmodule Pleroma.Web.Router do
post("/users/follow", UserController, :follow) post("/users/follow", UserController, :follow)
post("/users/unfollow", UserController, :unfollow) post("/users/unfollow", UserController, :unfollow)
delete("/users", UserController, :delete)
post("/users", UserController, :create) post("/users", UserController, :create)
patch("/users/:nickname/toggle_activation", UserController, :toggle_activation)
patch("/users/activate", UserController, :activate) patch("/users/suggest", UserController, :suggest)
patch("/users/deactivate", UserController, :deactivate) patch("/users/unsuggest", UserController, :unsuggest)
patch("/users/approve", UserController, :approve)
get("/relay", RelayController, :index) get("/relay", RelayController, :index)
post("/relay", RelayController, :follow) post("/relay", RelayController, :follow)
delete("/relay", RelayController, :unfollow) delete("/relay", RelayController, :unfollow)
post("/users/invite_token", InviteController, :create)
get("/users/invites", InviteController, :index)
post("/users/revoke_invite", InviteController, :revoke)
post("/users/email_invite", InviteController, :email)
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
patch("/users/force_password_reset", AdminAPIController, :force_password_reset) patch("/users/force_password_reset", AdminAPIController, :force_password_reset)
get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials) get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials) patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
get("/users", UserController, :index)
get("/users/:nickname", UserController, :show)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
get("/instances/:instance/statuses", InstanceController, :list_statuses)
delete("/instances/:instance", InstanceController, :delete)
get("/instance_document/:name", InstanceDocumentController, :show) get("/instance_document/:name", InstanceDocumentController, :show)
patch("/instance_document/:name", InstanceDocumentController, :update) patch("/instance_document/:name", InstanceDocumentController, :update)
delete("/instance_document/:name", InstanceDocumentController, :delete) delete("/instance_document/:name", InstanceDocumentController, :delete)
@ -221,28 +210,12 @@ defmodule Pleroma.Web.Router do
patch("/users/confirm_email", AdminAPIController, :confirm_email) patch("/users/confirm_email", AdminAPIController, :confirm_email)
patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email) patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
get("/reports", ReportController, :index)
get("/reports/:id", ReportController, :show)
patch("/reports", ReportController, :update)
post("/reports/:id/notes", ReportController, :notes_create)
delete("/reports/:report_id/notes/:id", ReportController, :notes_delete)
get("/statuses/:id", StatusController, :show)
put("/statuses/:id", StatusController, :update)
delete("/statuses/:id", StatusController, :delete)
get("/statuses", StatusController, :index)
get("/config", ConfigController, :show) get("/config", ConfigController, :show)
post("/config", ConfigController, :update) post("/config", ConfigController, :update)
get("/config/descriptions", ConfigController, :descriptions) get("/config/descriptions", ConfigController, :descriptions)
get("/need_reboot", AdminAPIController, :need_reboot) get("/need_reboot", AdminAPIController, :need_reboot)
get("/restart", AdminAPIController, :restart) get("/restart", AdminAPIController, :restart)
get("/moderation_log", AdminAPIController, :list_log)
post("/reload_emoji", AdminAPIController, :reload_emoji)
get("/stats", AdminAPIController, :stats)
get("/oauth_app", OAuthAppController, :index) get("/oauth_app", OAuthAppController, :index)
post("/oauth_app", OAuthAppController, :create) post("/oauth_app", OAuthAppController, :create)
patch("/oauth_app/:id", OAuthAppController, :update) patch("/oauth_app/:id", OAuthAppController, :update)
@ -252,19 +225,74 @@ defmodule Pleroma.Web.Router do
post("/media_proxy_caches/delete", MediaProxyCacheController, :delete) post("/media_proxy_caches/delete", MediaProxyCacheController, :delete)
post("/media_proxy_caches/purge", MediaProxyCacheController, :purge) post("/media_proxy_caches/purge", MediaProxyCacheController, :purge)
get("/chats/:id", ChatController, :show)
get("/chats/:id/messages", ChatController, :messages)
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
get("/frontends", FrontendController, :index) get("/frontends", FrontendController, :index)
post("/frontends/install", FrontendController, :install) post("/frontends/install", FrontendController, :install)
post("/backups", AdminAPIController, :create_backup) post("/backups", AdminAPIController, :create_backup)
end end
# AdminAPI: admins and mods (staff) can perform these actions (if enabled by config)
scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through([:admin_api, :require_privileged_staff])
delete("/users", UserController, :delete)
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
get("/statuses", StatusController, :index)
get("/chats/:id", ChatController, :show)
get("/chats/:id/messages", ChatController, :messages)
end
# AdminAPI: admins and mods (staff) can perform these actions
scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through(:admin_api)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
patch("/users/:nickname/toggle_activation", UserController, :toggle_activation)
patch("/users/activate", UserController, :activate)
patch("/users/deactivate", UserController, :deactivate)
patch("/users/approve", UserController, :approve)
post("/users/invite_token", InviteController, :create)
get("/users/invites", InviteController, :index)
post("/users/revoke_invite", InviteController, :revoke)
post("/users/email_invite", InviteController, :email)
get("/users", UserController, :index)
get("/users/:nickname", UserController, :show)
get("/instances/:instance/statuses", InstanceController, :list_statuses)
delete("/instances/:instance", InstanceController, :delete)
get("/reports", ReportController, :index)
get("/reports/:id", ReportController, :show)
patch("/reports", ReportController, :update)
post("/reports/:id/notes", ReportController, :notes_create)
delete("/reports/:report_id/notes/:id", ReportController, :notes_delete)
get("/statuses/:id", StatusController, :show)
put("/statuses/:id", StatusController, :update)
delete("/statuses/:id", StatusController, :delete)
get("/moderation_log", AdminAPIController, :list_log)
post("/reload_emoji", AdminAPIController, :reload_emoji)
get("/stats", AdminAPIController, :stats)
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
end
scope "/api/v1/pleroma/emoji", Pleroma.Web.PleromaAPI do scope "/api/v1/pleroma/emoji", Pleroma.Web.PleromaAPI do
scope "/pack" do scope "/pack" do
pipe_through([:admin_api, :require_admin]) pipe_through(:admin_api)
post("/", EmojiPackController, :create) post("/", EmojiPackController, :create)
patch("/", EmojiPackController, :update) patch("/", EmojiPackController, :update)
@ -279,7 +307,7 @@ defmodule Pleroma.Web.Router do
# Modifying packs # Modifying packs
scope "/packs" do scope "/packs" do
pipe_through([:admin_api, :require_admin]) pipe_through(:admin_api)
get("/import", EmojiPackController, :import_from_filesystem) get("/import", EmojiPackController, :import_from_filesystem)
get("/remote", EmojiPackController, :remote) get("/remote", EmojiPackController, :remote)
@ -367,6 +395,7 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
pipe_through(:api) pipe_through(:api)
get("/apps", AppController, :index)
get("/statuses/:id/reactions/:emoji", EmojiReactionController, :index) get("/statuses/:id/reactions/:emoji", EmojiReactionController, :index)
get("/statuses/:id/reactions", EmojiReactionController, :index) get("/statuses/:id/reactions", EmojiReactionController, :index)
end end
@ -456,6 +485,7 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/unblock", AccountController, :unblock) post("/accounts/:id/unblock", AccountController, :unblock)
post("/accounts/:id/mute", AccountController, :mute) post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute) post("/accounts/:id/unmute", AccountController, :unmute)
post("/accounts/:id/note", AccountController, :note)
get("/conversations", ConversationController, :index) get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read) post("/conversations/:id/read", ConversationController, :mark_as_read)
@ -535,6 +565,7 @@ defmodule Pleroma.Web.Router do
delete("/push/subscription", SubscriptionController, :delete) delete("/push/subscription", SubscriptionController, :delete)
get("/suggestions", SuggestionController, :index) get("/suggestions", SuggestionController, :index)
delete("/suggestions/:account_id", SuggestionController, :dismiss)
get("/timelines/home", TimelineController, :home) get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct) get("/timelines/direct", TimelineController, :direct)
@ -554,6 +585,8 @@ defmodule Pleroma.Web.Router do
get("/accounts/search", SearchController, :account_search) get("/accounts/search", SearchController, :account_search)
get("/search", SearchController, :search) get("/search", SearchController, :search)
get("/accounts/lookup", AccountController, :lookup)
get("/accounts/:id/statuses", AccountController, :statuses) get("/accounts/:id/statuses", AccountController, :statuses)
get("/accounts/:id/followers", AccountController, :followers) get("/accounts/:id/followers", AccountController, :followers)
get("/accounts/:id/following", AccountController, :following) get("/accounts/:id/following", AccountController, :following)
@ -579,6 +612,8 @@ defmodule Pleroma.Web.Router do
get("/timelines/tag/:tag", TimelineController, :hashtag) get("/timelines/tag/:tag", TimelineController, :hashtag)
get("/polls/:id", PollController, :show) get("/polls/:id", PollController, :show)
get("/directory", DirectoryController, :index)
end end
scope "/api/v2", Pleroma.Web.MastodonAPI do scope "/api/v2", Pleroma.Web.MastodonAPI do
@ -586,6 +621,8 @@ defmodule Pleroma.Web.Router do
get("/search", SearchController, :search2) get("/search", SearchController, :search2)
post("/media", MediaController, :create2) post("/media", MediaController, :create2)
get("/suggestions", SuggestionController, :index2)
end end
scope "/api", Pleroma.Web do scope "/api", Pleroma.Web do
@ -627,6 +664,11 @@ defmodule Pleroma.Web.Router do
get("/activities/:uuid", OStatus.OStatusController, :activity) get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice) get("/notice/:id", OStatus.OStatusController, :notice)
# Notice compatibility routes for other frontends
get("/@:nickname/:id", OStatus.OStatusController, :notice)
get("/@:nickname/posts/:id", OStatus.OStatusController, :notice)
get("/:nickname/status/:id", OStatus.OStatusController, :notice)
# Mastodon compatibility routes # Mastodon compatibility routes
get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object) get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object)
get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity) get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity)
@ -736,6 +778,12 @@ defmodule Pleroma.Web.Router do
get("/:version", Nodeinfo.NodeinfoController, :nodeinfo) get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
end end
scope "/", Pleroma.Web do
pipe_through(:api)
get("/manifest.json", ManifestController, :show)
end
scope "/", Pleroma.Web do scope "/", Pleroma.Web do
pipe_through(:pleroma_html) pipe_through(:pleroma_html)
@ -757,6 +805,11 @@ defmodule Pleroma.Web.Router do
end end
end end
scope "/" do
pipe_through([:pleroma_html, :authenticate, :require_admin])
live_dashboard("/phoenix/live_dashboard")
end
# Test-only routes needed to test action dispatching and plug chain execution # Test-only routes needed to test action dispatching and plug chain execution
if Pleroma.Config.get(:env) == :test do if Pleroma.Config.get(:env) == :test do
@test_actions [ @test_actions [

View file

@ -167,6 +167,15 @@ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts), defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id) do: assign(conn, :notice_id, notice_id)
defp assign_id(%{path_info: ["@" <> _nickname, notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
defp assign_id(%{path_info: ["@" <> _nickname, "posts", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
defp assign_id(%{path_info: [_nickname, "status", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
defp assign_id(%{path_info: ["users", user_id]} = conn, _opts), defp assign_id(%{path_info: ["users", user_id]} = conn, _opts),
do: assign(conn, :username_or_id, user_id) do: assign(conn, :username_or_id, user_id)

View file

@ -62,6 +62,15 @@ def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profil
end end
end end
def remote_interaction(%{body_params: %{ap_id: ap_id, profile: profile}} = conn, _params) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do
conn
|> json(%{url: String.replace(template, "{uri}", ap_id)})
else
_e -> json(conn, %{error: "Couldn't find user"})
end
end
def frontend_configurations(conn, _params) do def frontend_configurations(conn, _params) do
render(conn, "frontend_configurations.json") render(conn, "frontend_configurations.json")
end end

View file

@ -0,0 +1,28 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ManifestView do
use Pleroma.Web, :view
alias Pleroma.Config
alias Pleroma.Web.Endpoint
def render("manifest.json", _params) do
%{
name: Config.get([:instance, :name]),
description: Config.get([:instance, :description]),
icons: Config.get([:manifest, :icons]),
theme_color: Config.get([:manifest, :theme_color]),
background_color: Config.get([:manifest, :background_color]),
display: "standalone",
scope: Endpoint.url(),
start_url: "/",
categories: [
"social"
],
serviceworker: %{
src: "/sw.js"
}
}
end
end

15
mix.exs
View file

@ -8,7 +8,7 @@ def project do
elixir: "~> 1.9", elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(), compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())], elixirc_options: [warnings_as_errors: warnings_as_errors()],
xref: [exclude: [:eldap]], xref: [exclude: [:eldap]],
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
aliases: aliases(), aliases: aliases(),
@ -79,6 +79,7 @@ def application do
:comeonin, :comeonin,
:quack, :quack,
:fast_sanitize, :fast_sanitize,
:os_mon,
:ssl :ssl
], ],
included_applications: [:ex_syslogger] included_applications: [:ex_syslogger]
@ -90,8 +91,7 @@ defp elixirc_paths(:benchmark), do: ["lib", "benchmarks", "priv/scrubbers"]
defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"] defp elixirc_paths(_), do: ["lib"]
defp warnings_as_errors(:prod), do: false defp warnings_as_errors, do: System.get_env("CI") == "true"
defp warnings_as_errors(_), do: true
# Specifies OAuth dependencies. # Specifies OAuth dependencies.
defp oauth_deps do defp oauth_deps do
@ -129,7 +129,7 @@ defp deps do
{:trailing_format_plug, "~> 0.0.7"}, {:trailing_format_plug, "~> 0.0.7"},
{:fast_sanitize, "~> 0.2.0"}, {:fast_sanitize, "~> 0.2.0"},
{:html_entities, "~> 0.5", override: true}, {:html_entities, "~> 0.5", override: true},
{:phoenix_html, "~> 2.14"}, {:phoenix_html, "~> 3.1", override: true},
{:calendar, "~> 1.0"}, {:calendar, "~> 1.0"},
{:cachex, "~> 3.2"}, {:cachex, "~> 3.2"},
{:poison, "~> 3.0", override: true}, {:poison, "~> 3.0", override: true},
@ -137,6 +137,7 @@ defp deps do
{:castore, "~> 0.1"}, {:castore, "~> 0.1"},
{:cowlib, "~> 2.9", override: true}, {:cowlib, "~> 2.9", override: true},
{:gun, "~> 2.0.0-rc.1", override: true}, {:gun, "~> 2.0.0-rc.1", override: true},
{:finch, "~> 0.10.0"},
{:jason, "~> 1.2"}, {:jason, "~> 1.2"},
{:mogrify, "~> 0.9.1"}, {:mogrify, "~> 0.9.1"},
{:ex_aws, "~> 2.1.6"}, {:ex_aws, "~> 2.1.6"},
@ -192,9 +193,7 @@ defp deps do
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"}, ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
{:restarter, path: "./restarter"}, {:restarter, path: "./restarter"},
{:majic, {:majic, "~> 1.0"},
git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
{:eblurhash, "~> 1.1.0"}, {:eblurhash, "~> 1.1.0"},
{:open_api_spex, "~> 3.10"}, {:open_api_spex, "~> 3.10"},
{:elastix, ">= 0.0.0"}, {:elastix, ">= 0.0.0"},
@ -202,6 +201,8 @@ defp deps do
git: "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", git: "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git",
ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"}, ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"},
{:nimble_parsec, "~> 1.0", override: true}, {:nimble_parsec, "~> 1.0", override: true},
{:phoenix_live_dashboard, "~> 0.6.2"},
{:ecto_psql_extras, "~> 0.6"},
# indirect dependency version override # indirect dependency version override
{:plug, "~> 1.10.4", override: true}, {:plug, "~> 1.10.4", override: true},

View file

@ -32,6 +32,7 @@
"eblurhash": {:hex, :eblurhash, "1.1.0", "e10ccae762598507ebfacf0b645ed49520f2afa3e7e9943e73a91117dffce415", [:rebar3], [], "hexpm", "2e6b889d09fddd374e3c5ac57c486138768763264e99ac1074ae5fa7fc9ab51d"}, "eblurhash": {:hex, :eblurhash, "1.1.0", "e10ccae762598507ebfacf0b645ed49520f2afa3e7e9943e73a91117dffce415", [:rebar3], [], "hexpm", "2e6b889d09fddd374e3c5ac57c486138768763264e99ac1074ae5fa7fc9ab51d"},
"ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"}, "ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.4", "5d43fd088d39a158c860b17e8d210669587f63ec89ea122a4654861c8c6e2db4", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.15.7", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "311db02f1b772e3d0dc7f56a05044b5e1499d78ed6abf38885e1ca70059449e5"},
"ecto_sql": {:hex, :ecto_sql, "3.6.2", "9526b5f691701a5181427634c30655ac33d11e17e4069eff3ae1176c764e0ba3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.6.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ec9d7e6f742ea39b63aceaea9ac1d1773d574ea40df5a53ef8afbd9242fdb6b"}, "ecto_sql": {:hex, :ecto_sql, "3.6.2", "9526b5f691701a5181427634c30655ac33d11e17e4069eff3ae1176c764e0ba3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.6.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ec9d7e6f742ea39b63aceaea9ac1d1773d574ea40df5a53ef8afbd9242fdb6b"},
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"}, "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
"elasticsearch": {:hex, :elasticsearch, "1.0.1", "8339538d90af6b280f10ecd02b1eae372f09373e629b336a13461babf7366495", [:mix], [{:httpoison, ">= 0.0.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sigaws, "~> 0.7", [hex: :sigaws, repo: "hexpm", optional: true]}, {:vex, "~> 0.6", [hex: :vex, repo: "hexpm", optional: false]}], "hexpm", "83e7d8b8bee3e7e19a06ab4d357d24845ac1da894e79678227fd52c0b7f71867"}, "elasticsearch": {:hex, :elasticsearch, "1.0.1", "8339538d90af6b280f10ecd02b1eae372f09373e629b336a13461babf7366495", [:mix], [{:httpoison, ">= 0.0.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sigaws, "~> 0.7", [hex: :sigaws, repo: "hexpm", optional: true]}, {:vex, "~> 0.6", [hex: :vex, repo: "hexpm", optional: false]}], "hexpm", "83e7d8b8bee3e7e19a06ab4d357d24845ac1da894e79678227fd52c0b7f71867"},
@ -47,9 +48,10 @@
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
"excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"}, "excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"},
"fast_html": {:hex, :fast_html, "2.0.4", "4910ee49f2f6b19692e3bf30bf97f1b6b7dac489cd6b0f34cd0fe3042c56ba30", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "3bb49d541dfc02ad5e425904f53376d758c09f89e521afc7d2b174b3227761ea"}, "fast_html": {:hex, :fast_html, "2.0.5", "c61760340606c1077ff1f196f17834056cb1dd3d5cb92a9f2cabf28bc6221c3c", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "605f4f4829443c14127694ebabb681778712ceecb4470ec32aa31012330e6506"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.2", "3cbbaebaea6043865dfb5b4ecb0f1af066ad410a51470e353714b10c42007b81", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "69f204db9250afa94a0d559d9110139850f57de2b081719fbafa1e9a89e94466"}, "fast_sanitize": {:hex, :fast_sanitize, "0.2.2", "3cbbaebaea6043865dfb5b4ecb0f1af066ad410a51470e353714b10c42007b81", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "69f204db9250afa94a0d559d9110139850f57de2b081719fbafa1e9a89e94466"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.10.0", "8e5e6101ae98e7f1ef830594f774411a2f9cbce4f92d8179502da69fbbff52bc", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80324ba22edbdebca6fac05c8517e7457b79dfe101e3bf6b2f7c5c65c93a9077"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
"floki": {:hex, :floki, "0.30.1", "75d35526d3a1459920b6e87fdbc2e0b8a3670f965dd0903708d2b267e0904c55", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e9c03524447d1c4cbfccd672d739b8c18453eee377846b119d4fd71b1a176bb8"}, "floki": {:hex, :floki, "0.30.1", "75d35526d3a1459920b6e87fdbc2e0b8a3670f965dd0903708d2b267e0904c55", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e9c03524447d1c4cbfccd672d739b8c18453eee377846b119d4fd71b1a176bb8"},
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
@ -70,7 +72,7 @@
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
"linkify": {:hex, :linkify, "0.5.1", "6dc415cbc948b2f6ecec7cb226aab7ba9d3a1815bb501ae33e042334d707ecee", [:mix], [], "hexpm", "a3128c7e22fada4aa7214009501d8131e1fa3faf2f0a68b33dba379dc84ff944"}, "linkify": {:hex, :linkify, "0.5.1", "6dc415cbc948b2f6ecec7cb226aab7ba9d3a1815bb501ae33e042334d707ecee", [:mix], [], "hexpm", "a3128c7e22fada4aa7214009501d8131e1fa3faf2f0a68b33dba379dc84ff944"},
"majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "289cda1b6d0d70ccb2ba508a2b0bd24638db2880", [ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"]}, "majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
@ -78,13 +80,15 @@
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mint": {:hex, :mint, "1.4.0", "cd7d2451b201fc8e4a8fd86257fb3878d9e3752899eb67b0c5b25b180bde1212", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "10a99e144b815cbf8522dccbc8199d15802440fc7a64d67b6853adb6fa170217"},
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"}, "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"},
"mogrify": {:hex, :mogrify, "0.9.1", "a26f107c4987477769f272bd0f7e3ac4b7b75b11ba597fd001b877beffa9c068", [:mix], [], "hexpm", "134edf189337d2125c0948bf0c228fdeef975c594317452d536224069a5b7f05"}, "mogrify": {:hex, :mogrify, "0.9.1", "a26f107c4987477769f272bd0f7e3ac4b7b75b11ba597fd001b877beffa9c068", [:mix], [], "hexpm", "134edf189337d2125c0948bf0c228fdeef975c594317452d536224069a5b7f05"},
"mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"}, "mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"},
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]}, "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
"nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"}, "nimble_pool": {:hex, :nimble_pool, "0.2.4", "1db8e9f8a53d967d595e0b32a17030cdb6c0dc4a451b8ac787bf601d3f7704c3", [:mix], [], "hexpm", "367e8071e137b787764e6a9992ccb57b276dc2282535f767a07d881951ebeac6"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "2.3.4", "ec7509b9af2524d55f529cb7aee93d36131ae0bf0f37706f65d2fe707f4d9fd8", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c70ca0434758fd1805422ea4446af5e910ddc697c0c861549c8f0eb0cfbd2fdf"}, "oban": {:hex, :oban, "2.3.4", "ec7509b9af2524d55f529cb7aee93d36131ae0bf0f37706f65d2fe707f4d9fd8", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c70ca0434758fd1805422ea4446af5e910ddc697c0c861549c8f0eb0cfbd2fdf"},
"open_api_spex": {:hex, :open_api_spex, "3.10.0", "94e9521ad525b3fcf6dc77da7c45f87fdac24756d4de588cb0816b413e7c1844", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "2dbb2bde3d2b821f06936e8dfaf3284331186556291946d84eeba3750ac28765"}, "open_api_spex": {:hex, :open_api_spex, "3.10.0", "94e9521ad525b3fcf6dc77da7c45f87fdac24756d4de588cb0816b413e7c1844", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "2dbb2bde3d2b821f06936e8dfaf3284331186556291946d84eeba3750ac28765"},
@ -93,7 +97,9 @@
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
"phoenix": {:hex, :phoenix, "1.5.9", "a6368d36cfd59d917b37c44386e01315bc89f7609a10a45a22f47c007edf2597", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7e4bce20a67c012f1fbb0af90e5da49fa7bf0d34e3a067795703b74aef75427d"}, "phoenix": {:hex, :phoenix, "1.5.9", "a6368d36cfd59d917b37c44386e01315bc89f7609a10a45a22f47c007edf2597", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7e4bce20a67c012f1fbb0af90e5da49fa7bf0d34e3a067795703b74aef75427d"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"},
"phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"}, "phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.2", "0769470265eb13af01b5001b29cb935f4710d6adaa1ffc18417a570a337a2f0f", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5bc6c6b38a2ca8b5020b442322fcee6afd5e641637a0b1fb059d4bd89bc58e7b"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.17.5", "63f52a6f9f6983f04e424586ff897c016ecc5e4f8d1e2c22c2887af1c57215d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c5586e6a3d4df71b8214c769d4f5eb8ece2b4001711a7ca0f97323c36958b0e3"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.3", "039435dd975f7e55953525b88f1d596f26c6141412584c16f4db109708a8ee68", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4a540cea32e05356541737033d666ee7fea7700eb2101bf76783adbfe06601cd"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.3", "039435dd975f7e55953525b88f1d596f26c6141412584c16f4db109708a8ee68", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4a540cea32e05356541737033d666ee7fea7700eb2101bf76783adbfe06601cd"},
"plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"},
@ -121,7 +127,9 @@
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
"swoosh": {:hex, :swoosh, "1.3.11", "34f79c57f19892b43bd2168de9ff5de478a721a26328ef59567aad4243e7a77b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "f1e2a048db454f9982b9cf840f75e7399dd48be31ecc2a7dc10012a803b913af"}, "swoosh": {:hex, :swoosh, "1.3.11", "34f79c57f19892b43bd2168de9ff5de478a721a26328ef59567aad4243e7a77b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "f1e2a048db454f9982b9cf840f75e7399dd48be31ecc2a7dc10012a803b913af"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"tesla": {:hex, :tesla, "1.4.1", "ff855f1cac121e0d16281b49e8f066c4a0d89965f98864515713878cca849ac8", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "95f5de35922c8c4b3945bee7406f66eb680b0955232f78f5fb7e853aa1ce201a"}, "tesla": {:hex, :tesla, "1.4.1", "ff855f1cac121e0d16281b49e8f066c4a0d89965f98864515713878cca849ac8", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "95f5de35922c8c4b3945bee7406f66eb680b0955232f78f5fb7e853aa1ce201a"},
"timex": {:hex, :timex, "3.7.5", "3eca56e23bfa4e0848f0b0a29a92fa20af251a975116c6d504966e8a90516dfd", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a15608dca680f2ef663d71c95842c67f0af08a0f3b1d00e17bbd22872e2874e4"}, "timex": {:hex, :timex, "3.7.5", "3eca56e23bfa4e0848f0b0a29a92fa20af251a975116c6d504966e8a90516dfd", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a15608dca680f2ef663d71c95842c67f0af08a0f3b1d00e17bbd22872e2874e4"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},

View file

@ -0,0 +1,11 @@
defmodule Pleroma.Repo.Migrations.AddUserIdToApps do
use Ecto.Migration
def change do
alter table(:apps) do
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
end
create_if_not_exists(index(:apps, [:user_id]))
end
end

View file

@ -0,0 +1,15 @@
defmodule Pleroma.Repo.Migrations.CreateUserNotes do
use Ecto.Migration
def change do
create_if_not_exists table(:user_notes) do
add(:source_id, references(:users, type: :uuid, on_delete: :delete_all))
add(:target_id, references(:users, type: :uuid, on_delete: :delete_all))
add(:comment, :string)
timestamps()
end
create_if_not_exists(unique_index(:user_notes, [:source_id, :target_id]))
end
end

View file

@ -0,0 +1,11 @@
defmodule Pleroma.Repo.Migrations.AddSuggestions do
use Ecto.Migration
def change do
alter table(:users) do
add(:is_suggested, :boolean, default: false, null: false)
end
create_if_not_exists(index(:users, [:is_suggested]))
end
end

View file

@ -0,0 +1,11 @@
defmodule Pleroma.Repo.Migrations.AddLastStatusAtToUsers do
use Ecto.Migration
def change do
alter table(:users) do
add(:last_status_at, :naive_datetime)
end
create_if_not_exists(index(:users, [:last_status_at]))
end
end

View file

@ -0,0 +1,7 @@
defmodule Pleroma.Repo.Migrations.AddIsDiscoverableIndexToUsers do
use Ecto.Migration
def change do
create(index(:users, [:is_discoverable]))
end
end

View file

@ -0,0 +1,7 @@
defmodule Pleroma.Repo.Migrations.UserRelationshipsTargetIdRelationshipTypeIndex do
use Ecto.Migration
def change do
create_if_not_exists(index(:user_relationships, [:target_id, :relationship_type]))
end
end

View file

@ -13,6 +13,7 @@ config :pleroma, Pleroma.Web.Endpoint,
url: [host: "<%= domain %>", scheme: "https", port: <%= port %>], url: [host: "<%= domain %>", scheme: "https", port: <%= port %>],
http: [ip: {<%= String.replace(listen_ip, ".", ", ") %>}, port: <%= listen_port %>], http: [ip: {<%= String.replace(listen_ip, ".", ", ") %>}, port: <%= listen_port %>],
secret_key_base: "<%= secret %>", secret_key_base: "<%= secret %>",
live_view: [signing_salt: "<%= lv_signing_salt %>"],
signing_salt: "<%= signing_salt %>" signing_salt: "<%= signing_salt %>"
config :pleroma, :instance, config :pleroma, :instance,

View file

@ -82,6 +82,7 @@ test "transfer config values with full subkey update" do
on_exit(fn -> Restarter.Pleroma.refresh() end) on_exit(fn -> Restarter.Pleroma.refresh() end)
end end
@tag :erratic
test "don't restart if no reboot time settings were changed" do test "don't restart if no reboot time settings were changed" do
clear_config(:emoji) clear_config(:emoji)
insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]]) insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]])
@ -92,18 +93,21 @@ test "don't restart if no reboot time settings were changed" do
) )
end end
@tag :erratic
test "on reboot time key" do test "on reboot time key" do
clear_config(:shout) clear_config(:shout)
insert(:config, key: :shout, value: [enabled: false]) insert(:config, key: :shout, value: [enabled: false])
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted" assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
end end
@tag :erratic
test "on reboot time subkey" do test "on reboot time subkey" do
clear_config(Pleroma.Captcha) clear_config(Pleroma.Captcha)
insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60]) insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted" assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
end end
@tag :erratic
test "don't restart pleroma on reboot time key and subkey if there is false flag" do test "don't restart pleroma on reboot time key and subkey if there is false flag" do
clear_config(:shout) clear_config(:shout)
clear_config(Pleroma.Captcha) clear_config(Pleroma.Captcha)

View file

@ -46,6 +46,7 @@ test "gives the same connection to 2 concurrent requests" do
end end
end end
@tag :erratic
test "connection limit is respected with concurrent requests" do test "connection limit is respected with concurrent requests" do
clear_config([:connections_pool, :max_connections]) do clear_config([:connections_pool, :max_connections]) do
clear_config([:connections_pool, :max_connections], 1) clear_config([:connections_pool, :max_connections], 1)

View file

@ -34,4 +34,14 @@ test "it returns internal users when enabled" do
assert %{internal: true} |> Query.build() |> Repo.aggregate(:count) == 2 assert %{internal: true} |> Query.build() |> Repo.aggregate(:count) == 2
end end
end end
test "is_suggested param" do
_user1 = insert(:user, is_suggested: false)
user2 = insert(:user, is_suggested: true)
assert [^user2] =
%{is_suggested: true}
|> User.Query.build()
|> Repo.all()
end
end end

View file

@ -1718,6 +1718,38 @@ test "delete/1 purges a remote user" do
assert user.banner == %{} assert user.banner == %{}
end end
describe "set_suggestion" do
test "suggests a user" do
user = insert(:user, is_suggested: false)
refute user.is_suggested
{:ok, user} = User.set_suggestion(user, true)
assert user.is_suggested
end
test "suggests a list of users" do
unsuggested_users = [
insert(:user, is_suggested: false),
insert(:user, is_suggested: false),
insert(:user, is_suggested: false)
]
{:ok, users} = User.set_suggestion(unsuggested_users, true)
assert Enum.count(users) == 3
Enum.each(users, fn user ->
assert user.is_suggested
end)
end
test "unsuggests a user" do
user = insert(:user, is_suggested: true)
assert user.is_suggested
{:ok, user} = User.set_suggestion(user, false)
refute user.is_suggested
end
end
test "get_public_key_for_ap_id fetches a user that's not in the db" do test "get_public_key_for_ap_id fetches a user that's not in the db" do
assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin") assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin")
end end

View file

@ -88,6 +88,16 @@ test "it unfollows and blocks", %{user: user, blocked: blocked, block: block} do
assert User.blocks?(user, blocked) assert User.blocks?(user, blocked)
end end
test "it updates following relationship", %{user: user, blocked: blocked, block: block} do
{:ok, _, _} = SideEffects.handle(block)
refute Pleroma.FollowingRelationship.get(user, blocked)
assert User.get_follow_state(user, blocked) == nil
assert User.get_follow_state(blocked, user) == nil
assert User.get_follow_state(user, blocked, nil) == nil
assert User.get_follow_state(blocked, user, nil) == nil
end
test "it blocks but does not unfollow if the relevant setting is set", %{ test "it blocks but does not unfollow if the relevant setting is set", %{
user: user, user: user,
blocked: blocked, blocked: blocked,
@ -542,4 +552,74 @@ test "it streams out the announce", %{announce: announce} do
end end
end end
end end
describe "removing a follower" do
setup do
user = insert(:user)
followed = insert(:user)
{:ok, _, _, follow_activity} = CommonAPI.follow(user, followed)
{:ok, reject_data, []} = Builder.reject(followed, follow_activity)
{:ok, reject, _meta} = ActivityPub.persist(reject_data, local: true)
%{user: user, followed: followed, reject: reject}
end
test "", %{user: user, followed: followed, reject: reject} do
assert User.following?(user, followed)
assert Pleroma.FollowingRelationship.get(user, followed)
{:ok, _, _} = SideEffects.handle(reject)
refute User.following?(user, followed)
refute Pleroma.FollowingRelationship.get(user, followed)
assert User.get_follow_state(user, followed) == nil
assert User.get_follow_state(user, followed, nil) == nil
end
end
describe "removing a follower from remote" do
setup do
user = insert(:user)
followed = insert(:user, local: false)
# Mock a local-to-remote follow
{:ok, follow_data, []} = Builder.follow(user, followed)
follow_data =
follow_data
|> Map.put("state", "accept")
{:ok, follow, _meta} = ActivityPub.persist(follow_data, local: true)
{:ok, _, _} = SideEffects.handle(follow)
# Mock a remote-to-local accept
{:ok, accept_data, _} = Builder.accept(followed, follow)
{:ok, accept, _} = ActivityPub.persist(accept_data, local: false)
{:ok, _, _} = SideEffects.handle(accept)
# Mock a remote-to-local reject
{:ok, reject_data, []} = Builder.reject(followed, follow)
{:ok, reject, _meta} = ActivityPub.persist(reject_data, local: false)
%{user: user, followed: followed, reject: reject}
end
test "", %{user: user, followed: followed, reject: reject} do
assert User.following?(user, followed)
assert Pleroma.FollowingRelationship.get(user, followed)
{:ok, _, _} = SideEffects.handle(reject)
refute User.following?(user, followed)
refute Pleroma.FollowingRelationship.get(user, followed)
assert Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, followed).data["state"] ==
"reject"
assert User.get_follow_state(user, followed) == nil
assert User.get_follow_state(user, followed, nil) == nil
end
end
end end

View file

@ -213,6 +213,20 @@ test "updates the state of all Follow activities with the same actor and object"
assert refresh_record(follow_activity).data["state"] == "accept" assert refresh_record(follow_activity).data["state"] == "accept"
assert refresh_record(follow_activity_two).data["state"] == "accept" assert refresh_record(follow_activity_two).data["state"] == "accept"
end end
test "also updates the state of accepted follows" do
user = insert(:user)
follower = insert(:user)
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
{:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
{:ok, follow_activity_two} =
Utils.update_follow_state_for_all(follow_activity_two, "reject")
assert refresh_record(follow_activity).data["state"] == "reject"
assert refresh_record(follow_activity_two).data["state"] == "reject"
end
end end
describe "update_follow_state/2" do describe "update_follow_state/2" do

View file

@ -873,6 +873,56 @@ test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
"@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}" "@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}"
end end
test "PATCH /api/pleroma/admin/users/suggest", %{admin: admin, conn: conn} do
user1 = insert(:user, is_suggested: false)
user2 = insert(:user, is_suggested: false)
response =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/suggest",
%{nicknames: [user1.nickname, user2.nickname]}
)
|> json_response_and_validate_schema(200)
assert Enum.map(response["users"], & &1["is_suggested"]) == [true, true]
[user1, user2] = Repo.reload!([user1, user2])
assert user1.is_suggested
assert user2.is_suggested
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} added suggested users: @#{user1.nickname}, @#{user2.nickname}"
end
test "PATCH /api/pleroma/admin/users/unsuggest", %{admin: admin, conn: conn} do
user1 = insert(:user, is_suggested: true)
user2 = insert(:user, is_suggested: true)
response =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/unsuggest",
%{nicknames: [user1.nickname, user2.nickname]}
)
|> json_response_and_validate_schema(200)
assert Enum.map(response["users"], & &1["is_suggested"]) == [false, false]
[user1, user2] = Repo.reload!([user1, user2])
refute user1.is_suggested
refute user2.is_suggested
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} removed suggested users: @#{user1.nickname}, @#{user2.nickname}"
end
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
user = insert(:user) user = insert(:user)
@ -906,6 +956,7 @@ defp user_response(user, attrs \\ %{}) do
"display_name" => HTML.strip_tags(user.name || user.nickname), "display_name" => HTML.strip_tags(user.name || user.nickname),
"is_confirmed" => true, "is_confirmed" => true,
"is_approved" => true, "is_approved" => true,
"is_suggested" => false,
"url" => user.ap_id, "url" => user.ap_id,
"registration_reason" => nil, "registration_reason" => nil,
"actor_type" => "Person", "actor_type" => "Person",

View file

@ -0,0 +1,17 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ManifestControllerTest do
use Pleroma.Web.ConnCase
setup do
clear_config([:instance, :name], "Manifest Test")
clear_config([:manifest, :theme_color], "#ff0000")
end
test "manifest.json", %{conn: conn} do
conn = get(conn, "/manifest.json")
assert %{"name" => "Manifest Test", "theme_color" => "#ff0000"} = json_response(conn, 200)
end
end

View file

@ -922,6 +922,27 @@ test "following with reblogs" do
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
end end
test "following with subscription and unsubscribing" do
%{conn: conn} = oauth_access(["follow"])
followed = insert(:user)
ret_conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/accounts/#{followed.id}/follow", %{notify: true})
assert %{"id" => _id, "subscribing" => true} =
json_response_and_validate_schema(ret_conn, 200)
ret_conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/accounts/#{followed.id}/follow", %{notify: false})
assert %{"id" => _id, "subscribing" => false} =
json_response_and_validate_schema(ret_conn, 200)
end
test "following / unfollowing errors", %{user: user, conn: conn} do test "following / unfollowing errors", %{user: user, conn: conn} do
# self follow # self follow
conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow") conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
@ -1776,4 +1797,45 @@ test "getting a list of blocks" do
assert [%{"id" => ^id2}] = result assert [%{"id" => ^id2}] = result
end end
test "account lookup", %{conn: conn} do
%{nickname: acct} = insert(:user, %{nickname: "nickname"})
%{nickname: acct_two} = insert(:user, %{nickname: "nickname@notlocaldoma.in"})
result =
conn
|> get("/api/v1/accounts/lookup?acct=#{acct}")
|> json_response_and_validate_schema(200)
assert %{"acct" => ^acct} = result
result =
conn
|> get("/api/v1/accounts/lookup?acct=#{acct_two}")
|> json_response_and_validate_schema(200)
assert %{"acct" => ^acct_two} = result
_result =
conn
|> get("/api/v1/accounts/lookup?acct=unexisting_nickname")
|> json_response_and_validate_schema(404)
end
test "create a note on a user" do
%{conn: conn} = oauth_access(["write:accounts", "read:follows"])
other_user = insert(:user)
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/accounts/#{other_user.id}/note", %{
"comment" => "Example note"
})
assert [%{"note" => "Example note"}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/relationships?id=#{other_user.id}")
|> json_response_and_validate_schema(200)
end
end end

View file

@ -35,6 +35,33 @@ test "apps/verify_credentials", %{conn: conn} do
end end
test "creates an oauth app", %{conn: conn} do test "creates an oauth app", %{conn: conn} do
app_attrs = build(:oauth_app)
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/apps", %{
client_name: app_attrs.client_name,
redirect_uris: app_attrs.redirect_uris
})
[app] = Repo.all(App)
expected = %{
"name" => app.client_name,
"website" => app.website,
"client_id" => app.client_id,
"client_secret" => app.client_secret,
"id" => app.id |> to_string(),
"redirect_uri" => app.redirect_uris,
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
}
assert expected == json_response_and_validate_schema(conn, 200)
assert app.user_id == nil
end
test "creates an oauth app with a user", %{conn: conn} do
user = insert(:user) user = insert(:user)
app_attrs = build(:oauth_app) app_attrs = build(:oauth_app)
@ -60,5 +87,6 @@ test "creates an oauth app", %{conn: conn} do
} }
assert expected == json_response_and_validate_schema(conn, 200) assert expected == json_response_and_validate_schema(conn, 200)
assert app.user_id == user.id
end end
end end

View file

@ -0,0 +1,46 @@
defmodule Pleroma.Web.MastodonAPI.DirectoryControllerTest do
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
test "GET /api/v1/directory with :profile_directory disabled returns empty array", %{conn: conn} do
clear_config([:instance, :profile_directory], false)
insert(:user, is_discoverable: true)
insert(:user, is_discoverable: true)
result =
conn
|> get("/api/v1/directory")
|> json_response_and_validate_schema(200)
assert result == []
end
test "GET /api/v1/directory returns discoverable users only", %{conn: conn} do
%{id: user_id} = insert(:user, is_discoverable: true)
insert(:user, is_discoverable: false)
result =
conn
|> get("/api/v1/directory")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^user_id}] = result
end
test "GET /api/v1/directory returns users sorted by most recent statuses", %{conn: conn} do
insert(:user, is_discoverable: true)
%{id: user_id} = user = insert(:user, is_discoverable: true)
insert(:user, is_discoverable: true)
{:ok, _activity} = CommonAPI.post(user, %{status: "yay i'm discoverable"})
result =
conn
|> get("/api/v1/directory?order=active")
|> json_response_and_validate_schema(200)
assert [%{"id" => ^user_id} | _tail] = result
end
end

View file

@ -4,8 +4,11 @@
defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do
use Pleroma.Web.ConnCase, async: true use Pleroma.Web.ConnCase, async: true
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
setup do: oauth_access(["read"]) setup do: oauth_access(["read", "write"])
test "returns empty result", %{conn: conn} do test "returns empty result", %{conn: conn} do
res = res =
@ -15,4 +18,66 @@ test "returns empty result", %{conn: conn} do
assert res == [] assert res == []
end end
test "returns v2 suggestions", %{conn: conn} do
%{id: user_id} = insert(:user, is_suggested: true)
res =
conn
|> get("/api/v2/suggestions")
|> json_response_and_validate_schema(200)
assert [%{"source" => "staff", "account" => %{"id" => ^user_id}}] = res
end
test "returns v2 suggestions excluding dismissed accounts", %{conn: conn} do
%{id: user_id} = insert(:user, is_suggested: true)
conn
|> delete("/api/v1/suggestions/#{user_id}")
|> json_response_and_validate_schema(200)
res =
conn
|> get("/api/v2/suggestions")
|> json_response_and_validate_schema(200)
assert [] = res
end
test "returns v2 suggestions excluding blocked accounts", %{conn: conn, user: blocker} do
blocked = insert(:user, is_suggested: true)
{:ok, _} = CommonAPI.block(blocker, blocked)
res =
conn
|> get("/api/v2/suggestions")
|> json_response_and_validate_schema(200)
assert [] = res
end
test "returns v2 suggestions excluding followed accounts", %{conn: conn, user: follower} do
followed = insert(:user, is_suggested: true)
{:ok, _, _, _} = CommonAPI.follow(follower, followed)
res =
conn
|> get("/api/v2/suggestions")
|> json_response_and_validate_schema(200)
assert [] = res
end
test "dismiss suggestion", %{conn: conn, user: source} do
target = insert(:user, is_suggested: true)
res =
conn
|> delete("/api/v1/suggestions/#{target.id}")
|> json_response_and_validate_schema(200)
assert res == %{}
assert UserRelationship.exists?(:suggestion_dismiss, source, target)
end
end end

View file

@ -74,6 +74,7 @@ test "Represent a user account" do
fields: [] fields: []
}, },
fqn: "shp@shitposter.club", fqn: "shp@shitposter.club",
last_status_at: nil,
pleroma: %{ pleroma: %{
ap_id: user.ap_id, ap_id: user.ap_id,
also_known_as: ["https://shitposter.zone/users/shp"], also_known_as: ["https://shitposter.zone/users/shp"],
@ -83,6 +84,7 @@ test "Represent a user account" do
tags: [], tags: [],
is_admin: false, is_admin: false,
is_moderator: false, is_moderator: false,
is_suggested: false,
hide_favorites: true, hide_favorites: true,
hide_followers: false, hide_followers: false,
hide_follows: false, hide_follows: false,
@ -174,6 +176,7 @@ test "Represent a Service(bot) account" do
fields: [] fields: []
}, },
fqn: "shp@shitposter.club", fqn: "shp@shitposter.club",
last_status_at: nil,
pleroma: %{ pleroma: %{
ap_id: user.ap_id, ap_id: user.ap_id,
also_known_as: [], also_known_as: [],
@ -183,6 +186,7 @@ test "Represent a Service(bot) account" do
tags: [], tags: [],
is_admin: false, is_admin: false,
is_moderator: false, is_moderator: false,
is_suggested: false,
hide_favorites: true, hide_favorites: true,
hide_followers: false, hide_followers: false,
hide_follows: false, hide_follows: false,
@ -268,10 +272,12 @@ defp test_relationship_rendering(user, other_user, expected_result) do
muting: false, muting: false,
muting_notifications: false, muting_notifications: false,
subscribing: false, subscribing: false,
notifying: false,
requested: false, requested: false,
domain_blocking: false, domain_blocking: false,
showing_reblogs: true, showing_reblogs: true,
endorsed: false endorsed: false,
note: ""
} }
test "represent a relationship for the following and followed user" do test "represent a relationship for the following and followed user" do
@ -293,6 +299,7 @@ test "represent a relationship for the following and followed user" do
muting: true, muting: true,
muting_notifications: true, muting_notifications: true,
subscribing: true, subscribing: true,
notifying: true,
showing_reblogs: false, showing_reblogs: false,
id: to_string(other_user.id) id: to_string(other_user.id)
} }

View file

@ -0,0 +1,34 @@
# 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.SuggestionViewTest do
use Pleroma.DataCase, async: true
import Pleroma.Factory
alias Pleroma.Web.MastodonAPI.SuggestionView, as: View
test "show.json" do
user = insert(:user, is_suggested: true)
json = View.render("show.json", %{user: user, source: :staff, skip_visibility_check: true})
assert json.source == :staff
assert json.account.id == user.id
end
test "index.json" do
user1 = insert(:user, is_suggested: true)
user2 = insert(:user, is_suggested: true)
user3 = insert(:user, is_suggested: true)
[suggestion1, suggestion2, suggestion3] =
View.render("index.json", %{
users: [user1, user2, user3],
source: :staff,
skip_visibility_check: true
})
assert suggestion1.source == :staff
assert suggestion2.account.id == user2.id
assert suggestion3.account.url == user3.ap_id
end
end

View file

@ -41,4 +41,16 @@ test "has unique client_id" do
assert error.type == :unique assert error.type == :unique
end end
end end
test "get_user_apps/1" do
user = insert(:user)
apps = [
insert(:oauth_app, user_id: user.id),
insert(:oauth_app, user_id: user.id),
insert(:oauth_app, user_id: user.id)
]
assert App.get_user_apps(user) == apps
end
end end

View file

@ -343,4 +343,54 @@ test "does not require authentication on non-federating instances", %{
|> response(200) |> response(200)
end end
end end
describe "notice compatibility routes" do
test "Soapbox FE", %{conn: conn} do
user = insert(:user)
note_activity = insert(:note_activity, user: user)
resp =
conn
|> put_req_header("accept", "text/html")
|> get("/@#{user.nickname}/posts/#{note_activity.id}")
|> response(200)
expected =
"<meta content=\"#{Endpoint.url()}/notice/#{note_activity.id}\" property=\"og:url\">"
assert resp =~ expected
end
test "Mastodon", %{conn: conn} do
user = insert(:user)
note_activity = insert(:note_activity, user: user)
resp =
conn
|> put_req_header("accept", "text/html")
|> get("/@#{user.nickname}/#{note_activity.id}")
|> response(200)
expected =
"<meta content=\"#{Endpoint.url()}/notice/#{note_activity.id}\" property=\"og:url\">"
assert resp =~ expected
end
test "Twitter", %{conn: conn} do
user = insert(:user)
note_activity = insert(:note_activity, user: user)
resp =
conn
|> put_req_header("accept", "text/html")
|> get("/#{user.nickname}/status/#{note_activity.id}")
|> response(200)
expected =
"<meta content=\"#{Endpoint.url()}/notice/#{note_activity.id}\" property=\"og:url\">"
assert resp =~ expected
end
end
end end

View file

@ -0,0 +1,53 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.AppControllerTest do
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.Push
import Pleroma.Factory
test "apps", %{conn: conn} do
user = insert(:user)
app_attrs = build(:oauth_app)
creation =
conn
|> put_req_header("content-type", "application/json")
|> assign(:user, user)
|> post("/api/v1/apps", %{
client_name: app_attrs.client_name,
redirect_uris: app_attrs.redirect_uris
})
[app] = App.get_user_apps(user)
expected = %{
"name" => app.client_name,
"website" => app.website,
"client_id" => app.client_id,
"client_secret" => app.client_secret,
"id" => app.id |> to_string(),
"redirect_uri" => app.redirect_uris,
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
}
assert expected == json_response_and_validate_schema(creation, 200)
response =
conn
|> put_req_header("content-type", "application/json")
|> assign(:user, user)
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read", "follow"]))
|> get("/api/v1/pleroma/apps")
|> json_response_and_validate_schema(200)
[apps] = response
assert length(response) == 1
assert apps["client_id"] == app.client_id
end
end

View file

@ -0,0 +1,21 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.AppViewTest do
use Pleroma.DataCase, async: true
alias Pleroma.Web.PleromaAPI.AppView
import Pleroma.Factory
test "index.json" do
apps = [
insert(:oauth_app),
insert(:oauth_app),
insert(:oauth_app)
]
results = AppView.render("index.json", %{apps: apps})
assert [%{client_id: _, client_secret: _}, _, _] = results
end
end

View file

@ -0,0 +1,60 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureStaffPrivilegedPlugTest do
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug
import Pleroma.Factory
test "accepts a user that is an admin" do
user = insert(:user, is_admin: true)
conn = assign(build_conn(), :user, user)
ret_conn = EnsureStaffPrivilegedPlug.call(conn, %{})
assert conn == ret_conn
end
test "accepts a user that is a moderator when :privileged_staff is enabled" do
clear_config([:instance, :privileged_staff], true)
user = insert(:user, is_moderator: true)
conn = assign(build_conn(), :user, user)
ret_conn = EnsureStaffPrivilegedPlug.call(conn, %{})
assert conn == ret_conn
end
test "denies a user that is a moderator when :privileged_staff is disabled" do
clear_config([:instance, :privileged_staff], false)
user = insert(:user, is_moderator: true)
conn =
build_conn()
|> assign(:user, user)
|> EnsureStaffPrivilegedPlug.call(%{})
assert conn.status == 403
end
test "denies a user that isn't a staff member" do
user = insert(:user)
conn =
build_conn()
|> assign(:user, user)
|> EnsureStaffPrivilegedPlug.call(%{})
assert conn.status == 403
end
test "denies when a user isn't set" do
conn = EnsureStaffPrivilegedPlug.call(build_conn(), %{})
assert conn.status == 403
end
end

View file

@ -86,6 +86,8 @@ test "api routes are detected correctly" do
"objects", "objects",
"activities", "activities",
"notice", "notice",
"@:nickname",
":nickname",
"users", "users",
"tags", "tags",
"mailer", "mailer",
@ -94,8 +96,10 @@ test "api routes are detected correctly" do
"internal", "internal",
".well-known", ".well-known",
"nodeinfo", "nodeinfo",
"manifest.json",
"auth", "auth",
"proxy", "proxy",
"phoenix",
"test", "test",
"user_exists", "user_exists",
"check_password" "check_password"

View file

@ -48,6 +48,7 @@ test "it is enabled if remote_ip_found flag doesn't exist" do
refute RateLimiter.disabled?(build_conn()) refute RateLimiter.disabled?(build_conn())
end end
@tag :erratic
test "it restricts based on config values" do test "it restricts based on config values" do
limiter_name = :test_plug_opts limiter_name = :test_plug_opts
scale = 80 scale = 80
@ -137,6 +138,7 @@ test "it supports combination of options modifying bucket name" do
end end
describe "unauthenticated users" do describe "unauthenticated users" do
@tag :erratic
test "are restricted based on remote IP" do test "are restricted based on remote IP" do
limiter_name = :test_unauthenticated limiter_name = :test_unauthenticated
clear_config([:rate_limit, limiter_name], [{1000, 5}, {1, 10}]) clear_config([:rate_limit, limiter_name], [{1000, 5}, {1, 10}])
@ -174,6 +176,7 @@ test "are restricted based on remote IP" do
:ok :ok
end end
@tag :erratic
test "can have limits separate from unauthenticated connections" do test "can have limits separate from unauthenticated connections" do
limiter_name = :test_authenticated1 limiter_name = :test_authenticated1
@ -199,6 +202,7 @@ test "can have limits separate from unauthenticated connections" do
assert conn.halted assert conn.halted
end end
@tag :erratic
test "different users are counted independently" do test "different users are counted independently" do
limiter_name = :test_authenticated2 limiter_name = :test_authenticated2
clear_config([:rate_limit, limiter_name], [{1, 10}, {1000, 5}]) clear_config([:rate_limit, limiter_name], [{1, 10}, {1000, 5}])

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: [] os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: []
ExUnit.start(exclude: [:federated | os_exclude]) ExUnit.start(exclude: [:federated, :erratic] ++ os_exclude)
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)