Merge branch 'develop' into openapi/admin/relay
This commit is contained in:
commit
68cb152a08
@ -44,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Fix follower/blocks import when nicknames starts with @
|
||||
- Filtering of push notifications on activities from blocked domains
|
||||
- Resolving Peertube accounts with Webfinger
|
||||
- `blob:` urls not being allowed by connect-src CSP
|
||||
|
||||
## [Unreleased (patch)]
|
||||
|
||||
|
@ -22,8 +22,21 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
@max_concurrency 10
|
||||
|
||||
@visibility ~w(public private direct unlisted)
|
||||
@types ~w(simple emoji mentions hell_thread attachment tag like reblog simple_thread remote)
|
||||
@groups ~w(user friends non_friends)
|
||||
@types [
|
||||
:simple,
|
||||
:emoji,
|
||||
:mentions,
|
||||
:hell_thread,
|
||||
:attachment,
|
||||
:tag,
|
||||
:like,
|
||||
:reblog,
|
||||
:simple_thread
|
||||
]
|
||||
@groups [:friends_local, :friends_remote, :non_friends_local, :non_friends_local]
|
||||
@remote_groups [:friends_remote, :non_friends_remote]
|
||||
@friends_groups [:friends_local, :friends_remote]
|
||||
@non_friends_groups [:non_friends_local, :non_friends_remote]
|
||||
|
||||
@spec generate(User.t(), keyword()) :: :ok
|
||||
def generate(user, opts \\ []) do
|
||||
@ -34,33 +47,24 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
|
||||
opts = Keyword.merge(@defaults, opts)
|
||||
|
||||
friends =
|
||||
user
|
||||
|> Users.get_users(limit: opts[:friends_used], local: :local, friends?: true)
|
||||
|> Enum.shuffle()
|
||||
users = Users.prepare_users(user, opts)
|
||||
|
||||
non_friends =
|
||||
user
|
||||
|> Users.get_users(limit: opts[:non_friends_used], local: :local, friends?: false)
|
||||
|> Enum.shuffle()
|
||||
{:ok, _} = Agent.start_link(fn -> users[:non_friends_remote] end, name: :non_friends_remote)
|
||||
|
||||
task_data =
|
||||
for visibility <- @visibility,
|
||||
type <- @types,
|
||||
group <- @groups,
|
||||
group <- [:user | @groups],
|
||||
do: {visibility, type, group}
|
||||
|
||||
IO.puts("Starting generating #{opts[:iterations]} iterations of activities...")
|
||||
|
||||
friends_thread = Enum.take(friends, 5)
|
||||
non_friends_thread = Enum.take(friends, 5)
|
||||
|
||||
public_long_thread = fn ->
|
||||
generate_long_thread("public", user, friends_thread, non_friends_thread, opts)
|
||||
generate_long_thread("public", users, opts)
|
||||
end
|
||||
|
||||
private_long_thread = fn ->
|
||||
generate_long_thread("private", user, friends_thread, non_friends_thread, opts)
|
||||
generate_long_thread("private", users, opts)
|
||||
end
|
||||
|
||||
iterations = opts[:iterations]
|
||||
@ -73,10 +77,10 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
i when i == iterations - 2 ->
|
||||
spawn(public_long_thread)
|
||||
spawn(private_long_thread)
|
||||
generate_activities(user, friends, non_friends, Enum.shuffle(task_data), opts)
|
||||
generate_activities(users, Enum.shuffle(task_data), opts)
|
||||
|
||||
_ ->
|
||||
generate_activities(user, friends, non_friends, Enum.shuffle(task_data), opts)
|
||||
generate_activities(users, Enum.shuffle(task_data), opts)
|
||||
end
|
||||
)
|
||||
end)
|
||||
@ -127,16 +131,16 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
end)
|
||||
end
|
||||
|
||||
defp generate_long_thread(visibility, user, friends, non_friends, _opts) do
|
||||
defp generate_long_thread(visibility, users, _opts) do
|
||||
group =
|
||||
if visibility == "public",
|
||||
do: "friends",
|
||||
else: "user"
|
||||
do: :friends_local,
|
||||
else: :user
|
||||
|
||||
tasks = get_reply_tasks(visibility, group) |> Stream.cycle() |> Enum.take(50)
|
||||
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(user, %{
|
||||
CommonAPI.post(users[:user], %{
|
||||
status: "Start of #{visibility} long thread",
|
||||
visibility: visibility
|
||||
})
|
||||
@ -150,31 +154,28 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
Map.put(state, key, activity)
|
||||
end)
|
||||
|
||||
acc = {activity.id, ["@" <> user.nickname, "reply to long thread"]}
|
||||
insert_replies_for_long_thread(tasks, visibility, user, friends, non_friends, acc)
|
||||
acc = {activity.id, ["@" <> users[:user].nickname, "reply to long thread"]}
|
||||
insert_replies_for_long_thread(tasks, visibility, users, acc)
|
||||
IO.puts("Generating #{visibility} long thread ended\n")
|
||||
end
|
||||
|
||||
defp insert_replies_for_long_thread(tasks, visibility, user, friends, non_friends, acc) do
|
||||
defp insert_replies_for_long_thread(tasks, visibility, users, acc) do
|
||||
Enum.reduce(tasks, acc, fn
|
||||
"friend", {id, data} ->
|
||||
friend = Enum.random(friends)
|
||||
insert_reply(friend, List.delete(data, "@" <> friend.nickname), id, visibility)
|
||||
|
||||
"non_friend", {id, data} ->
|
||||
non_friend = Enum.random(non_friends)
|
||||
insert_reply(non_friend, List.delete(data, "@" <> non_friend.nickname), id, visibility)
|
||||
|
||||
"user", {id, data} ->
|
||||
:user, {id, data} ->
|
||||
user = users[:user]
|
||||
insert_reply(user, List.delete(data, "@" <> user.nickname), id, visibility)
|
||||
|
||||
group, {id, data} ->
|
||||
replier = Enum.random(users[group])
|
||||
insert_reply(replier, List.delete(data, "@" <> replier.nickname), id, visibility)
|
||||
end)
|
||||
end
|
||||
|
||||
defp generate_activities(user, friends, non_friends, task_data, opts) do
|
||||
defp generate_activities(users, task_data, opts) do
|
||||
Task.async_stream(
|
||||
task_data,
|
||||
fn {visibility, type, group} ->
|
||||
insert_activity(type, visibility, group, user, friends, non_friends, opts)
|
||||
insert_activity(type, visibility, group, users, opts)
|
||||
end,
|
||||
max_concurrency: @max_concurrency,
|
||||
timeout: 30_000
|
||||
@ -182,67 +183,104 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
defp insert_activity("simple", visibility, group, user, friends, non_friends, _opts) do
|
||||
{:ok, _activity} =
|
||||
defp insert_local_activity(visibility, group, users, status) do
|
||||
{:ok, _} =
|
||||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{status: "Simple status", visibility: visibility})
|
||||
|> get_actor(users)
|
||||
|> CommonAPI.post(%{status: status, visibility: visibility})
|
||||
end
|
||||
|
||||
defp insert_activity("emoji", visibility, group, user, friends, non_friends, _opts) do
|
||||
{:ok, _activity} =
|
||||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{
|
||||
status: "Simple status with emoji :firefox:",
|
||||
visibility: visibility
|
||||
})
|
||||
defp insert_remote_activity(visibility, group, users, status) do
|
||||
actor = get_actor(group, users)
|
||||
{act_data, obj_data} = prepare_activity_data(actor, visibility, users[:user])
|
||||
{activity_data, object_data} = other_data(actor, status)
|
||||
|
||||
activity_data
|
||||
|> Map.merge(act_data)
|
||||
|> Map.put("object", Map.merge(object_data, obj_data))
|
||||
|> Pleroma.Web.ActivityPub.ActivityPub.insert(false)
|
||||
end
|
||||
|
||||
defp insert_activity("mentions", visibility, group, user, friends, non_friends, _opts) do
|
||||
defp user_mentions(users) do
|
||||
user_mentions =
|
||||
get_random_mentions(friends, Enum.random(0..3)) ++
|
||||
get_random_mentions(non_friends, Enum.random(0..3))
|
||||
Enum.reduce(
|
||||
@groups,
|
||||
[],
|
||||
fn group, acc ->
|
||||
acc ++ get_random_mentions(users[group], Enum.random(0..2))
|
||||
end
|
||||
)
|
||||
|
||||
user_mentions =
|
||||
if Enum.random([true, false]),
|
||||
do: ["@" <> user.nickname | user_mentions],
|
||||
else: user_mentions
|
||||
|
||||
{:ok, _activity} =
|
||||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{
|
||||
status: Enum.join(user_mentions, ", ") <> " simple status with mentions",
|
||||
visibility: visibility
|
||||
})
|
||||
if Enum.random([true, false]),
|
||||
do: ["@" <> users[:user].nickname | user_mentions],
|
||||
else: user_mentions
|
||||
end
|
||||
|
||||
defp insert_activity("hell_thread", visibility, group, user, friends, non_friends, _opts) do
|
||||
mentions =
|
||||
with {:ok, nil} <- Cachex.get(:user_cache, "hell_thread_mentions") do
|
||||
cached =
|
||||
([user | Enum.take(friends, 10)] ++ Enum.take(non_friends, 10))
|
||||
|> Enum.map(&"@#{&1.nickname}")
|
||||
|> Enum.join(", ")
|
||||
defp hell_thread_mentions(users) do
|
||||
with {:ok, nil} <- Cachex.get(:user_cache, "hell_thread_mentions") do
|
||||
cached =
|
||||
@groups
|
||||
|> Enum.reduce([users[:user]], fn group, acc ->
|
||||
acc ++ Enum.take(users[group], 5)
|
||||
end)
|
||||
|> Enum.map(&"@#{&1.nickname}")
|
||||
|> Enum.join(", ")
|
||||
|
||||
Cachex.put(:user_cache, "hell_thread_mentions", cached)
|
||||
cached
|
||||
else
|
||||
{:ok, cached} -> cached
|
||||
end
|
||||
|
||||
{:ok, _activity} =
|
||||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{
|
||||
status: mentions <> " hell thread status",
|
||||
visibility: visibility
|
||||
})
|
||||
Cachex.put(:user_cache, "hell_thread_mentions", cached)
|
||||
cached
|
||||
else
|
||||
{:ok, cached} -> cached
|
||||
end
|
||||
end
|
||||
|
||||
defp insert_activity("attachment", visibility, group, user, friends, non_friends, _opts) do
|
||||
actor = get_actor(group, user, friends, non_friends)
|
||||
defp insert_activity(:simple, visibility, group, users, _opts)
|
||||
when group in @remote_groups do
|
||||
insert_remote_activity(visibility, group, users, "Remote status")
|
||||
end
|
||||
|
||||
defp insert_activity(:simple, visibility, group, users, _opts) do
|
||||
insert_local_activity(visibility, group, users, "Simple status")
|
||||
end
|
||||
|
||||
defp insert_activity(:emoji, visibility, group, users, _opts)
|
||||
when group in @remote_groups do
|
||||
insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:")
|
||||
end
|
||||
|
||||
defp insert_activity(:emoji, visibility, group, users, _opts) do
|
||||
insert_local_activity(visibility, group, users, "Simple status with emoji :firefox:")
|
||||
end
|
||||
|
||||
defp insert_activity(:mentions, visibility, group, users, _opts)
|
||||
when group in @remote_groups do
|
||||
mentions = user_mentions(users)
|
||||
|
||||
status = Enum.join(mentions, ", ") <> " remote status with mentions"
|
||||
|
||||
insert_remote_activity(visibility, group, users, status)
|
||||
end
|
||||
|
||||
defp insert_activity(:mentions, visibility, group, users, _opts) do
|
||||
mentions = user_mentions(users)
|
||||
|
||||
status = Enum.join(mentions, ", ") <> " simple status with mentions"
|
||||
insert_remote_activity(visibility, group, users, status)
|
||||
end
|
||||
|
||||
defp insert_activity(:hell_thread, visibility, group, users, _)
|
||||
when group in @remote_groups do
|
||||
mentions = hell_thread_mentions(users)
|
||||
insert_remote_activity(visibility, group, users, mentions <> " remote hell thread status")
|
||||
end
|
||||
|
||||
defp insert_activity(:hell_thread, visibility, group, users, _opts) do
|
||||
mentions = hell_thread_mentions(users)
|
||||
|
||||
insert_local_activity(visibility, group, users, mentions <> " hell thread status")
|
||||
end
|
||||
|
||||
defp insert_activity(:attachment, visibility, group, users, _opts) do
|
||||
actor = get_actor(group, users)
|
||||
|
||||
obj_data = %{
|
||||
"actor" => actor.ap_id,
|
||||
@ -268,67 +306,54 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
})
|
||||
end
|
||||
|
||||
defp insert_activity("tag", visibility, group, user, friends, non_friends, _opts) do
|
||||
{:ok, _activity} =
|
||||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{status: "Status with #tag", visibility: visibility})
|
||||
defp insert_activity(:tag, visibility, group, users, _opts) do
|
||||
insert_local_activity(visibility, group, users, "Status with #tag")
|
||||
end
|
||||
|
||||
defp insert_activity("like", visibility, group, user, friends, non_friends, opts) do
|
||||
actor = get_actor(group, user, friends, non_friends)
|
||||
defp insert_activity(:like, visibility, group, users, opts) do
|
||||
actor = get_actor(group, users)
|
||||
|
||||
with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(),
|
||||
{:ok, _activity} <- CommonAPI.favorite(actor, activity_id) do
|
||||
:ok
|
||||
else
|
||||
{:error, _} ->
|
||||
insert_activity("like", visibility, group, user, friends, non_friends, opts)
|
||||
insert_activity(:like, visibility, group, users, opts)
|
||||
|
||||
nil ->
|
||||
Process.sleep(15)
|
||||
insert_activity("like", visibility, group, user, friends, non_friends, opts)
|
||||
insert_activity(:like, visibility, group, users, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp insert_activity("reblog", visibility, group, user, friends, non_friends, opts) do
|
||||
actor = get_actor(group, user, friends, non_friends)
|
||||
defp insert_activity(:reblog, visibility, group, users, opts) do
|
||||
actor = get_actor(group, users)
|
||||
|
||||
with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(),
|
||||
{:ok, _activity, _object} <- CommonAPI.repeat(activity_id, actor) do
|
||||
{:ok, _activity} <- CommonAPI.repeat(activity_id, actor) do
|
||||
:ok
|
||||
else
|
||||
{:error, _} ->
|
||||
insert_activity("reblog", visibility, group, user, friends, non_friends, opts)
|
||||
insert_activity(:reblog, visibility, group, users, opts)
|
||||
|
||||
nil ->
|
||||
Process.sleep(15)
|
||||
insert_activity("reblog", visibility, group, user, friends, non_friends, opts)
|
||||
insert_activity(:reblog, visibility, group, users, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp insert_activity("simple_thread", visibility, group, user, friends, non_friends, _opts)
|
||||
when visibility in ["public", "unlisted", "private"] do
|
||||
actor = get_actor(group, user, friends, non_friends)
|
||||
tasks = get_reply_tasks(visibility, group)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{status: "Simple status", visibility: visibility})
|
||||
|
||||
acc = {activity.id, ["@" <> actor.nickname, "reply to status"]}
|
||||
insert_replies(tasks, visibility, user, friends, non_friends, acc)
|
||||
end
|
||||
|
||||
defp insert_activity("simple_thread", "direct", group, user, friends, non_friends, _opts) do
|
||||
actor = get_actor(group, user, friends, non_friends)
|
||||
defp insert_activity(:simple_thread, "direct", group, users, _opts) do
|
||||
actor = get_actor(group, users)
|
||||
tasks = get_reply_tasks("direct", group)
|
||||
|
||||
list =
|
||||
case group do
|
||||
"non_friends" ->
|
||||
Enum.take(non_friends, 3)
|
||||
:user ->
|
||||
group = Enum.random(@friends_groups)
|
||||
Enum.take(users[group], 3)
|
||||
|
||||
_ ->
|
||||
Enum.take(friends, 3)
|
||||
Enum.take(users[group], 3)
|
||||
end
|
||||
|
||||
data = Enum.map(list, &("@" <> &1.nickname))
|
||||
@ -339,40 +364,30 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
visibility: "direct"
|
||||
})
|
||||
|
||||
acc = {activity.id, ["@" <> user.nickname | data] ++ ["reply to status"]}
|
||||
insert_direct_replies(tasks, user, list, acc)
|
||||
acc = {activity.id, ["@" <> users[:user].nickname | data] ++ ["reply to status"]}
|
||||
insert_direct_replies(tasks, users[:user], list, acc)
|
||||
end
|
||||
|
||||
defp insert_activity("remote", _, "user", _, _, _, _), do: :ok
|
||||
defp insert_activity(:simple_thread, visibility, group, users, _opts) do
|
||||
actor = get_actor(group, users)
|
||||
tasks = get_reply_tasks(visibility, group)
|
||||
|
||||
defp insert_activity("remote", visibility, group, user, _friends, _non_friends, opts) do
|
||||
remote_friends =
|
||||
Users.get_users(user, limit: opts[:friends_used], local: :external, friends?: true)
|
||||
{:ok, activity} =
|
||||
CommonAPI.post(users[:user], %{status: "Simple status", visibility: visibility})
|
||||
|
||||
remote_non_friends =
|
||||
Users.get_users(user, limit: opts[:non_friends_used], local: :external, friends?: false)
|
||||
|
||||
actor = get_actor(group, user, remote_friends, remote_non_friends)
|
||||
|
||||
{act_data, obj_data} = prepare_activity_data(actor, visibility, user)
|
||||
{activity_data, object_data} = other_data(actor)
|
||||
|
||||
activity_data
|
||||
|> Map.merge(act_data)
|
||||
|> Map.put("object", Map.merge(object_data, obj_data))
|
||||
|> Pleroma.Web.ActivityPub.ActivityPub.insert(false)
|
||||
acc = {activity.id, ["@" <> actor.nickname, "reply to status"]}
|
||||
insert_replies(tasks, visibility, users, acc)
|
||||
end
|
||||
|
||||
defp get_actor("user", user, _friends, _non_friends), do: user
|
||||
defp get_actor("friends", _user, friends, _non_friends), do: Enum.random(friends)
|
||||
defp get_actor("non_friends", _user, _friends, non_friends), do: Enum.random(non_friends)
|
||||
defp get_actor(:user, %{user: user}), do: user
|
||||
defp get_actor(group, users), do: Enum.random(users[group])
|
||||
|
||||
defp other_data(actor) do
|
||||
defp other_data(actor, content) do
|
||||
%{host: host} = URI.parse(actor.ap_id)
|
||||
datetime = DateTime.utc_now()
|
||||
context_id = "http://#{host}:4000/contexts/#{UUID.generate()}"
|
||||
activity_id = "http://#{host}:4000/activities/#{UUID.generate()}"
|
||||
object_id = "http://#{host}:4000/objects/#{UUID.generate()}"
|
||||
context_id = "https://#{host}/contexts/#{UUID.generate()}"
|
||||
activity_id = "https://#{host}/activities/#{UUID.generate()}"
|
||||
object_id = "https://#{host}/objects/#{UUID.generate()}"
|
||||
|
||||
activity_data = %{
|
||||
"actor" => actor.ap_id,
|
||||
@ -389,7 +404,7 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
"attributedTo" => actor.ap_id,
|
||||
"bcc" => [],
|
||||
"bto" => [],
|
||||
"content" => "Remote post",
|
||||
"content" => content,
|
||||
"context" => context_id,
|
||||
"conversation" => context_id,
|
||||
"emoji" => %{},
|
||||
@ -475,51 +490,65 @@ defmodule Pleroma.LoadTesting.Activities do
|
||||
{act_data, obj_data}
|
||||
end
|
||||
|
||||
defp get_reply_tasks("public", "user"), do: ~w(friend non_friend user)
|
||||
defp get_reply_tasks("public", "friends"), do: ~w(non_friend user friend)
|
||||
defp get_reply_tasks("public", "non_friends"), do: ~w(user friend non_friend)
|
||||
defp get_reply_tasks("public", :user) do
|
||||
[:friends_local, :friends_remote, :non_friends_local, :non_friends_remote, :user]
|
||||
end
|
||||
|
||||
defp get_reply_tasks(visibility, "user") when visibility in ["unlisted", "private"],
|
||||
do: ~w(friend user friend)
|
||||
defp get_reply_tasks("public", group) when group in @friends_groups do
|
||||
[:non_friends_local, :non_friends_remote, :user, :friends_local, :friends_remote]
|
||||
end
|
||||
|
||||
defp get_reply_tasks(visibility, "friends") when visibility in ["unlisted", "private"],
|
||||
do: ~w(user friend user)
|
||||
defp get_reply_tasks("public", group) when group in @non_friends_groups do
|
||||
[:user, :friends_local, :friends_remote, :non_friends_local, :non_friends_remote]
|
||||
end
|
||||
|
||||
defp get_reply_tasks(visibility, "non_friends") when visibility in ["unlisted", "private"],
|
||||
do: []
|
||||
defp get_reply_tasks(visibility, :user) when visibility in ["unlisted", "private"] do
|
||||
[:friends_local, :friends_remote, :user, :friends_local, :friends_remote]
|
||||
end
|
||||
|
||||
defp get_reply_tasks("direct", "user"), do: ~w(friend user friend)
|
||||
defp get_reply_tasks("direct", "friends"), do: ~w(user friend user)
|
||||
defp get_reply_tasks("direct", "non_friends"), do: ~w(user non_friend user)
|
||||
defp get_reply_tasks(visibility, group)
|
||||
when visibility in ["unlisted", "private"] and group in @friends_groups do
|
||||
[:user, :friends_remote, :friends_local, :user]
|
||||
end
|
||||
|
||||
defp insert_replies(tasks, visibility, user, friends, non_friends, acc) do
|
||||
defp get_reply_tasks(visibility, group)
|
||||
when visibility in ["unlisted", "private"] and
|
||||
group in @non_friends_groups,
|
||||
do: []
|
||||
|
||||
defp get_reply_tasks("direct", :user), do: [:friends_local, :user, :friends_remote]
|
||||
|
||||
defp get_reply_tasks("direct", group) when group in @friends_groups,
|
||||
do: [:user, group, :user]
|
||||
|
||||
defp get_reply_tasks("direct", group) when group in @non_friends_groups do
|
||||
[:user, :non_friends_remote, :user, :non_friends_local]
|
||||
end
|
||||
|
||||
defp insert_replies(tasks, visibility, users, acc) do
|
||||
Enum.reduce(tasks, acc, fn
|
||||
"friend", {id, data} ->
|
||||
friend = Enum.random(friends)
|
||||
insert_reply(friend, data, id, visibility)
|
||||
:user, {id, data} ->
|
||||
insert_reply(users[:user], data, id, visibility)
|
||||
|
||||
"non_friend", {id, data} ->
|
||||
non_friend = Enum.random(non_friends)
|
||||
insert_reply(non_friend, data, id, visibility)
|
||||
|
||||
"user", {id, data} ->
|
||||
insert_reply(user, data, id, visibility)
|
||||
group, {id, data} ->
|
||||
replier = Enum.random(users[group])
|
||||
insert_reply(replier, data, id, visibility)
|
||||
end)
|
||||
end
|
||||
|
||||
defp insert_direct_replies(tasks, user, list, acc) do
|
||||
Enum.reduce(tasks, acc, fn
|
||||
group, {id, data} when group in ["friend", "non_friend"] ->
|
||||
:user, {id, data} ->
|
||||
{reply_id, _} = insert_reply(user, List.delete(data, "@" <> user.nickname), id, "direct")
|
||||
{reply_id, data}
|
||||
|
||||
_, {id, data} ->
|
||||
actor = Enum.random(list)
|
||||
|
||||
{reply_id, _} =
|
||||
insert_reply(actor, List.delete(data, "@" <> actor.nickname), id, "direct")
|
||||
|
||||
{reply_id, data}
|
||||
|
||||
"user", {id, data} ->
|
||||
{reply_id, _} = insert_reply(user, List.delete(data, "@" <> user.nickname), id, "direct")
|
||||
{reply_id, data}
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -36,6 +36,7 @@ defmodule Pleroma.LoadTesting.Fetcher do
|
||||
fetch_home_timeline(user)
|
||||
fetch_direct_timeline(user)
|
||||
fetch_public_timeline(user)
|
||||
fetch_public_timeline(user, :with_blocks)
|
||||
fetch_public_timeline(user, :local)
|
||||
fetch_public_timeline(user, :tag)
|
||||
fetch_notifications(user)
|
||||
@ -227,6 +228,58 @@ defmodule Pleroma.LoadTesting.Fetcher do
|
||||
fetch_public_timeline(opts, "public timeline only media")
|
||||
end
|
||||
|
||||
defp fetch_public_timeline(user, :with_blocks) do
|
||||
opts = opts_for_public_timeline(user)
|
||||
|
||||
remote_non_friends = Agent.get(:non_friends_remote, & &1)
|
||||
|
||||
Benchee.run(%{
|
||||
"public timeline without blocks" => fn ->
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
end
|
||||
})
|
||||
|
||||
Enum.each(remote_non_friends, fn non_friend ->
|
||||
{:ok, _} = User.block(user, non_friend)
|
||||
end)
|
||||
|
||||
user = User.get_by_id(user.id)
|
||||
|
||||
opts = Map.put(opts, "blocking_user", user)
|
||||
|
||||
Benchee.run(
|
||||
%{
|
||||
"public timeline with user block" => fn ->
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
end
|
||||
},
|
||||
)
|
||||
|
||||
domains =
|
||||
Enum.reduce(remote_non_friends, [], fn non_friend, domains ->
|
||||
{:ok, _user} = User.unblock(user, non_friend)
|
||||
%{host: host} = URI.parse(non_friend.ap_id)
|
||||
[host | domains]
|
||||
end)
|
||||
|
||||
domains = Enum.uniq(domains)
|
||||
|
||||
Enum.each(domains, fn domain ->
|
||||
{:ok, _} = User.block_domain(user, domain)
|
||||
end)
|
||||
|
||||
user = User.get_by_id(user.id)
|
||||
opts = Map.put(opts, "blocking_user", user)
|
||||
|
||||
Benchee.run(
|
||||
%{
|
||||
"public timeline with domain block" => fn opts ->
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
end
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
defp fetch_public_timeline(opts, title) when is_binary(title) do
|
||||
first_page_last = ActivityPub.fetch_public_activities(opts) |> List.last()
|
||||
|
||||
|
@ -27,7 +27,7 @@ defmodule Pleroma.LoadTesting.Users do
|
||||
|
||||
make_friends(main_user, opts[:friends])
|
||||
|
||||
Repo.get(User, main_user.id)
|
||||
User.get_by_id(main_user.id)
|
||||
end
|
||||
|
||||
def generate_users(max) do
|
||||
@ -166,4 +166,24 @@ defmodule Pleroma.LoadTesting.Users do
|
||||
)
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
@spec prepare_users(User.t(), keyword()) :: map()
|
||||
def prepare_users(user, opts) do
|
||||
friends_limit = opts[:friends_used]
|
||||
non_friends_limit = opts[:non_friends_used]
|
||||
|
||||
%{
|
||||
user: user,
|
||||
friends_local: fetch_users(user, friends_limit, :local, true),
|
||||
friends_remote: fetch_users(user, friends_limit, :external, true),
|
||||
non_friends_local: fetch_users(user, non_friends_limit, :local, false),
|
||||
non_friends_remote: fetch_users(user, non_friends_limit, :external, false)
|
||||
}
|
||||
end
|
||||
|
||||
defp fetch_users(user, limit, local, friends?) do
|
||||
user
|
||||
|> get_users(limit: limit, local: local, friends?: friends?)
|
||||
|> Enum.shuffle()
|
||||
end
|
||||
end
|
||||
|
@ -5,7 +5,6 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.MastodonAPI.TimelineController
|
||||
|
||||
def run(_args) do
|
||||
Mix.Pleroma.start_pleroma()
|
||||
@ -37,7 +36,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
|
||||
Benchee.run(
|
||||
%{
|
||||
"Hashtag fetching, any" => fn tags ->
|
||||
TimelineController.hashtag_fetching(
|
||||
hashtag_fetching(
|
||||
%{
|
||||
"any" => tags
|
||||
},
|
||||
@ -47,7 +46,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
|
||||
end,
|
||||
# Will always return zero results because no overlapping hashtags are generated.
|
||||
"Hashtag fetching, all" => fn tags ->
|
||||
TimelineController.hashtag_fetching(
|
||||
hashtag_fetching(
|
||||
%{
|
||||
"all" => tags
|
||||
},
|
||||
@ -67,7 +66,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
|
||||
Benchee.run(
|
||||
%{
|
||||
"Hashtag fetching" => fn tag ->
|
||||
TimelineController.hashtag_fetching(
|
||||
hashtag_fetching(
|
||||
%{
|
||||
"tag" => tag
|
||||
},
|
||||
@ -80,4 +79,35 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
|
||||
time: 5
|
||||
)
|
||||
end
|
||||
|
||||
defp hashtag_fetching(params, user, local_only) do
|
||||
tags =
|
||||
[params["tag"], params["any"]]
|
||||
|> List.flatten()
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(&String.downcase(&1))
|
||||
|
||||
tag_all =
|
||||
params
|
||||
|> Map.get("all", [])
|
||||
|> Enum.map(&String.downcase(&1))
|
||||
|
||||
tag_reject =
|
||||
params
|
||||
|> Map.get("none", [])
|
||||
|> Enum.map(&String.downcase(&1))
|
||||
|
||||
_activities =
|
||||
params
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("local_only", local_only)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("tag", tags)
|
||||
|> Map.put("tag_all", tag_all)
|
||||
|> Map.put("tag_reject", tag_reject)
|
||||
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
|
||||
end
|
||||
end
|
||||
|
@ -171,7 +171,8 @@ config :mime, :types, %{
|
||||
"application/ld+json" => ["activity+json"]
|
||||
}
|
||||
|
||||
config :tesla, adapter: Tesla.Adapter.Gun
|
||||
config :tesla, adapter: Tesla.Adapter.Hackney
|
||||
|
||||
# Configures http settings, upstream proxy etc.
|
||||
config :pleroma, :http,
|
||||
proxy_url: nil,
|
||||
@ -183,7 +184,7 @@ config :pleroma, :instance,
|
||||
name: "Pleroma",
|
||||
email: "example@example.com",
|
||||
notify_email: "noreply@example.com",
|
||||
description: "A Pleroma instance, an alternative fediverse server",
|
||||
description: "Pleroma: An efficient and flexible fediverse server",
|
||||
background_image: "/images/city.jpg",
|
||||
limit: 5_000,
|
||||
chat_limit: 5_000,
|
||||
@ -274,7 +275,7 @@ config :pleroma, :markup,
|
||||
config :pleroma, :frontend_configurations,
|
||||
pleroma_fe: %{
|
||||
alwaysShowSubjectInput: true,
|
||||
background: "/static/aurora_borealis.jpg",
|
||||
background: "/images/city.jpg",
|
||||
collapseMessageWithSubject: false,
|
||||
disableChat: false,
|
||||
greentext: false,
|
||||
|
@ -511,7 +511,23 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||
- `discoverable`
|
||||
- `actor_type`
|
||||
|
||||
- Response: none (code `200`)
|
||||
- Response:
|
||||
|
||||
```json
|
||||
{"status": "success"}
|
||||
```
|
||||
|
||||
```json
|
||||
{"errors":
|
||||
{"actor_type": "is invalid"},
|
||||
{"email": "has invalid format"},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{"error": "Unable to update user."}
|
||||
```
|
||||
|
||||
## `GET /api/pleroma/admin/reports`
|
||||
|
||||
|
@ -6,10 +6,6 @@ A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma
|
||||
|
||||
Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings
|
||||
|
||||
## Attachment cap
|
||||
|
||||
Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting.
|
||||
|
||||
## Timelines
|
||||
|
||||
Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.
|
||||
@ -32,12 +28,20 @@ Has these additional fields under the `pleroma` object:
|
||||
- `thread_muted`: true if the thread the post belongs to is muted
|
||||
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
||||
|
||||
## Attachments
|
||||
## Media Attachments
|
||||
|
||||
Has these additional fields under the `pleroma` object:
|
||||
|
||||
- `mime_type`: mime type of the attachment.
|
||||
|
||||
### Attachment cap
|
||||
|
||||
Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting.
|
||||
|
||||
### Limitations
|
||||
|
||||
Pleroma does not process remote images and therefore cannot include fields such as `meta` and `blurhash`. It does not support focal points or aspect ratios. The frontend is expected to handle it.
|
||||
|
||||
## Accounts
|
||||
|
||||
The `id` parameter can also be the `nickname` of the user. This only works in these endpoints, not the deeper nested ones for following etc.
|
||||
|
@ -69,3 +69,32 @@ mix pleroma.database update_users_following_followers_counts
|
||||
```sh tab="From Source"
|
||||
mix pleroma.database fix_likes_collections
|
||||
```
|
||||
|
||||
## Vacuum the database
|
||||
|
||||
### Analyze
|
||||
|
||||
Running an `analyze` vacuum job can improve performance by updating statistics used by the query planner. **It is safe to cancel this.**
|
||||
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl database vacuum analyze
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.database vacuum analyze
|
||||
```
|
||||
|
||||
### Full
|
||||
|
||||
Running a `full` vacuum job rebuilds your entire database by reading all of the data and rewriting it into smaller
|
||||
and more compact files with an optimized layout. This process will take a long time and use additional disk space as
|
||||
it builds the files side-by-side the existing database files. It can make your database faster and use less disk space,
|
||||
but should only be run if necessary. **It is safe to cancel this.**
|
||||
|
||||
```sh tab="OTP"
|
||||
./bin/pleroma_ctl database vacuum full
|
||||
```
|
||||
|
||||
```sh tab="From Source"
|
||||
mix pleroma.database vacuum full
|
||||
```
|
31
docs/configuration/postgresql.md
Normal file
31
docs/configuration/postgresql.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Optimizing your PostgreSQL performance
|
||||
|
||||
Pleroma performance depends to a large extent on good database performance. The default PostgreSQL settings are mostly fine, but often you can get better performance by changing a few settings.
|
||||
|
||||
You can use [PGTune](https://pgtune.leopard.in.ua) to get recommendations for your setup. If you do, set the "Number of Connections" field to 20, as Pleroma will only use 10 concurrent connections anyway. If you don't, it will give you advice that might even hurt your performance.
|
||||
|
||||
We also recommend not using the "Network Storage" option.
|
||||
|
||||
## Example configurations
|
||||
|
||||
Here are some configuration suggestions for PostgreSQL 10+.
|
||||
|
||||
### 1GB RAM, 1 CPU
|
||||
```
|
||||
shared_buffers = 256MB
|
||||
effective_cache_size = 768MB
|
||||
maintenance_work_mem = 64MB
|
||||
work_mem = 13107kB
|
||||
```
|
||||
|
||||
### 2GB RAM, 2 CPU
|
||||
```
|
||||
shared_buffers = 512MB
|
||||
effective_cache_size = 1536MB
|
||||
maintenance_work_mem = 128MB
|
||||
work_mem = 26214kB
|
||||
max_worker_processes = 2
|
||||
max_parallel_workers_per_gather = 1
|
||||
max_parallel_workers = 2
|
||||
```
|
||||
|
@ -38,8 +38,8 @@ sudo apt install git build-essential postgresql postgresql-contrib
|
||||
* Download and add the Erlang repository:
|
||||
|
||||
```shell
|
||||
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
|
||||
sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
|
||||
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
|
||||
sudo dpkg -i /tmp/erlang-solutions_2.0_all.deb
|
||||
```
|
||||
|
||||
* Install Elixir and Erlang:
|
||||
|
@ -40,8 +40,8 @@ sudo apt install git build-essential postgresql postgresql-contrib
|
||||
|
||||
* Erlangのリポジトリをダウンロードおよびインストールします。
|
||||
```
|
||||
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
|
||||
sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
|
||||
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
|
||||
sudo dpkg -i /tmp/erlang-solutions_2.0_all.deb
|
||||
```
|
||||
|
||||
* ElixirとErlangをインストールします、
|
||||
|
@ -63,7 +63,7 @@ apt install postgresql-11-rum
|
||||
```
|
||||
|
||||
#### (Optional) Performance configuration
|
||||
For optimal performance, you may use [PGTune](https://pgtune.leopard.in.ua), don't forget to restart postgresql after editing the configuration
|
||||
It is encouraged to check [Optimizing your PostgreSQL performance](../configuration/postgresql.md) document, for tips on PostgreSQL tuning.
|
||||
|
||||
```sh tab="Alpine"
|
||||
rc-service postgresql restart
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.Database do
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Maintenance
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
@ -34,13 +35,7 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||
)
|
||||
|
||||
if Keyword.get(options, :vacuum) do
|
||||
Logger.info("Runnning VACUUM FULL")
|
||||
|
||||
Repo.query!(
|
||||
"vacuum full;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
Maintenance.vacuum("full")
|
||||
end
|
||||
end
|
||||
|
||||
@ -94,13 +89,7 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||
|> Repo.delete_all(timeout: :infinity)
|
||||
|
||||
if Keyword.get(options, :vacuum) do
|
||||
Logger.info("Runnning VACUUM FULL")
|
||||
|
||||
Repo.query!(
|
||||
"vacuum full;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
Maintenance.vacuum("full")
|
||||
end
|
||||
end
|
||||
|
||||
@ -135,4 +124,10 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||
end)
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
def run(["vacuum", args]) do
|
||||
start_pleroma()
|
||||
|
||||
Maintenance.vacuum(args)
|
||||
end
|
||||
end
|
||||
|
@ -15,7 +15,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||
{options, [], []} = parse_global_opts(args)
|
||||
|
||||
url_or_path = options[:manifest] || default_manifest()
|
||||
manifest = fetch_manifest(url_or_path)
|
||||
manifest = fetch_and_decode(url_or_path)
|
||||
|
||||
Enum.each(manifest, fn {name, info} ->
|
||||
to_print = [
|
||||
@ -42,12 +42,12 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||
|
||||
url_or_path = options[:manifest] || default_manifest()
|
||||
|
||||
manifest = fetch_manifest(url_or_path)
|
||||
manifest = fetch_and_decode(url_or_path)
|
||||
|
||||
for pack_name <- pack_names do
|
||||
if Map.has_key?(manifest, pack_name) do
|
||||
pack = manifest[pack_name]
|
||||
src_url = pack["src"]
|
||||
src = pack["src"]
|
||||
|
||||
IO.puts(
|
||||
IO.ANSI.format([
|
||||
@ -57,11 +57,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||
:normal,
|
||||
" from ",
|
||||
:underline,
|
||||
src_url
|
||||
src
|
||||
])
|
||||
)
|
||||
|
||||
binary_archive = Tesla.get!(client(), src_url).body
|
||||
{:ok, binary_archive} = fetch(src)
|
||||
archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
|
||||
|
||||
sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright]
|
||||
@ -74,8 +74,8 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||
raise "Bad SHA256 for #{pack_name}"
|
||||
end
|
||||
|
||||
# The url specified in files should be in the same directory
|
||||
files_url =
|
||||
# The location specified in files should be in the same directory
|
||||
files_loc =
|
||||
url_or_path
|
||||
|> Path.dirname()
|
||||
|> Path.join(pack["files"])
|
||||
@ -88,11 +88,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||
:normal,
|
||||
" from ",
|
||||
:underline,
|
||||
files_url
|
||||
files_loc
|
||||
])
|
||||
)
|
||||
|
||||
files = Tesla.get!(client(), files_url).body |> Jason.decode!()
|
||||
files = fetch_and_decode(files_loc)
|
||||
|
||||
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
|
||||
|
||||
@ -237,16 +237,20 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_manifest(from) do
|
||||
Jason.decode!(
|
||||
if String.starts_with?(from, "http") do
|
||||
Tesla.get!(client(), from).body
|
||||
else
|
||||
File.read!(from)
|
||||
end
|
||||
)
|
||||
defp fetch_and_decode(from) do
|
||||
with {:ok, json} <- fetch(from) do
|
||||
Jason.decode!(json)
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch("http" <> _ = from) do
|
||||
with {:ok, %{body: body}} <- Tesla.get(client(), from) do
|
||||
{:ok, body}
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch(path), do: File.read(path)
|
||||
|
||||
defp parse_global_opts(args) do
|
||||
OptionParser.parse(
|
||||
args,
|
||||
|
@ -24,10 +24,7 @@ defmodule Pleroma.Activity.Queries do
|
||||
|
||||
@spec by_actor(query, String.t()) :: query
|
||||
def by_actor(query \\ Activity, actor) do
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("(?)->>'actor' = ?", activity.data, ^actor)
|
||||
)
|
||||
from(a in query, where: a.actor == ^actor)
|
||||
end
|
||||
|
||||
@spec by_author(query, User.t()) :: query
|
||||
|
@ -24,6 +24,6 @@ defmodule Pleroma.Constants do
|
||||
|
||||
const(static_only_files,
|
||||
do:
|
||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
|
||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
|
||||
)
|
||||
end
|
||||
|
@ -63,7 +63,7 @@ defmodule Pleroma.Conversation do
|
||||
ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
|
||||
{:ok, conversation} = create_for_ap_id(ap_id)
|
||||
|
||||
users = User.get_users_from_set(activity.recipients, false)
|
||||
users = User.get_users_from_set(activity.recipients, local_only: false)
|
||||
|
||||
participations =
|
||||
Enum.map(users, fn user ->
|
||||
|
@ -499,7 +499,7 @@ defmodule Pleroma.Emoji.Pack do
|
||||
if Base.decode16!(sha) == :crypto.hash(:sha256, archive) do
|
||||
{:ok, archive}
|
||||
else
|
||||
{:error, :imvalid_checksum}
|
||||
{:error, :invalid_checksum}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -22,22 +22,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|
||||
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
|
||||
end
|
||||
|
||||
defp add_scheme_opts(opts, %URI{scheme: "http"}), do: opts
|
||||
|
||||
defp add_scheme_opts(opts, %URI{scheme: "https", host: host}) do
|
||||
ssl_opts = [
|
||||
ssl_options: [
|
||||
# Workaround for remote server certificate chain issues
|
||||
partial_chain: &:hackney_connect.partial_chain/1,
|
||||
|
||||
# We don't support TLS v1.3 yet
|
||||
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
|
||||
server_name_indication: to_charlist(host)
|
||||
]
|
||||
]
|
||||
|
||||
Keyword.merge(opts, ssl_opts)
|
||||
end
|
||||
defp add_scheme_opts(opts, _), do: opts
|
||||
|
||||
def after_request(_), do: :ok
|
||||
end
|
||||
|
37
lib/pleroma/maintenance.ex
Normal file
37
lib/pleroma/maintenance.ex
Normal file
@ -0,0 +1,37 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Maintenance do
|
||||
alias Pleroma.Repo
|
||||
require Logger
|
||||
|
||||
def vacuum(args) do
|
||||
case args do
|
||||
"analyze" ->
|
||||
Logger.info("Runnning VACUUM ANALYZE.")
|
||||
|
||||
Repo.query!(
|
||||
"vacuum analyze;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
"full" ->
|
||||
Logger.info("Runnning VACUUM FULL.")
|
||||
|
||||
Logger.warn(
|
||||
"Re-packing your entire database may take a while and will consume extra disk space during the process."
|
||||
)
|
||||
|
||||
Repo.query!(
|
||||
"vacuum full;",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
_ ->
|
||||
Logger.error("Error: invalid vacuum argument.")
|
||||
end
|
||||
end
|
||||
end
|
@ -92,8 +92,9 @@ defmodule Pleroma.Notification do
|
||||
|> join(:left, [n, a], object in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
|
||||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||
object.data,
|
||||
a.data,
|
||||
a.data
|
||||
)
|
||||
)
|
||||
@ -224,18 +225,8 @@ defmodule Pleroma.Notification do
|
||||
|> Marker.multi_set_last_read_id(user, "notifications")
|
||||
|> Repo.transaction()
|
||||
|
||||
Notification
|
||||
for_user_query(user)
|
||||
|> where([n], n.id in ^notification_ids)
|
||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||
|> join(:left, [n, a], object in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
|
||||
object.data,
|
||||
a.data
|
||||
)
|
||||
)
|
||||
|> preload([n, a, o], activity: {a, object: o})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@ -370,7 +361,8 @@ defmodule Pleroma.Notification do
|
||||
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do
|
||||
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
|
||||
|
||||
potential_receivers = User.get_users_from_set(potential_receiver_ap_ids, local_only)
|
||||
potential_receivers =
|
||||
User.get_users_from_set(potential_receiver_ap_ids, local_only: local_only)
|
||||
|
||||
notification_enabled_ap_ids =
|
||||
potential_receiver_ap_ids
|
||||
|
@ -31,7 +31,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
||||
{"x-content-type-options", "nosniff"},
|
||||
{"referrer-policy", referrer_policy},
|
||||
{"x-download-options", "noopen"},
|
||||
{"content-security-policy", csp_string() <> ";"}
|
||||
{"content-security-policy", csp_string()}
|
||||
]
|
||||
|
||||
if report_uri do
|
||||
@ -43,23 +43,46 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
||||
]
|
||||
}
|
||||
|
||||
headers ++ [{"reply-to", Jason.encode!(report_group)}]
|
||||
[{"reply-to", Jason.encode!(report_group)} | headers]
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
static_csp_rules = [
|
||||
"default-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"font-src 'self'",
|
||||
"manifest-src 'self'"
|
||||
]
|
||||
|
||||
@csp_start [Enum.join(static_csp_rules, ";") <> ";"]
|
||||
|
||||
defp csp_string do
|
||||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
||||
static_url = Pleroma.Web.Endpoint.static_url()
|
||||
websocket_url = Pleroma.Web.Endpoint.websocket_url()
|
||||
report_uri = Config.get([:http_security, :report_uri])
|
||||
|
||||
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
|
||||
img_src = "img-src 'self' data: blob:"
|
||||
media_src = "media-src 'self'"
|
||||
|
||||
{img_src, media_src} =
|
||||
if Config.get([:media_proxy, :enabled]) &&
|
||||
!Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
|
||||
sources = get_proxy_and_attachment_sources()
|
||||
{[img_src, sources], [media_src, sources]}
|
||||
else
|
||||
{[img_src, " https:"], [media_src, " https:"]}
|
||||
end
|
||||
|
||||
connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
|
||||
|
||||
connect_src =
|
||||
if Pleroma.Config.get(:env) == :dev do
|
||||
connect_src <> " http://localhost:3035/"
|
||||
[connect_src, " http://localhost:3035/"]
|
||||
else
|
||||
connect_src
|
||||
end
|
||||
@ -71,27 +94,46 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
||||
"script-src 'self'"
|
||||
end
|
||||
|
||||
main_part = [
|
||||
"default-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"img-src 'self' data: blob: https:",
|
||||
"media-src 'self' https:",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"font-src 'self'",
|
||||
"manifest-src 'self'",
|
||||
connect_src,
|
||||
script_src
|
||||
]
|
||||
report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
|
||||
insecure = if scheme == "https", do: "upgrade-insecure-requests"
|
||||
|
||||
report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: []
|
||||
|
||||
insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: []
|
||||
|
||||
(main_part ++ report ++ insecure)
|
||||
|> Enum.join("; ")
|
||||
@csp_start
|
||||
|> add_csp_param(img_src)
|
||||
|> add_csp_param(media_src)
|
||||
|> add_csp_param(connect_src)
|
||||
|> add_csp_param(script_src)
|
||||
|> add_csp_param(insecure)
|
||||
|> add_csp_param(report)
|
||||
|> :erlang.iolist_to_binary()
|
||||
end
|
||||
|
||||
defp get_proxy_and_attachment_sources do
|
||||
media_proxy_whitelist =
|
||||
Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc ->
|
||||
add_source(acc, host)
|
||||
end)
|
||||
|
||||
upload_base_url =
|
||||
if Config.get([Pleroma.Upload, :base_url]),
|
||||
do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host
|
||||
|
||||
s3_endpoint =
|
||||
if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3,
|
||||
do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host
|
||||
|
||||
[]
|
||||
|> add_source(upload_base_url)
|
||||
|> add_source(s3_endpoint)
|
||||
|> add_source(media_proxy_whitelist)
|
||||
end
|
||||
|
||||
defp add_source(iodata, nil), do: iodata
|
||||
defp add_source(iodata, source), do: [[?\s, source] | iodata]
|
||||
|
||||
defp add_csp_param(csp_iodata, nil), do: csp_iodata
|
||||
|
||||
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
|
||||
|
||||
def warn_if_disabled do
|
||||
unless Config.get([:http_security, :enabled]) do
|
||||
Logger.warn("
|
||||
|
@ -538,9 +538,10 @@ defmodule Pleroma.User do
|
||||
|> delete_change(:also_known_as)
|
||||
|> unique_constraint(:email)
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_inclusion(:actor_type, ["Person", "Service"])
|
||||
end
|
||||
|
||||
@spec update_as_admin(%User{}, map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec update_as_admin(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||
def update_as_admin(user, params) do
|
||||
params = Map.put(params, "password_confirmation", params["password"])
|
||||
changeset = update_as_admin_changeset(user, params)
|
||||
@ -561,7 +562,7 @@ defmodule Pleroma.User do
|
||||
|> put_change(:password_reset_pending, false)
|
||||
end
|
||||
|
||||
@spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec reset_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||
def reset_password(%User{} = user, params) do
|
||||
reset_password(user, user, params)
|
||||
end
|
||||
@ -1208,8 +1209,9 @@ defmodule Pleroma.User do
|
||||
|
||||
def increment_unread_conversation_count(_, user), do: {:ok, user}
|
||||
|
||||
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
|
||||
def get_users_from_set(ap_ids, local_only \\ true) do
|
||||
@spec get_users_from_set([String.t()], keyword()) :: [User.t()]
|
||||
def get_users_from_set(ap_ids, opts \\ []) do
|
||||
local_only = Keyword.get(opts, :local_only, true)
|
||||
criteria = %{ap_id: ap_ids, deactivated: false}
|
||||
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
|
||||
|
||||
@ -1618,12 +1620,19 @@ defmodule Pleroma.User do
|
||||
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
|
||||
|
||||
def get_or_fetch_by_ap_id(ap_id) do
|
||||
user = get_cached_by_ap_id(ap_id)
|
||||
cached_user = get_cached_by_ap_id(ap_id)
|
||||
|
||||
if !is_nil(user) and !needs_update?(user) do
|
||||
{:ok, user}
|
||||
else
|
||||
fetch_by_ap_id(ap_id)
|
||||
maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
|
||||
|
||||
case {cached_user, maybe_fetched_user} do
|
||||
{_, {:ok, %User{} = user}} ->
|
||||
{:ok, user}
|
||||
|
||||
{%User{} = user, _} ->
|
||||
{:ok, user}
|
||||
|
||||
_ ->
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -45,7 +45,7 @@ defmodule Pleroma.User.Query do
|
||||
is_admin: boolean(),
|
||||
is_moderator: boolean(),
|
||||
super_users: boolean(),
|
||||
exclude_service_users: boolean(),
|
||||
invisible: boolean(),
|
||||
followers: User.t(),
|
||||
friends: User.t(),
|
||||
recipients_from_activity: [String.t()],
|
||||
@ -89,8 +89,8 @@ defmodule Pleroma.User.Query do
|
||||
where(query, [u], ilike(field(u, ^key), ^"%#{value}%"))
|
||||
end
|
||||
|
||||
defp compose_query({:exclude_service_users, _}, query) do
|
||||
where(query, [u], not like(u.ap_id, "%/relay") and not like(u.ap_id, "%/internal/fetch"))
|
||||
defp compose_query({:invisible, bool}, query) when is_boolean(bool) do
|
||||
where(query, [u], u.invisible == ^bool)
|
||||
end
|
||||
|
||||
defp compose_query({key, value}, query)
|
||||
|
@ -538,14 +538,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]
|
||||
def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do
|
||||
@spec fetch_public_or_unlisted_activities(map(), Pagination.type()) :: [Activity.t()]
|
||||
def fetch_public_or_unlisted_activities(opts \\ %{}, pagination \\ :keyset) do
|
||||
opts = Map.drop(opts, ["user"])
|
||||
|
||||
[Constants.as_public()]
|
||||
|> fetch_activities_query(opts)
|
||||
|> restrict_unlisted()
|
||||
|> Pagination.fetch_paginated(opts, pagination)
|
||||
query = fetch_activities_query([Constants.as_public()], opts)
|
||||
|
||||
query =
|
||||
if opts["restrict_unlisted"] do
|
||||
restrict_unlisted(query)
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
Pagination.fetch_paginated(query, opts, pagination)
|
||||
end
|
||||
|
||||
@spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]
|
||||
def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do
|
||||
opts
|
||||
|> Map.put("restrict_unlisted", true)
|
||||
|> fetch_public_or_unlisted_activities(pagination)
|
||||
end
|
||||
|
||||
@valid_visibilities ~w[direct unlisted public private]
|
||||
@ -923,6 +936,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
[activity, object: o] in query,
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
|
||||
where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids),
|
||||
where:
|
||||
fragment(
|
||||
"recipients_contain_blocked_domains(?, ?) = false",
|
||||
activity.recipients,
|
||||
^domain_blocks
|
||||
),
|
||||
where:
|
||||
fragment(
|
||||
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
|
||||
@ -1017,6 +1036,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
end
|
||||
end
|
||||
|
||||
defp exclude_invisible_actors(query, %{"invisible_actors" => true}), do: query
|
||||
|
||||
defp exclude_invisible_actors(query, _opts) do
|
||||
invisible_ap_ids =
|
||||
User.Query.build(%{invisible: true, select: [:ap_id]})
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn %{ap_id: ap_id} -> ap_id end)
|
||||
|
||||
from([activity] in query, where: activity.actor not in ^invisible_ap_ids)
|
||||
end
|
||||
|
||||
defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
|
||||
from(activity in query, where: activity.id != ^id)
|
||||
end
|
||||
@ -1122,6 +1152,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
|> restrict_instance(opts)
|
||||
|> Activity.restrict_deactivated_users()
|
||||
|> exclude_poll_votes(opts)
|
||||
|> exclude_invisible_actors(opts)
|
||||
|> exclude_visibility(opts)
|
||||
end
|
||||
|
||||
@ -1145,7 +1176,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
|> Activity.with_joined_object()
|
||||
|> Object.with_joined_activity()
|
||||
|> select([_like, object, activity], %{activity | object: object})
|
||||
|> order_by([like, _, _], desc: like.id)
|
||||
|> order_by([like, _, _], desc_nulls_last: like.id)
|
||||
|> Pagination.fetch_paginated(
|
||||
Map.merge(params, %{"skip_order" => true}),
|
||||
pagination,
|
||||
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
@ -85,15 +86,20 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
||||
end
|
||||
end
|
||||
|
||||
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
||||
def announce(actor, object, options \\ []) do
|
||||
public? = Keyword.get(options, :public, false)
|
||||
to = [actor.follower_address, object.data["actor"]]
|
||||
|
||||
to =
|
||||
if public? do
|
||||
[Pleroma.Constants.as_public() | to]
|
||||
else
|
||||
to
|
||||
cond do
|
||||
actor.ap_id == Relay.relay_ap_id() ->
|
||||
[actor.follower_address]
|
||||
|
||||
public? ->
|
||||
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
|
||||
|
||||
true ->
|
||||
[actor.follower_address, object.data["actor"]]
|
||||
end
|
||||
|
||||
{:ok,
|
||||
|
@ -33,11 +33,14 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||
# - Stream out the announce
|
||||
def handle(%{data: %{"type" => "Announce"}} = object, meta) do
|
||||
announced_object = Object.get_by_ap_id(object.data["object"])
|
||||
user = User.get_cached_by_ap_id(object.data["actor"])
|
||||
|
||||
Utils.add_announce_to_object(object, announced_object)
|
||||
|
||||
Notification.create_notifications(object)
|
||||
ActivityPub.stream_out(object)
|
||||
if !User.is_internal_user?(user) do
|
||||
Notification.create_notifications(object)
|
||||
ActivityPub.stream_out(object)
|
||||
end
|
||||
|
||||
{:ok, object, meta}
|
||||
end
|
||||
|
@ -1045,10 +1045,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
Map.put(object, "tag", tags)
|
||||
end
|
||||
|
||||
# TODO These should be added on our side on insertion, it doesn't make much
|
||||
# sense to regenerate these all the time
|
||||
def add_mention_tags(object) do
|
||||
{enabled_receivers, disabled_receivers} = Utils.get_notified_from_object(object)
|
||||
potential_receivers = enabled_receivers ++ disabled_receivers
|
||||
mentions = Enum.map(potential_receivers, &build_mention_tag/1)
|
||||
to = object["to"] || []
|
||||
cc = object["cc"] || []
|
||||
mentioned = User.get_users_from_set(to ++ cc, local_only: false)
|
||||
|
||||
mentions = Enum.map(mentioned, &build_mention_tag/1)
|
||||
|
||||
tags = object["tag"] || []
|
||||
Map.put(object, "tag", tags ++ mentions)
|
||||
|
@ -16,7 +16,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
alias Pleroma.ReportNote
|
||||
alias Pleroma.Stats
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
@ -31,8 +30,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MastodonAPI
|
||||
alias Pleroma.Web.MastodonAPI.AppView
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.Router
|
||||
|
||||
require Logger
|
||||
@ -68,14 +65,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
]
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:invites"], admin: true}
|
||||
when action in [:create_invite_token, :revoke_invite, :email_invite]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:follows"], admin: true}
|
||||
@ -120,10 +109,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
:config_update,
|
||||
:resend_confirmation_email,
|
||||
:confirm_email,
|
||||
:oauth_app_create,
|
||||
:oauth_app_list,
|
||||
:oauth_app_update,
|
||||
:oauth_app_delete,
|
||||
:reload_emoji
|
||||
]
|
||||
)
|
||||
@ -529,69 +514,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
render_error(conn, :forbidden, "You can't revoke your own admin status.")
|
||||
end
|
||||
|
||||
@doc "Sends registration invite via email"
|
||||
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
|
||||
with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
|
||||
{_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
|
||||
{:ok, invite_token} <- UserInviteToken.create_invite(),
|
||||
email <-
|
||||
Pleroma.Emails.UserEmail.user_invitation_email(
|
||||
user,
|
||||
invite_token,
|
||||
email,
|
||||
params["name"]
|
||||
),
|
||||
{:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
{:registrations_open, _} ->
|
||||
{:error, "To send invites you need to set the `registrations_open` option to false."}
|
||||
|
||||
{:invites_enabled, _} ->
|
||||
{:error, "To send invites you need to set the `invites_enabled` option to true."}
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Create an account registration invite token"
|
||||
def create_invite_token(conn, params) do
|
||||
opts = %{}
|
||||
|
||||
opts =
|
||||
if params["max_use"],
|
||||
do: Map.put(opts, :max_use, params["max_use"]),
|
||||
else: opts
|
||||
|
||||
opts =
|
||||
if params["expires_at"],
|
||||
do: Map.put(opts, :expires_at, params["expires_at"]),
|
||||
else: opts
|
||||
|
||||
{:ok, invite} = UserInviteToken.create_invite(opts)
|
||||
|
||||
json(conn, AccountView.render("invite.json", %{invite: invite}))
|
||||
end
|
||||
|
||||
@doc "Get list of created invites"
|
||||
def invites(conn, _params) do
|
||||
invites = UserInviteToken.list_invites()
|
||||
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("invites.json", %{invites: invites})
|
||||
end
|
||||
|
||||
@doc "Revokes invite by token"
|
||||
def revoke_invite(conn, %{"token" => token}) do
|
||||
with {:ok, invite} <- UserInviteToken.find_by_token(token),
|
||||
{:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("invite.json", %{invite: updated_invite})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Get a password reset token (base64 string) for given nickname"
|
||||
def get_password_reset(conn, %{"nickname" => nickname}) do
|
||||
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)
|
||||
@ -647,7 +569,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
%{assigns: %{user: admin}} = conn,
|
||||
%{"nickname" => nickname} = params
|
||||
) do
|
||||
with {_, user} <- {:user, User.get_cached_by_nickname(nickname)},
|
||||
with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
|
||||
{:ok, _user} <-
|
||||
User.update_as_admin(user, params) do
|
||||
ModerationLog.insert_log(%{
|
||||
@ -669,11 +591,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
json(conn, %{status: "success"})
|
||||
else
|
||||
{:error, changeset} ->
|
||||
{_, {error, _}} = Enum.at(changeset.errors, 0)
|
||||
json(conn, %{error: "New password #{error}."})
|
||||
errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
|
||||
|
||||
json(conn, %{errors: errors})
|
||||
|
||||
_ ->
|
||||
json(conn, %{error: "Unable to change password."})
|
||||
json(conn, %{error: "Unable to update user."})
|
||||
end
|
||||
end
|
||||
|
||||
@ -949,83 +872,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
conn |> json("")
|
||||
end
|
||||
|
||||
def oauth_app_create(conn, params) do
|
||||
params =
|
||||
if params["name"] do
|
||||
Map.put(params, "client_name", params["name"])
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
result =
|
||||
case App.create(params) do
|
||||
{:ok, app} ->
|
||||
AppView.render("show.json", %{app: app, admin: true})
|
||||
|
||||
{:error, changeset} ->
|
||||
App.errors(changeset)
|
||||
end
|
||||
|
||||
json(conn, result)
|
||||
end
|
||||
|
||||
def oauth_app_update(conn, params) do
|
||||
params =
|
||||
if params["name"] do
|
||||
Map.put(params, "client_name", params["name"])
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
with {:ok, app} <- App.update(params) do
|
||||
json(conn, AppView.render("show.json", %{app: app, admin: true}))
|
||||
else
|
||||
{:error, changeset} ->
|
||||
json(conn, App.errors(changeset))
|
||||
|
||||
nil ->
|
||||
json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
def oauth_app_list(conn, params) do
|
||||
{page, page_size} = page_params(params)
|
||||
|
||||
search_params = %{
|
||||
client_name: params["name"],
|
||||
client_id: params["client_id"],
|
||||
page: page,
|
||||
page_size: page_size
|
||||
}
|
||||
|
||||
search_params =
|
||||
if Map.has_key?(params, "trusted") do
|
||||
Map.put(search_params, :trusted, params["trusted"])
|
||||
else
|
||||
search_params
|
||||
end
|
||||
|
||||
with {:ok, apps, count} <- App.search(search_params) do
|
||||
json(
|
||||
conn,
|
||||
AppView.render("index.json",
|
||||
apps: apps,
|
||||
count: count,
|
||||
page_size: page_size,
|
||||
admin: true
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def oauth_app_delete(conn, params) do
|
||||
with {:ok, _app} <- App.destroy(params["id"]) do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
_ -> json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
def stats(conn, _) do
|
||||
count = Stats.get_status_visibility_count()
|
||||
|
||||
|
78
lib/pleroma/web/admin_api/controllers/invite_controller.ex
Normal file
78
lib/pleroma/web/admin_api/controllers/invite_controller.ex
Normal file
@ -0,0 +1,78 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.InviteController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.UserInviteToken
|
||||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :index)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:invites"], admin: true} when action in [:create, :revoke, :email]
|
||||
)
|
||||
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InviteOperation
|
||||
|
||||
@doc "Get list of created invites"
|
||||
def index(conn, _params) do
|
||||
invites = UserInviteToken.list_invites()
|
||||
|
||||
render(conn, "index.json", invites: invites)
|
||||
end
|
||||
|
||||
@doc "Create an account registration invite token"
|
||||
def create(%{body_params: params} = conn, _) do
|
||||
{:ok, invite} = UserInviteToken.create_invite(params)
|
||||
|
||||
render(conn, "show.json", invite: invite)
|
||||
end
|
||||
|
||||
@doc "Revokes invite by token"
|
||||
def revoke(%{body_params: %{token: token}} = conn, _) do
|
||||
with {:ok, invite} <- UserInviteToken.find_by_token(token),
|
||||
{:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
|
||||
render(conn, "show.json", invite: updated_invite)
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Sends registration invite via email"
|
||||
def email(%{assigns: %{user: user}, body_params: %{email: email} = params} = conn, _) do
|
||||
with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
|
||||
{_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
|
||||
{:ok, invite_token} <- UserInviteToken.create_invite(),
|
||||
{:ok, _} <-
|
||||
user
|
||||
|> Pleroma.Emails.UserEmail.user_invitation_email(
|
||||
invite_token,
|
||||
email,
|
||||
params[:name]
|
||||
)
|
||||
|> Pleroma.Emails.Mailer.deliver() do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
{:registrations_open, _} ->
|
||||
{:error, "To send invites you need to set the `registrations_open` option to false."}
|
||||
|
||||
{:invites_enabled, _} ->
|
||||
{:error, "To send invites you need to set the `invites_enabled` option to true."}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,87 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.OAuthAppController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Web.OAuth.App
|
||||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write"], admin: true}
|
||||
when action in [:create, :index, :update, :delete]
|
||||
)
|
||||
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.OAuthAppOperation
|
||||
|
||||
def index(conn, params) do
|
||||
search_params =
|
||||
params
|
||||
|> Map.take([:client_id, :page, :page_size, :trusted])
|
||||
|> Map.put(:client_name, params[:name])
|
||||
|
||||
with {:ok, apps, count} <- App.search(search_params) do
|
||||
render(conn, "index.json",
|
||||
apps: apps,
|
||||
count: count,
|
||||
page_size: params.page_size,
|
||||
admin: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def create(%{body_params: params} = conn, _) do
|
||||
params =
|
||||
if params[:name] do
|
||||
Map.put(params, :client_name, params[:name])
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
case App.create(params) do
|
||||
{:ok, app} ->
|
||||
render(conn, "show.json", app: app, admin: true)
|
||||
|
||||
{:error, changeset} ->
|
||||
json(conn, App.errors(changeset))
|
||||
end
|
||||
end
|
||||
|
||||
def update(%{body_params: params} = conn, %{id: id}) do
|
||||
params =
|
||||
if params[:name] do
|
||||
Map.put(params, :client_name, params.name)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
with {:ok, app} <- App.update(id, params) do
|
||||
render(conn, "show.json", app: app, admin: true)
|
||||
else
|
||||
{:error, changeset} ->
|
||||
json(conn, App.errors(changeset))
|
||||
|
||||
nil ->
|
||||
json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, params) do
|
||||
with {:ok, _app} <- App.destroy(params.id) do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
_ -> json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
end
|
@ -42,7 +42,7 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
|
||||
def show(conn, %{id: id}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(id) do
|
||||
conn
|
||||
|> put_view(MastodonAPI.StatusView)
|
||||
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
||||
|> render("show.json", %{activity: activity})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
|
@ -21,7 +21,7 @@ defmodule Pleroma.Web.AdminAPI.Search do
|
||||
query =
|
||||
params
|
||||
|> Map.drop([:page, :page_size])
|
||||
|> Map.put(:exclude_service_users, true)
|
||||
|> Map.put(:invisible, false)
|
||||
|> User.Query.build()
|
||||
|> order_by([u], u.nickname)
|
||||
|
||||
@ -31,7 +31,6 @@ defmodule Pleroma.Web.AdminAPI.Search do
|
||||
count = Repo.aggregate(query, :count, :id)
|
||||
|
||||
results = Repo.all(paginated_query)
|
||||
|
||||
{:ok, results, count}
|
||||
end
|
||||
end
|
||||
|
@ -80,24 +80,6 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
|
||||
}
|
||||
end
|
||||
|
||||
def render("invite.json", %{invite: invite}) do
|
||||
%{
|
||||
"id" => invite.id,
|
||||
"token" => invite.token,
|
||||
"used" => invite.used,
|
||||
"expires_at" => invite.expires_at,
|
||||
"uses" => invite.uses,
|
||||
"max_use" => invite.max_use,
|
||||
"invite_type" => invite.invite_type
|
||||
}
|
||||
end
|
||||
|
||||
def render("invites.json", %{invites: invites}) do
|
||||
%{
|
||||
invites: render_many(invites, AccountView, "invite.json", as: :invite)
|
||||
}
|
||||
end
|
||||
|
||||
def render("created.json", %{user: user}) do
|
||||
%{
|
||||
type: "success",
|
||||
|
25
lib/pleroma/web/admin_api/views/invite_view.ex
Normal file
25
lib/pleroma/web/admin_api/views/invite_view.ex
Normal file
@ -0,0 +1,25 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.InviteView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
def render("index.json", %{invites: invites}) do
|
||||
%{
|
||||
invites: render_many(invites, __MODULE__, "show.json", as: :invite)
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{invite: invite}) do
|
||||
%{
|
||||
"id" => invite.id,
|
||||
"token" => invite.token,
|
||||
"used" => invite.used,
|
||||
"expires_at" => invite.expires_at,
|
||||
"uses" => invite.uses,
|
||||
"max_use" => invite.max_use,
|
||||
"invite_type" => invite.invite_type
|
||||
}
|
||||
end
|
||||
end
|
148
lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
Normal file
148
lib/pleroma/web/api_spec/operations/admin/invite_operation.ex
Normal file
@ -0,0 +1,148 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.Admin.InviteOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
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: ["Admin", "Invites"],
|
||||
summary: "Get a list of generated invites",
|
||||
operationId: "AdminAPI.InviteController.index",
|
||||
security: [%{"oAuth" => ["read:invites"]}],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Invites", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
invites: %Schema{type: :array, items: invite()}
|
||||
},
|
||||
example: %{
|
||||
"invites" => [
|
||||
%{
|
||||
"id" => 123,
|
||||
"token" => "kSQtDj_GNy2NZsL9AQDFIsHN5qdbguB6qRg3WHw6K1U=",
|
||||
"used" => true,
|
||||
"expires_at" => nil,
|
||||
"uses" => 0,
|
||||
"max_use" => nil,
|
||||
"invite_type" => "one_time"
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Invites"],
|
||||
summary: "Create an account registration invite token",
|
||||
operationId: "AdminAPI.InviteController.create",
|
||||
security: [%{"oAuth" => ["write:invites"]}],
|
||||
requestBody:
|
||||
request_body("Parameters", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
max_use: %Schema{type: :integer},
|
||||
expires_at: %Schema{type: :string, format: :date, example: "2020-04-20"}
|
||||
}
|
||||
}),
|
||||
responses: %{
|
||||
200 => Operation.response("Invite", "application/json", invite())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def revoke_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Invites"],
|
||||
summary: "Revoke invite by token",
|
||||
operationId: "AdminAPI.InviteController.revoke",
|
||||
security: [%{"oAuth" => ["write:invites"]}],
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:token],
|
||||
properties: %{
|
||||
token: %Schema{type: :string}
|
||||
}
|
||||
},
|
||||
required: true
|
||||
),
|
||||
responses: %{
|
||||
200 => Operation.response("Invite", "application/json", invite()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def email_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Invites"],
|
||||
summary: "Sends registration invite via email",
|
||||
operationId: "AdminAPI.InviteController.email",
|
||||
security: [%{"oAuth" => ["write:invites"]}],
|
||||
requestBody:
|
||||
request_body(
|
||||
"Parameters",
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:email],
|
||||
properties: %{
|
||||
email: %Schema{type: :string, format: :email},
|
||||
name: %Schema{type: :string}
|
||||
}
|
||||
},
|
||||
required: true
|
||||
),
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp invite do
|
||||
%Schema{
|
||||
title: "Invite",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :integer},
|
||||
token: %Schema{type: :string},
|
||||
used: %Schema{type: :boolean},
|
||||
expires_at: %Schema{type: :string, format: :date, nullable: true},
|
||||
uses: %Schema{type: :integer},
|
||||
max_use: %Schema{type: :integer, nullable: true},
|
||||
invite_type: %Schema{
|
||||
type: :string,
|
||||
enum: ["one_time", "reusable", "date_limited", "reusable_date_limited"]
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"id" => 123,
|
||||
"token" => "kSQtDj_GNy2NZsL9AQDFIsHN5qdbguB6qRg3WHw6K1U=",
|
||||
"used" => true,
|
||||
"expires_at" => nil,
|
||||
"uses" => 0,
|
||||
"max_use" => nil,
|
||||
"invite_type" => "one_time"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
215
lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
Normal file
215
lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
Normal file
@ -0,0 +1,215 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.Admin.OAuthAppOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
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{
|
||||
summary: "List OAuth apps",
|
||||
tags: ["Admin", "oAuth Apps"],
|
||||
operationId: "AdminAPI.OAuthAppController.index",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
parameters: [
|
||||
Operation.parameter(:name, :query, %Schema{type: :string}, "App name"),
|
||||
Operation.parameter(:client_id, :query, %Schema{type: :string}, "Client ID"),
|
||||
Operation.parameter(:page, :query, %Schema{type: :integer, default: 1}, "Page"),
|
||||
Operation.parameter(
|
||||
:trusted,
|
||||
:query,
|
||||
%Schema{type: :boolean, default: false},
|
||||
"Trusted apps"
|
||||
),
|
||||
Operation.parameter(
|
||||
:page_size,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 50},
|
||||
"Number of apps to return"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("List of apps", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
apps: %Schema{type: :array, items: oauth_app()},
|
||||
count: %Schema{type: :integer},
|
||||
page_size: %Schema{type: :integer}
|
||||
},
|
||||
example: %{
|
||||
"apps" => [
|
||||
%{
|
||||
"id" => 1,
|
||||
"name" => "App name",
|
||||
"client_id" => "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
|
||||
"client_secret" => "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
|
||||
"redirect_uri" => "https://example.com/oauth-callback",
|
||||
"website" => "https://example.com",
|
||||
"trusted" => true
|
||||
}
|
||||
],
|
||||
"count" => 1,
|
||||
"page_size" => 50
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "oAuth Apps"],
|
||||
summary: "Create OAuth App",
|
||||
operationId: "AdminAPI.OAuthAppController.create",
|
||||
requestBody: request_body("Parameters", create_request()),
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("App", "application/json", oauth_app()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "oAuth Apps"],
|
||||
summary: "Update OAuth App",
|
||||
operationId: "AdminAPI.OAuthAppController.update",
|
||||
parameters: [id_param()],
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
requestBody: request_body("Parameters", update_request()),
|
||||
responses: %{
|
||||
200 => Operation.response("App", "application/json", oauth_app()),
|
||||
400 =>
|
||||
Operation.response("Bad Request", "application/json", %Schema{
|
||||
oneOf: [ApiError, %Schema{type: :string}]
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "oAuth Apps"],
|
||||
summary: "Delete OAuth App",
|
||||
operationId: "AdminAPI.OAuthAppController.delete",
|
||||
parameters: [id_param()],
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
400 => no_content_response()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_request do
|
||||
%Schema{
|
||||
title: "oAuthAppCreateRequest",
|
||||
type: :object,
|
||||
required: [:name, :redirect_uris],
|
||||
properties: %{
|
||||
name: %Schema{type: :string, description: "Application Name"},
|
||||
scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"},
|
||||
redirect_uris: %Schema{
|
||||
type: :string,
|
||||
description:
|
||||
"Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
|
||||
},
|
||||
website: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "A URL to the homepage of the app"
|
||||
},
|
||||
trusted: %Schema{
|
||||
type: :boolean,
|
||||
nullable: true,
|
||||
default: false,
|
||||
description: "Is the app trusted?"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"name" => "My App",
|
||||
"redirect_uris" => "https://myapp.com/auth/callback",
|
||||
"website" => "https://myapp.com/",
|
||||
"scopes" => ["read", "write"],
|
||||
"trusted" => true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_request do
|
||||
%Schema{
|
||||
title: "oAuthAppUpdateRequest",
|
||||
type: :object,
|
||||
properties: %{
|
||||
name: %Schema{type: :string, description: "Application Name"},
|
||||
scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"},
|
||||
redirect_uris: %Schema{
|
||||
type: :string,
|
||||
description:
|
||||
"Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
|
||||
},
|
||||
website: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "A URL to the homepage of the app"
|
||||
},
|
||||
trusted: %Schema{
|
||||
type: :boolean,
|
||||
nullable: true,
|
||||
default: false,
|
||||
description: "Is the app trusted?"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"name" => "My App",
|
||||
"redirect_uris" => "https://myapp.com/auth/callback",
|
||||
"website" => "https://myapp.com/",
|
||||
"scopes" => ["read", "write"],
|
||||
"trusted" => true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp oauth_app do
|
||||
%Schema{
|
||||
title: "oAuthApp",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :integer},
|
||||
name: %Schema{type: :string},
|
||||
client_id: %Schema{type: :string},
|
||||
client_secret: %Schema{type: :string},
|
||||
redirect_uri: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true},
|
||||
trusted: %Schema{type: :boolean}
|
||||
},
|
||||
example: %{
|
||||
"id" => 123,
|
||||
"name" => "My App",
|
||||
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
|
||||
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
|
||||
"redirect_uri" => "https://myapp.com/oauth-callback",
|
||||
"website" => "https://myapp.com/",
|
||||
"trusted" => false
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def id_param do
|
||||
Operation.parameter(:id, :path, :integer, "App ID",
|
||||
example: 1337,
|
||||
required: true
|
||||
)
|
||||
end
|
||||
end
|
@ -137,7 +137,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do
|
||||
"background_upload_limit" => 4_000_000,
|
||||
"background_image" => "/static/image.png",
|
||||
"banner_upload_limit" => 4_000_000,
|
||||
"description" => "A Pleroma instance, an alternative fediverse server",
|
||||
"description" => "Pleroma: An efficient and flexible fediverse server",
|
||||
"email" => "lain@lain.com",
|
||||
"languages" => ["en"],
|
||||
"max_toot_chars" => 5000,
|
||||
|
42
lib/pleroma/web/embed_controller.ex
Normal file
42
lib/pleroma/web/embed_controller.ex
Normal file
@ -0,0 +1,42 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.EmbedController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
plug(:put_layout, :embed)
|
||||
|
||||
def show(conn, %{"id" => id}) do
|
||||
with %Activity{local: true} = activity <-
|
||||
Activity.get_by_id_with_object(id),
|
||||
true <- Visibility.is_public?(activity.object) do
|
||||
{:ok, author} = User.get_or_fetch(activity.object.data["actor"])
|
||||
|
||||
conn
|
||||
|> delete_resp_header("x-frame-options")
|
||||
|> delete_resp_header("content-security-policy")
|
||||
|> render("show.html",
|
||||
activity: activity,
|
||||
author: User.sanitize_html(author),
|
||||
counts: get_counts(activity)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_counts(%Activity{} = activity) do
|
||||
%Object{data: data} = Object.normalize(activity)
|
||||
|
||||
%{
|
||||
likes: Map.get(data, "like_count", 0),
|
||||
replies: Map.get(data, "repliesCount", 0),
|
||||
announces: Map.get(data, "announcement_count", 0)
|
||||
}
|
||||
end
|
||||
end
|
@ -56,7 +56,7 @@ defmodule Pleroma.Web.Feed.UserController do
|
||||
"actor_id" => user.ap_id
|
||||
}
|
||||
|> put_if_exist("max_id", params["max_id"])
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|> ActivityPub.fetch_public_or_unlisted_activities()
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("application/#{format}+xml")
|
||||
|
@ -81,7 +81,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
[name: :relation_id_action, params: ["id", "uri"]] when action in @relationship_actions
|
||||
[name: :relation_id_action, params: [:id, :uri]] when action in @relationship_actions
|
||||
)
|
||||
|
||||
plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions)
|
||||
@ -139,9 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||
end
|
||||
|
||||
@doc "PATCH /api/v1/accounts/update_credentials"
|
||||
def update_credentials(%{assigns: %{user: original_user}, body_params: params} = conn, _params) do
|
||||
user = original_user
|
||||
|
||||
def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _params) do
|
||||
params =
|
||||
params
|
||||
|> Enum.filter(fn {_, value} -> not is_nil(value) end)
|
||||
@ -183,12 +181,31 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||
changeset = User.update_changeset(user, user_params)
|
||||
|
||||
with {:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
user
|
||||
|> build_update_activity_params()
|
||||
|> ActivityPub.update()
|
||||
|
||||
render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
|
||||
else
|
||||
_e -> render_error(conn, :forbidden, "Invalid request")
|
||||
end
|
||||
end
|
||||
|
||||
# Hotfix, handling will be redone with the pipeline
|
||||
defp build_update_activity_params(user) do
|
||||
object =
|
||||
Pleroma.Web.ActivityPub.UserView.render("user.json", user: user)
|
||||
|> Map.delete("@context")
|
||||
|
||||
%{
|
||||
local: true,
|
||||
to: [user.follower_address],
|
||||
cc: [],
|
||||
object: object,
|
||||
actor: user.ap_id
|
||||
}
|
||||
end
|
||||
|
||||
defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
|
||||
with true <- is_map(params),
|
||||
true <- Map.has_key?(params, params_field),
|
||||
|
@ -21,6 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
|
||||
|
||||
@doc "GET /api/v1/conversations"
|
||||
def index(%{assigns: %{user: user}} = conn, params) do
|
||||
params = stringify_pagination_params(params)
|
||||
participations = Participation.for_user_with_last_activity_id(user, params)
|
||||
|
||||
conn
|
||||
@ -36,4 +37,20 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
|
||||
render(conn, "participation.json", participation: participation, for: user)
|
||||
end
|
||||
end
|
||||
|
||||
defp stringify_pagination_params(params) do
|
||||
atom_keys =
|
||||
Pleroma.Pagination.page_keys()
|
||||
|> Enum.map(&String.to_atom(&1))
|
||||
|
||||
str_keys =
|
||||
params
|
||||
|> Map.take(atom_keys)
|
||||
|> Enum.map(fn {key, value} -> {to_string(key), value} end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
params
|
||||
|> Map.delete(atom_keys)
|
||||
|> Map.merge(str_keys)
|
||||
end
|
||||
end
|
||||
|
@ -84,13 +84,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
[name: :status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]]
|
||||
[name: :status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: [:id]]
|
||||
when action in ~w(reblog unreblog)a
|
||||
)
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
[name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]]
|
||||
[name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: [:id]]
|
||||
when action in ~w(favourite unfavourite)a
|
||||
)
|
||||
|
||||
|
@ -111,7 +111,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
||||
else
|
||||
activities =
|
||||
params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("type", ["Create"])
|
||||
|> Map.put("local_only", local_only)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|
@ -182,12 +182,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||
bot = user.actor_type in ["Application", "Service"]
|
||||
|
||||
emojis =
|
||||
Enum.map(user.emoji, fn {shortcode, url} ->
|
||||
Enum.map(user.emoji, fn {shortcode, raw_url} ->
|
||||
url = MediaProxy.url(raw_url)
|
||||
|
||||
%{
|
||||
"shortcode" => shortcode,
|
||||
"url" => url,
|
||||
"static_url" => url,
|
||||
"visible_in_picker" => false
|
||||
shortcode: shortcode,
|
||||
url: url,
|
||||
static_url: url,
|
||||
visible_in_picker: false
|
||||
}
|
||||
end)
|
||||
|
||||
|
@ -25,12 +25,12 @@ defmodule Pleroma.Web.OAuth.App do
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@spec changeset(App.t(), map()) :: Ecto.Changeset.t()
|
||||
@spec changeset(t(), map()) :: Ecto.Changeset.t()
|
||||
def changeset(struct, params) do
|
||||
cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted])
|
||||
end
|
||||
|
||||
@spec register_changeset(App.t(), map()) :: Ecto.Changeset.t()
|
||||
@spec register_changeset(t(), map()) :: Ecto.Changeset.t()
|
||||
def register_changeset(struct, params \\ %{}) do
|
||||
changeset =
|
||||
struct
|
||||
@ -52,18 +52,19 @@ defmodule Pleroma.Web.OAuth.App do
|
||||
end
|
||||
end
|
||||
|
||||
@spec create(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def create(params) do
|
||||
with changeset <- __MODULE__.register_changeset(%__MODULE__{}, params) do
|
||||
Repo.insert(changeset)
|
||||
end
|
||||
%__MODULE__{}
|
||||
|> register_changeset(params)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@spec update(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update(params) do
|
||||
with %__MODULE__{} = app <- Repo.get(__MODULE__, params["id"]),
|
||||
changeset <- changeset(app, params) do
|
||||
Repo.update(changeset)
|
||||
@spec update(pos_integer(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def update(id, params) do
|
||||
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
|
||||
app
|
||||
|> changeset(params)
|
||||
|> Repo.update()
|
||||
end
|
||||
end
|
||||
|
||||
@ -71,7 +72,7 @@ defmodule Pleroma.Web.OAuth.App do
|
||||
Gets app by attrs or create new with attrs.
|
||||
And updates the scopes if need.
|
||||
"""
|
||||
@spec get_or_make(map(), list(String.t())) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec get_or_make(map(), list(String.t())) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def get_or_make(attrs, scopes) do
|
||||
with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do
|
||||
update_scopes(app, scopes)
|
||||
@ -92,7 +93,7 @@ defmodule Pleroma.Web.OAuth.App do
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@spec search(map()) :: {:ok, [App.t()], non_neg_integer()}
|
||||
@spec search(map()) :: {:ok, [t()], non_neg_integer()}
|
||||
def search(params) do
|
||||
query = from(a in __MODULE__)
|
||||
|
||||
@ -128,7 +129,7 @@ defmodule Pleroma.Web.OAuth.App do
|
||||
{:ok, Repo.all(query), count}
|
||||
end
|
||||
|
||||
@spec destroy(pos_integer()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def destroy(id) do
|
||||
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
|
||||
Repo.delete(app)
|
||||
|
@ -106,7 +106,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "The requested instance does not support sharing emoji packs"})
|
||||
|
||||
{:error, :imvalid_checksum} ->
|
||||
{:error, :invalid_checksum} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"})
|
||||
|
@ -164,10 +164,10 @@ defmodule Pleroma.Web.Router do
|
||||
post("/relay", RelayController, :follow)
|
||||
delete("/relay", RelayController, :unfollow)
|
||||
|
||||
post("/users/invite_token", AdminAPIController, :create_invite_token)
|
||||
get("/users/invites", AdminAPIController, :invites)
|
||||
post("/users/revoke_invite", AdminAPIController, :revoke_invite)
|
||||
post("/users/email_invite", AdminAPIController, :email_invite)
|
||||
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)
|
||||
@ -205,10 +205,10 @@ defmodule Pleroma.Web.Router do
|
||||
post("/reload_emoji", AdminAPIController, :reload_emoji)
|
||||
get("/stats", AdminAPIController, :stats)
|
||||
|
||||
get("/oauth_app", AdminAPIController, :oauth_app_list)
|
||||
post("/oauth_app", AdminAPIController, :oauth_app_create)
|
||||
patch("/oauth_app/:id", AdminAPIController, :oauth_app_update)
|
||||
delete("/oauth_app/:id", AdminAPIController, :oauth_app_delete)
|
||||
get("/oauth_app", OAuthAppController, :index)
|
||||
post("/oauth_app", OAuthAppController, :create)
|
||||
patch("/oauth_app/:id", OAuthAppController, :update)
|
||||
delete("/oauth_app/:id", OAuthAppController, :delete)
|
||||
end
|
||||
|
||||
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
||||
@ -664,6 +664,8 @@ defmodule Pleroma.Web.Router do
|
||||
post("/auth/password", MastodonAPI.AuthController, :password_reset)
|
||||
|
||||
get("/web/*path", MastoFEController, :index)
|
||||
|
||||
get("/embed/:id", EmbedController, :show)
|
||||
end
|
||||
|
||||
scope "/proxy/", Pleroma.Web.MediaProxy do
|
||||
|
@ -136,7 +136,7 @@ defmodule Pleroma.Web.Streamer do
|
||||
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
|
||||
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host),
|
||||
true <- thread_containment(item, user),
|
||||
false <- CommonAPI.thread_muted?(user, item) do
|
||||
false <- CommonAPI.thread_muted?(user, parent) do
|
||||
false
|
||||
else
|
||||
_ -> true
|
||||
|
8
lib/pleroma/web/templates/embed/_attachment.html.eex
Normal file
8
lib/pleroma/web/templates/embed/_attachment.html.eex
Normal file
@ -0,0 +1,8 @@
|
||||
<%= case @mediaType do %>
|
||||
<% "audio" -> %>
|
||||
<audio src="<%= @url %>" controls="controls"></audio>
|
||||
<% "video" -> %>
|
||||
<video src="<%= @url %>" controls="controls"></video>
|
||||
<% _ -> %>
|
||||
<img src="<%= @url %>" alt="<%= @name %>" title="<%= @name %>">
|
||||
<% end %>
|
76
lib/pleroma/web/templates/embed/show.html.eex
Normal file
76
lib/pleroma/web/templates/embed/show.html.eex
Normal file
@ -0,0 +1,76 @@
|
||||
<div>
|
||||
<div class="p-author h-card">
|
||||
<a class="u-url" rel="author noopener" href="<%= @author.ap_id %>">
|
||||
<div class="avatar">
|
||||
<img src="<%= User.avatar_url(@author) |> MediaProxy.url %>" width="48" height="48" alt="">
|
||||
</div>
|
||||
<span class="display-name" style="padding-left: 0.5em;">
|
||||
<bdi><%= raw (@author.name |> Formatter.emojify(@author.emoji)) %></bdi>
|
||||
<span class="nickname"><%= full_nickname(@author) %></span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="activity-content" >
|
||||
<%= if status_title(@activity) != "" do %>
|
||||
<details <%= if open_content?() do %>open<% end %>>
|
||||
<summary><%= raw status_title(@activity) %></summary>
|
||||
<div><%= activity_content(@activity) %></div>
|
||||
</details>
|
||||
<% else %>
|
||||
<div><%= activity_content(@activity) %></div>
|
||||
<% end %>
|
||||
<%= for %{"name" => name, "url" => [url | _]} <- attachments(@activity) do %>
|
||||
<div class="attachment">
|
||||
<%= if sensitive?(@activity) do %>
|
||||
<details class="nsfw">
|
||||
<summary onClick="updateHeight()"><%= Gettext.gettext("sensitive media") %></summary>
|
||||
<div class="nsfw-content">
|
||||
<%= render("_attachment.html", %{name: name, url: url["href"],
|
||||
mediaType: fetch_media_type(url)}) %>
|
||||
</div>
|
||||
</details>
|
||||
<% else %>
|
||||
<%= render("_attachment.html", %{name: name, url: url["href"],
|
||||
mediaType: fetch_media_type(url)}) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<dl class="counts pull-right">
|
||||
<dt><%= Gettext.gettext("replies") %></dt><dd><%= @counts.replies %></dd>
|
||||
<dt><%= Gettext.gettext("announces") %></dt><dd><%= @counts.announces %></dd>
|
||||
<dt><%= Gettext.gettext("likes") %></dt><dd><%= @counts.likes %></dd>
|
||||
</dl>
|
||||
|
||||
<p class="date pull-left">
|
||||
<%= link published(@activity), to: activity_url(@author, @activity) %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateHeight() {
|
||||
window.requestAnimationFrame(function(){
|
||||
var height = document.getElementsByTagName('html')[0].scrollHeight;
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'setHeightPleromaEmbed',
|
||||
id: window.parentId,
|
||||
height: height,
|
||||
}, '*');
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('message', function(e){
|
||||
var data = e.data || {};
|
||||
|
||||
if (!window.parent || data.type !== 'setHeightPleromaEmbed') {
|
||||
return;
|
||||
}
|
||||
|
||||
window.parentId = data.id
|
||||
|
||||
updateHeight()
|
||||
});
|
||||
</script>
|
15
lib/pleroma/web/templates/layout/embed.html.eex
Normal file
15
lib/pleroma/web/templates/layout/embed.html.eex
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />
|
||||
<title><%= Pleroma.Config.get([:instance, :name]) %></title>
|
||||
<meta content='noindex' name='robots'>
|
||||
<%= Phoenix.HTML.raw(assigns[:meta] || "") %>
|
||||
<link rel="stylesheet" href="/embed.css">
|
||||
<base target="_parent">
|
||||
</head>
|
||||
<body>
|
||||
<%= render @view_module, @view_template, assigns %>
|
||||
</body>
|
||||
</html>
|
74
lib/pleroma/web/views/embed_view.ex
Normal file
74
lib/pleroma/web/views/embed_view.ex
Normal file
@ -0,0 +1,74 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.EmbedView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Calendar.Strftime
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Emoji.Formatter
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Gettext
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.Metadata.Utils
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
|
||||
use Phoenix.HTML
|
||||
|
||||
@media_types ["image", "audio", "video"]
|
||||
|
||||
defp fetch_media_type(%{"mediaType" => mediaType}) do
|
||||
Utils.fetch_media_type(@media_types, mediaType)
|
||||
end
|
||||
|
||||
defp open_content? do
|
||||
Pleroma.Config.get(
|
||||
[:frontend_configurations, :collapse_message_with_subjects],
|
||||
true
|
||||
)
|
||||
end
|
||||
|
||||
defp full_nickname(user) do
|
||||
%{host: host} = URI.parse(user.ap_id)
|
||||
"@" <> user.nickname <> "@" <> host
|
||||
end
|
||||
|
||||
defp status_title(%Activity{object: %Object{data: %{"name" => name}}}) when is_binary(name),
|
||||
do: name
|
||||
|
||||
defp status_title(%Activity{object: %Object{data: %{"summary" => summary}}})
|
||||
when is_binary(summary),
|
||||
do: summary
|
||||
|
||||
defp status_title(_), do: nil
|
||||
|
||||
defp activity_content(%Activity{object: %Object{data: %{"content" => content}}}) do
|
||||
content |> Pleroma.HTML.filter_tags() |> raw()
|
||||
end
|
||||
|
||||
defp activity_content(_), do: nil
|
||||
|
||||
defp activity_url(%User{local: true}, activity) do
|
||||
Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
|
||||
end
|
||||
|
||||
defp activity_url(%User{local: false}, %Activity{object: %Object{data: data}}) do
|
||||
data["url"] || data["external_url"] || data["id"]
|
||||
end
|
||||
|
||||
defp attachments(%Activity{object: %Object{data: %{"attachment" => attachments}}}) do
|
||||
attachments
|
||||
end
|
||||
|
||||
defp sensitive?(%Activity{object: %Object{data: %{"sensitive" => sensitive}}}) do
|
||||
sensitive
|
||||
end
|
||||
|
||||
defp published(%Activity{object: %Object{data: %{"published" => published}}}) do
|
||||
published
|
||||
|> NaiveDateTime.from_iso8601!()
|
||||
|> Strftime.strftime!("%B %d, %Y, %l:%M %p")
|
||||
end
|
||||
end
|
@ -16,6 +16,8 @@ defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do
|
||||
def perform(_opts, _job) do
|
||||
if Config.get([:oauth2, :clean_expired_tokens], false) do
|
||||
Token.delete_expired_tokens()
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -37,6 +37,8 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
|
||||
)
|
||||
|> Repo.all()
|
||||
|> send_emails
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -55,7 +55,11 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
|
||||
|> Repo.all()
|
||||
|> Enum.map(&Pleroma.Emails.NewUsersDigestEmail.new_users(&1, users_and_statuses))
|
||||
|> Enum.each(&Pleroma.Emails.Mailer.deliver/1)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -23,6 +23,8 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
|
||||
def perform(_opts, _job) do
|
||||
if Config.get([ActivityExpiration, :enabled]) do
|
||||
Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
|
10
mix.lock
10
mix.lock
@ -12,7 +12,7 @@
|
||||
"calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "738d0e17a93c2ccfe4ddc707bdc8e672e9074c8569498483feb1c4530fb91b2b"},
|
||||
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]},
|
||||
"castore": {:hex, :castore, "0.1.5", "591c763a637af2cc468a72f006878584bc6c306f8d111ef8ba1d4c10e0684010", [:mix], [], "hexpm", "6db356b2bc6cc22561e051ff545c20ad064af57647e436650aa24d7d06cd941a"},
|
||||
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
|
||||
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
"comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
|
||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
|
||||
@ -50,12 +50,12 @@
|
||||
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
|
||||
"gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"},
|
||||
"gun": {:git, "https://github.com/ninenines/gun.git", "e1a69b36b180a574c0ac314ced9613fdd52312cc", [ref: "e1a69b36b180a574c0ac314ced9613fdd52312cc"]},
|
||||
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
|
||||
"hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},
|
||||
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
|
||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
|
||||
"idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"},
|
||||
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
|
||||
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
|
||||
"joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
|
||||
@ -102,7 +102,7 @@
|
||||
"recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"},
|
||||
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]},
|
||||
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
|
||||
"swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {: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", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"},
|
||||
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
||||
@ -112,7 +112,7 @@
|
||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||
"tzdata": {:hex, :tzdata, "0.5.22", "f2ba9105117ee0360eae2eca389783ef7db36d533899b2e84559404dbc77ebb8", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cd66c8a1e6a9e121d1f538b01bef459334bb4029a1ffb4eeeb5e4eae0337e7b6"},
|
||||
"ueberauth": {:hex, :ueberauth, "0.6.2", "25a31111249d60bad8b65438b2306a4dc91f3208faa62f5a8c33e8713989b2e8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "db9fbfb5ac707bc4f85a297758406340bf0358b4af737a88113c1a9eee120ac7"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
|
||||
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
|
||||
"web_push_encryption": {:hex, :web_push_encryption, "0.2.3", "a0ceab85a805a30852f143d22d71c434046fbdbafbc7292e7887cec500826a80", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "9315c8f37c108835cf3f8e9157d7a9b8f420a34f402d1b1620a31aed5b93ecdf"},
|
||||
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
|
||||
|
@ -3,14 +3,16 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-05-15 09:37+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"PO-Revision-Date: 2020-06-02 07:36+0000\n"
|
||||
"Last-Translator: Fristi <fristi@subcon.town>\n"
|
||||
"Language-Team: Dutch <https://translate.pleroma.social/projects/pleroma/"
|
||||
"pleroma/nl/>\n"
|
||||
"Language: nl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Translate Toolkit 2.5.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.0.4\n"
|
||||
|
||||
## This file is a PO Template file.
|
||||
##
|
||||
@ -23,142 +25,142 @@ msgstr ""
|
||||
## effect: edit them in PO (`.po`) files instead.
|
||||
## From Ecto.Changeset.cast/4
|
||||
msgid "can't be blank"
|
||||
msgstr ""
|
||||
msgstr "kan niet leeg zijn"
|
||||
|
||||
## From Ecto.Changeset.unique_constraint/3
|
||||
msgid "has already been taken"
|
||||
msgstr ""
|
||||
msgstr "is al bezet"
|
||||
|
||||
## From Ecto.Changeset.put_change/3
|
||||
msgid "is invalid"
|
||||
msgstr ""
|
||||
msgstr "is ongeldig"
|
||||
|
||||
## From Ecto.Changeset.validate_format/3
|
||||
msgid "has invalid format"
|
||||
msgstr ""
|
||||
msgstr "heeft een ongeldig formaat"
|
||||
|
||||
## From Ecto.Changeset.validate_subset/3
|
||||
msgid "has an invalid entry"
|
||||
msgstr ""
|
||||
msgstr "heeft een ongeldige entry"
|
||||
|
||||
## From Ecto.Changeset.validate_exclusion/3
|
||||
msgid "is reserved"
|
||||
msgstr ""
|
||||
msgstr "is gereserveerd"
|
||||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
msgstr ""
|
||||
msgstr "komt niet overeen met bevestiging"
|
||||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
msgstr ""
|
||||
msgstr "is nog geassocieerd met deze entry"
|
||||
|
||||
msgid "are still associated with this entry"
|
||||
msgstr ""
|
||||
msgstr "zijn nog geassocieerd met deze entry"
|
||||
|
||||
## From Ecto.Changeset.validate_length/3
|
||||
msgid "should be %{count} character(s)"
|
||||
msgid_plural "should be %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dient %{count} karakter te bevatten"
|
||||
msgstr[1] "dient %{count} karakters te bevatten"
|
||||
|
||||
msgid "should have %{count} item(s)"
|
||||
msgid_plural "should have %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dient %{count} item te bevatten"
|
||||
msgstr[1] "dient %{count} items te bevatten"
|
||||
|
||||
msgid "should be at least %{count} character(s)"
|
||||
msgid_plural "should be at least %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dient ten minste %{count} karakter te bevatten"
|
||||
msgstr[1] "dient ten minste %{count} karakters te bevatten"
|
||||
|
||||
msgid "should have at least %{count} item(s)"
|
||||
msgid_plural "should have at least %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dient ten minste %{count} item te bevatten"
|
||||
msgstr[1] "dient ten minste %{count} items te bevatten"
|
||||
|
||||
msgid "should be at most %{count} character(s)"
|
||||
msgid_plural "should be at most %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dient niet meer dan %{count} karakter te bevatten"
|
||||
msgstr[1] "dient niet meer dan %{count} karakters te bevatten"
|
||||
|
||||
msgid "should have at most %{count} item(s)"
|
||||
msgid_plural "should have at most %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "dient niet meer dan %{count} item te bevatten"
|
||||
msgstr[1] "dient niet meer dan %{count} items te bevatten"
|
||||
|
||||
## From Ecto.Changeset.validate_number/3
|
||||
msgid "must be less than %{number}"
|
||||
msgstr ""
|
||||
msgstr "dient kleiner te zijn dan %{number}"
|
||||
|
||||
msgid "must be greater than %{number}"
|
||||
msgstr ""
|
||||
msgstr "dient groter te zijn dan %{number}"
|
||||
|
||||
msgid "must be less than or equal to %{number}"
|
||||
msgstr ""
|
||||
msgstr "dient kleiner dan of gelijk te zijn aan %{number}"
|
||||
|
||||
msgid "must be greater than or equal to %{number}"
|
||||
msgstr ""
|
||||
msgstr "dient groter dan of gelijk te zijn aan %{number}"
|
||||
|
||||
msgid "must be equal to %{number}"
|
||||
msgstr ""
|
||||
msgstr "dient gelijk te zijn aan %{number}"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:421
|
||||
#, elixir-format
|
||||
msgid "Account not found"
|
||||
msgstr ""
|
||||
msgstr "Account niet gevonden"
|
||||
|
||||
#: lib/pleroma/web/common_api/common_api.ex:249
|
||||
#, elixir-format
|
||||
msgid "Already voted"
|
||||
msgstr ""
|
||||
msgstr "Al gestemd"
|
||||
|
||||
#: lib/pleroma/web/oauth/oauth_controller.ex:360
|
||||
#, elixir-format
|
||||
msgid "Bad request"
|
||||
msgstr ""
|
||||
msgstr "Bad request"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
|
||||
#, elixir-format
|
||||
msgid "Can't delete object"
|
||||
msgstr ""
|
||||
msgstr "Object kan niet verwijderd worden"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
|
||||
#, elixir-format
|
||||
msgid "Can't delete this post"
|
||||
msgstr ""
|
||||
msgstr "Bericht kan niet verwijderd worden"
|
||||
|
||||
#: lib/pleroma/web/controller_helper.ex:95
|
||||
#: lib/pleroma/web/controller_helper.ex:101
|
||||
#, elixir-format
|
||||
msgid "Can't display this activity"
|
||||
msgstr ""
|
||||
msgstr "Activiteit kan niet worden getoond"
|
||||
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227
|
||||
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
|
||||
#, elixir-format
|
||||
msgid "Can't find user"
|
||||
msgstr ""
|
||||
msgstr "Gebruiker kan niet gevonden worden"
|
||||
|
||||
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114
|
||||
#, elixir-format
|
||||
msgid "Can't get favorites"
|
||||
msgstr ""
|
||||
msgstr "Favorieten konden niet opgehaald worden"
|
||||
|
||||
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437
|
||||
#, elixir-format
|
||||
msgid "Can't like object"
|
||||
msgstr ""
|
||||
msgstr "Object kan niet geliked worden"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:556
|
||||
#, elixir-format
|
||||
msgid "Cannot post an empty status without attachments"
|
||||
msgstr ""
|
||||
msgstr "Status kan niet geplaatst worden zonder tekst of bijlagen"
|
||||
|
||||
#: lib/pleroma/web/common_api/utils.ex:504
|
||||
#, elixir-format
|
||||
msgid "Comment must be up to %{max_size} characters"
|
||||
msgstr ""
|
||||
msgstr "Opmerking dient maximaal %{max_size} karakters te bevatten"
|
||||
|
||||
#: lib/pleroma/config/config_db.ex:222
|
||||
#, elixir-format
|
||||
|
@ -0,0 +1,33 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddRecipientsContainBlockedDomainsFunction do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
|
||||
def up do
|
||||
statement = """
|
||||
CREATE OR REPLACE FUNCTION recipients_contain_blocked_domains(recipients varchar[], blocked_domains varchar[]) RETURNS boolean AS $$
|
||||
DECLARE
|
||||
recipient_domain varchar;
|
||||
recipient varchar;
|
||||
BEGIN
|
||||
FOREACH recipient IN ARRAY recipients LOOP
|
||||
recipient_domain = split_part(recipient, '/', 3)::varchar;
|
||||
|
||||
IF recipient_domain = ANY(blocked_domains) THEN
|
||||
RETURN TRUE;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
RETURN FALSE;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
"""
|
||||
|
||||
execute(statement)
|
||||
end
|
||||
|
||||
def down do
|
||||
execute(
|
||||
"drop function if exists recipients_contain_blocked_domains(recipients varchar[], blocked_domains varchar[])"
|
||||
)
|
||||
end
|
||||
end
|
7
priv/repo/migrations/20200526144426_add_apps_indexes.exs
Normal file
7
priv/repo/migrations/20200526144426_add_apps_indexes.exs
Normal file
@ -0,0 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddAppsIndexes do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create(index(:apps, [:client_id, :client_secret]))
|
||||
end
|
||||
end
|
@ -0,0 +1,8 @@
|
||||
defmodule Pleroma.Repo.Migrations.ChangeNotificationUserIndex do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
drop_if_exists(index(:notifications, [:user_id]))
|
||||
create_if_not_exists(index(:notifications, [:user_id, "id desc nulls last"]))
|
||||
end
|
||||
end
|
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/chunk-3384.d50ed383.css
Normal file
1
priv/static/adminfe/chunk-3384.d50ed383.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/chunk-e458.6c0703cb.css
Normal file
1
priv/static/adminfe/chunk-e458.6c0703cb.css
Normal file
@ -0,0 +1 @@
|
||||
.status-card{margin-bottom:10px}.status-card .account{text-decoration:underline;line-height:26px;font-size:13px}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;height:22px}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 4px 3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}.statuses-container{padding:0 15px}.statuses-container h1{margin:10px 0 15px}.statuses-container .status-container{margin:0 0 10px}.statuses-header-container .el-button.is-plain:focus,.statuses-header-container .el-button.is-plain:hover{border-color:#dcdfe6;color:#606266;cursor:default}.checkbox-container{margin-bottom:15px}.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 0 15px}.reboot-button{padding:10px;margin:0;width:145px}.select-instance{width:396px}.statuses-header,.statuses-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.statuses-pagination{padding:15px 0;text-align:center}@media only screen and (max-width:480px){.checkbox-container{margin-bottom:10px}.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:10px 0}.select-field{width:100%;margin-bottom:5px}.select-instance{width:100%}.statuses-header-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.statuses-header-container .el-button-group{width:100%}.statuses-header-container .el-button{padding:10px 6.5px;width:50%}.statuses-header-container .el-button-group>.el-button:first-child{border-bottom-left-radius:0}.statuses-header-container .el-button-group>.el-button:not(:first-child):not(:last-child).private-button{border-top-right-radius:4px}.statuses-header-container .el-button-group>.el-button:not(:first-child):not(:last-child).public-button{border-bottom-left-radius:4px;border-top:#fff}.statuses-header-container .el-button-group>.el-button:last-child{border-top-right-radius:0;border-top:#fff}.statuses-header-container .reboot-button{margin:10px 0 0}}
|
@ -1 +0,0 @@
|
||||
.status-card{margin-bottom:10px}.status-card .account{text-decoration:underline;line-height:26px;font-size:13px}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;height:22px}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 4px 3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}.statuses-container{padding:0 15px}.statuses-container h1{margin:10px 0 15px}.statuses-container .status-container{margin:0 0 10px}.checkbox-container{margin-bottom:15px}.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 0 15px}.reboot-button{padding:10px;margin:0;width:145px}.select-instance{width:396px}.statuses-header,.statuses-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.statuses-pagination{padding:15px 0;text-align:center}@media only screen and (max-width:480px){.checkbox-container{margin-bottom:10px}.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:10px 0}.select-field{width:100%;margin-bottom:5px}.select-instance{width:100%}.statuses-header-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.statuses-header-container .el-button{padding:10px 6.5px}.statuses-header-container .reboot-button{margin:10px 0 0}}
|
@ -1 +1 @@
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.796ca6d4.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.1b4f6ce0.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.203f69f8.js></script></body></html>
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.796ca6d4.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.b08eb412.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.0146039c.js></script></body></html>
|
2
priv/static/adminfe/static/js/app.0146039c.js
Normal file
2
priv/static/adminfe/static/js/app.0146039c.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/app.0146039c.js.map
Normal file
1
priv/static/adminfe/static/js/app.0146039c.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
priv/static/adminfe/static/js/chunk-3384.b2ebeeca.js
Normal file
2
priv/static/adminfe/static/js/chunk-3384.b2ebeeca.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/chunk-3384.b2ebeeca.js.map
Normal file
1
priv/static/adminfe/static/js/chunk-3384.b2ebeeca.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
priv/static/adminfe/static/js/chunk-7e30.ec42e302.js
Normal file
2
priv/static/adminfe/static/js/chunk-7e30.ec42e302.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/chunk-7e30.ec42e302.js.map
Normal file
1
priv/static/adminfe/static/js/chunk-7e30.ec42e302.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
priv/static/adminfe/static/js/chunk-e458.bb460d81.js
Normal file
2
priv/static/adminfe/static/js/chunk-e458.bb460d81.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/chunk-e458.bb460d81.js.map
Normal file
1
priv/static/adminfe/static/js/chunk-e458.bb460d81.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1,2 +1,2 @@
|
||||
!function(e){function n(n){for(var r,c,a=n[0],f=n[1],h=n[2],i=0,l=[];i<a.length;i++)c=a[i],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);for(d&&d(n);l.length;)l.shift()();return o.push.apply(o,h||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var f=t[c];0!==u[f]&&(r=!1)}r&&(o.splice(n--,1),e=a(a.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var n=[];c[e]?n.push(c[e]):0!==c[e]&&{"chunk-6b68":1,"chunk-d38a":1,"chunk-0558":1,"chunk-0778":1,"chunk-3384":1,"chunk-6e81":1,"chunk-4011":1,"chunk-970d":1,"chunk-22d2":1,"chunk-e458":1,"chunk-7637":1,"chunk-0961":1}[e]&&n.push(c[e]=new Promise(function(n,t){for(var r=({}[e]||e)+"."+{"7zzA":"31d6cfe0",JEtC:"31d6cfe0",ZhIB:"31d6cfe0","chunk-6b68":"0cc00484","chunk-d38a":"cabdc22e","chunk-0558":"af0d89cd","chunk-0778":"d9e7180a","chunk-3384":"2278f87c","chunk-6e81":"0e80d020","chunk-7f9e":"31d6cfe0","chunk-4011":"c4799067","chunk-df62":"31d6cfe0","chunk-970d":"f59cca8c","chunk-22d2":"813009b9","chunk-e458":"f88bafea","chunk-7637":"941c4edb",oAJy:"31d6cfe0","chunk-0961":"d3692214","chunk-16d0":"31d6cfe0"}[e]+".css",c=a.p+r,u=document.getElementsByTagName("link"),o=0;o<u.length;o++){var f=(i=u[o]).getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(f===r||f===c))return n()}var h=document.getElementsByTagName("style");for(o=0;o<h.length;o++){var i;if((f=(i=h[o]).getAttribute("data-href"))===r||f===c)return n()}var d=document.createElement("link");d.rel="stylesheet",d.type="text/css",d.onload=n,d.onerror=function(n){var r=n&&n.target&&n.target.src||c,u=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");u.request=r,t(u)},d.href=c,document.getElementsByTagName("head")[0].appendChild(d)}).then(function(){c[e]=0}));var t=u[e];if(0!==t)if(t)n.push(t[2]);else{var r=new Promise(function(n,r){t=u[e]=[n,r]});n.push(t[2]=r);var o,f=document.createElement("script");f.charset="utf-8",f.timeout=120,a.nc&&f.setAttribute("nonce",a.nc),f.src=function(e){return a.p+"static/js/"+({}[e]||e)+"."+{"7zzA":"e1ae1c94",JEtC:"f9ba4594",ZhIB:"861df339","chunk-6b68":"fbc0f684","chunk-d38a":"a851004a","chunk-0558":"75954137","chunk-0778":"b17650df","chunk-3384":"458ffaf1","chunk-6e81":"3733ace2","chunk-7f9e":"c49aa694","chunk-4011":"67fb1692","chunk-df62":"6c5105a6","chunk-970d":"2457e066","chunk-22d2":"a0cf7976","chunk-e458":"4e5aad44","chunk-7637":"8f5fb36e",oAJy:"840fb1c2","chunk-0961":"ef33e81b","chunk-16d0":"6ce78978"}[e]+".js"}(e),o=function(n){f.onerror=f.onload=null,clearTimeout(h);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var h=setTimeout(function(){o({type:"timeout",target:f})},12e4);f.onerror=f.onload=o,document.head.appendChild(f)}return Promise.all(n)},a.m=e,a.c=r,a.d=function(e,n,t){a.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,n){if(1&n&&(e=a(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)a.d(t,r,function(n){return e[n]}.bind(null,r));return t},a.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(n,"a",n),n},a.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},a.p="",a.oe=function(e){throw console.error(e),e};var f=window.webpackJsonp=window.webpackJsonp||[],h=f.push.bind(f);f.push=n,f=f.slice();for(var i=0;i<f.length;i++)n(f[i]);var d=h;t()}([]);
|
||||
//# sourceMappingURL=runtime.1b4f6ce0.js.map
|
||||
!function(e){function n(n){for(var r,c,a=n[0],f=n[1],h=n[2],i=0,l=[];i<a.length;i++)c=a[i],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);for(d&&d(n);l.length;)l.shift()();return o.push.apply(o,h||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var f=t[c];0!==u[f]&&(r=!1)}r&&(o.splice(n--,1),e=a(a.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var n=[];c[e]?n.push(c[e]):0!==c[e]&&{"chunk-6b68":1,"chunk-d38a":1,"chunk-0558":1,"chunk-0778":1,"chunk-3384":1,"chunk-6e81":1,"chunk-7e30":1,"chunk-e458":1,"chunk-970d":1,"chunk-22d2":1,"chunk-7637":1,"chunk-0961":1}[e]&&n.push(c[e]=new Promise(function(n,t){for(var r=({}[e]||e)+"."+{"7zzA":"31d6cfe0",JEtC:"31d6cfe0",ZhIB:"31d6cfe0","chunk-6b68":"0cc00484","chunk-d38a":"cabdc22e","chunk-0558":"af0d89cd","chunk-0778":"d9e7180a","chunk-3384":"d50ed383","chunk-6e81":"0e80d020","chunk-7f9e":"31d6cfe0","chunk-7e30":"f2b9674a","chunk-df62":"31d6cfe0","chunk-e458":"6c0703cb","chunk-970d":"f59cca8c","chunk-22d2":"813009b9","chunk-7637":"941c4edb",oAJy:"31d6cfe0","chunk-0961":"d3692214","chunk-16d0":"31d6cfe0"}[e]+".css",c=a.p+r,u=document.getElementsByTagName("link"),o=0;o<u.length;o++){var f=(i=u[o]).getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(f===r||f===c))return n()}var h=document.getElementsByTagName("style");for(o=0;o<h.length;o++){var i;if((f=(i=h[o]).getAttribute("data-href"))===r||f===c)return n()}var d=document.createElement("link");d.rel="stylesheet",d.type="text/css",d.onload=n,d.onerror=function(n){var r=n&&n.target&&n.target.src||c,u=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");u.request=r,t(u)},d.href=c,document.getElementsByTagName("head")[0].appendChild(d)}).then(function(){c[e]=0}));var t=u[e];if(0!==t)if(t)n.push(t[2]);else{var r=new Promise(function(n,r){t=u[e]=[n,r]});n.push(t[2]=r);var o,f=document.createElement("script");f.charset="utf-8",f.timeout=120,a.nc&&f.setAttribute("nonce",a.nc),f.src=function(e){return a.p+"static/js/"+({}[e]||e)+"."+{"7zzA":"e1ae1c94",JEtC:"f9ba4594",ZhIB:"861df339","chunk-6b68":"fbc0f684","chunk-d38a":"a851004a","chunk-0558":"75954137","chunk-0778":"b17650df","chunk-3384":"b2ebeeca","chunk-6e81":"3733ace2","chunk-7f9e":"c49aa694","chunk-7e30":"ec42e302","chunk-df62":"6c5105a6","chunk-e458":"bb460d81","chunk-970d":"2457e066","chunk-22d2":"a0cf7976","chunk-7637":"8f5fb36e",oAJy:"840fb1c2","chunk-0961":"ef33e81b","chunk-16d0":"6ce78978"}[e]+".js"}(e),o=function(n){f.onerror=f.onload=null,clearTimeout(h);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var h=setTimeout(function(){o({type:"timeout",target:f})},12e4);f.onerror=f.onload=o,document.head.appendChild(f)}return Promise.all(n)},a.m=e,a.c=r,a.d=function(e,n,t){a.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,n){if(1&n&&(e=a(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)a.d(t,r,function(n){return e[n]}.bind(null,r));return t},a.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(n,"a",n),n},a.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},a.p="",a.oe=function(e){throw console.error(e),e};var f=window.webpackJsonp=window.webpackJsonp||[],h=f.push.bind(f);f.push=n,f=f.slice();for(var i=0;i<f.length;i++)n(f[i]);var d=h;t()}([]);
|
||||
//# sourceMappingURL=runtime.b08eb412.js.map
|
File diff suppressed because one or more lines are too long
115
priv/static/embed.css
Normal file
115
priv/static/embed.css
Normal file
@ -0,0 +1,115 @@
|
||||
body {
|
||||
background-color: #282c37;
|
||||
font-family: sans-serif;
|
||||
color: white;
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
float: left;
|
||||
border-radius: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.activity-content {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.attachment {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.attachment img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.date a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.date a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.date a,
|
||||
.counts {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.counts dt,
|
||||
.counts dd {
|
||||
float: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.h-card {
|
||||
min-height: 48px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.h-card a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.h-card a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
padding-top: 4px;
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* keep emoji from being hilariously huge */
|
||||
.display-name img {
|
||||
max-height: 1em;
|
||||
}
|
||||
|
||||
.display-name .nickname {
|
||||
padding-top: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nickname:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
a.button {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
color: white;
|
||||
background-color: #419bdd;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
font-weight: 500;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
a.button:hover {
|
||||
text-decoration: none;
|
||||
background-color: #61a6d9;
|
||||
}
|
43
priv/static/embed.js
Normal file
43
priv/static/embed.js
Normal file
@ -0,0 +1,43 @@
|
||||
(function () {
|
||||
'use strict'
|
||||
|
||||
var ready = function (loaded) {
|
||||
if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {
|
||||
loaded()
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', loaded)
|
||||
}
|
||||
}
|
||||
|
||||
ready(function () {
|
||||
var iframes = []
|
||||
|
||||
window.addEventListener('message', function (e) {
|
||||
var data = e.data || {}
|
||||
|
||||
if (data.type !== 'setHeightPleromaEmbed' || !iframes[data.id]) {
|
||||
return
|
||||
}
|
||||
|
||||
iframes[data.id].height = data.height
|
||||
});
|
||||
|
||||
[].forEach.call(document.querySelectorAll('iframe.pleroma-embed'), function (iframe) {
|
||||
iframe.scrolling = 'no'
|
||||
iframe.style.overflow = 'hidden'
|
||||
|
||||
iframes.push(iframe)
|
||||
|
||||
var id = iframes.length - 1
|
||||
|
||||
iframe.onload = function () {
|
||||
iframe.contentWindow.postMessage({
|
||||
type: 'setHeightPleromaEmbed',
|
||||
id: id
|
||||
}, '*')
|
||||
}
|
||||
|
||||
iframe.onload()
|
||||
})
|
||||
})
|
||||
})()
|
@ -31,17 +31,5 @@ defmodule Pleroma.HTTP.AdapterHelper.HackneyTest do
|
||||
assert opts[:b] == 1
|
||||
refute Keyword.has_key?(opts, :proxy)
|
||||
end
|
||||
|
||||
test "add opts for https" do
|
||||
uri = URI.parse("https://domain.com")
|
||||
|
||||
opts = Hackney.options(uri)
|
||||
|
||||
assert opts[:ssl_options] == [
|
||||
partial_chain: &:hackney_connect.partial_chain/1,
|
||||
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
|
||||
server_name_indication: 'domain.com'
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
3
test/instance_static/local_pack/files.json
Normal file
3
test/instance_static/local_pack/files.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"blank": "blank.png"
|
||||
}
|
10
test/instance_static/local_pack/manifest.json
Normal file
10
test/instance_static/local_pack/manifest.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"local": {
|
||||
"src_sha256": "384025A1AC6314473863A11AC7AB38A12C01B851A3F82359B89B4D4211D3291D",
|
||||
"src": "test/fixtures/emoji/packs/blank.png.zip",
|
||||
"license": "Apache 2.0",
|
||||
"homepage": "https://example.com",
|
||||
"files": "files.json",
|
||||
"description": "Some local pack"
|
||||
}
|
||||
}
|
@ -454,8 +454,7 @@ defmodule Pleroma.NotificationTest do
|
||||
status: "hey again @#{other_user.nickname}!"
|
||||
})
|
||||
|
||||
[n2, n1] = notifs = Notification.for_user(other_user)
|
||||
assert length(notifs) == 2
|
||||
[n2, n1] = Notification.for_user(other_user)
|
||||
|
||||
assert n2.id > n1.id
|
||||
|
||||
@ -464,7 +463,9 @@ defmodule Pleroma.NotificationTest do
|
||||
status: "hey yet again @#{other_user.nickname}!"
|
||||
})
|
||||
|
||||
Notification.set_read_up_to(other_user, n2.id)
|
||||
[_, read_notification] = Notification.set_read_up_to(other_user, n2.id)
|
||||
|
||||
assert read_notification.activity.object
|
||||
|
||||
[n3, n2, n1] = Notification.for_user(other_user)
|
||||
|
||||
@ -972,7 +973,9 @@ defmodule Pleroma.NotificationTest do
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"})
|
||||
|
||||
assert length(Notification.for_user(user)) == 1
|
||||
[notification] = Notification.for_user(user)
|
||||
|
||||
assert notification.activity.object
|
||||
end
|
||||
|
||||
test "it doesn't return notifications for muted user with notifications" do
|
||||
|
@ -68,6 +68,7 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest do
|
||||
assert "$pbkdf2" <> _ = user.password_hash
|
||||
end
|
||||
|
||||
@tag :skip_on_mac
|
||||
test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
|
||||
user =
|
||||
insert(:user,
|
||||
|
@ -67,7 +67,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
|
||||
|
||||
[csp] = Conn.get_resp_header(conn, "content-security-policy")
|
||||
|
||||
assert csp =~ ~r|report-uri https://endpoint.com; report-to csp-endpoint;|
|
||||
assert csp =~ ~r|report-uri https://endpoint.com;report-to csp-endpoint;|
|
||||
|
||||
[reply_to] = Conn.get_resp_header(conn, "reply-to")
|
||||
|
||||
|
@ -34,7 +34,8 @@ defmodule Pleroma.Factory do
|
||||
last_digest_emailed_at: NaiveDateTime.utc_now(),
|
||||
last_refreshed_at: NaiveDateTime.utc_now(),
|
||||
notification_settings: %Pleroma.User.NotificationSetting{},
|
||||
multi_factor_authentication_settings: %Pleroma.MFA.Settings{}
|
||||
multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
|
||||
ap_enabled: true
|
||||
}
|
||||
|
||||
%{
|
||||
|
@ -73,6 +73,19 @@ defmodule Mix.Tasks.Pleroma.EmojiTest do
|
||||
on_exit(fn -> File.rm_rf!("test/instance_static/emoji/finmoji") end)
|
||||
end
|
||||
|
||||
test "install local emoji pack" do
|
||||
assert capture_io(fn ->
|
||||
Emoji.run([
|
||||
"get-packs",
|
||||
"local",
|
||||
"--manifest",
|
||||
"test/instance_static/local_pack/manifest.json"
|
||||
])
|
||||
end) =~ "Writing pack.json for"
|
||||
|
||||
on_exit(fn -> File.rm_rf!("test/instance_static/emoji/local") end)
|
||||
end
|
||||
|
||||
test "pack not found" do
|
||||
mock(fn
|
||||
%{
|
||||
|
@ -65,7 +65,8 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
|
||||
"type" => "Undo",
|
||||
"actor_id" => follower_id,
|
||||
"limit" => 1,
|
||||
"skip_preload" => true
|
||||
"skip_preload" => true,
|
||||
"invisible_actors" => true
|
||||
})
|
||||
|
||||
assert undo_activity.data["type"] == "Undo"
|
||||
|
@ -586,6 +586,26 @@ defmodule Pleroma.UserTest do
|
||||
|
||||
refute user.last_refreshed_at == orig_user.last_refreshed_at
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test "it returns the old user if stale, but unfetchable" do
|
||||
a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)
|
||||
|
||||
orig_user =
|
||||
insert(
|
||||
:user,
|
||||
local: false,
|
||||
nickname: "admin@mastodon.example.org",
|
||||
ap_id: "http://mastodon.example.org/users/raymoo",
|
||||
last_refreshed_at: a_week_ago
|
||||
)
|
||||
|
||||
assert orig_user.last_refreshed_at == a_week_ago
|
||||
|
||||
{:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/raymoo")
|
||||
|
||||
assert user.last_refreshed_at == orig_user.last_refreshed_at
|
||||
end
|
||||
end
|
||||
|
||||
test "returns an ap_id for a user" do
|
||||
@ -1782,7 +1802,7 @@ defmodule Pleroma.UserTest do
|
||||
user = insert(:user)
|
||||
assert User.avatar_url(user) =~ "/images/avi.png"
|
||||
|
||||
Pleroma.Config.put([:assets, :default_user_avatar], "avatar.png")
|
||||
clear_config([:assets, :default_user_avatar], "avatar.png")
|
||||
|
||||
user = User.get_cached_by_nickname_or_id(user.nickname)
|
||||
assert User.avatar_url(user) =~ "avatar.png"
|
||||
|
@ -451,6 +451,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||
assert Activity.get_by_ap_id(data["id"])
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test "it inserts an incoming activity into the database" <>
|
||||
"even if we can't fetch the user but have it in our db",
|
||||
%{conn: conn} do
|
||||
user =
|
||||
insert(:user,
|
||||
ap_id: "https://mastodon.example.org/users/raymoo",
|
||||
ap_enabled: true,
|
||||
local: false,
|
||||
last_refreshed_at: nil
|
||||
)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||
|> Poison.decode!()
|
||||
|> Map.put("actor", user.ap_id)
|
||||
|> put_in(["object", "attridbutedTo"], user.ap_id)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|
||||
assert "ok" == json_response(conn, 200)
|
||||
|
||||
ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
|
||||
assert Activity.get_by_ap_id(data["id"])
|
||||
end
|
||||
|
||||
test "it clears `unreachable` federation status of the sender", %{conn: conn} do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user