forked from YokaiRick/akkoma
Merge branch 'develop' into openapi/admin/relay
This commit is contained in:
commit
68cb152a08
113 changed files with 2650 additions and 1157 deletions
|
@ -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 @@ def generate(user, opts \\ []) 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 @@ def generate(user, opts \\ []) 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 @@ def generate_tagged_activities(opts \\ []) 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 @@ defp generate_long_thread(visibility, user, friends, non_friends, _opts) 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,47 +183,46 @@ defp generate_activities(user, friends, non_friends, task_data, opts) 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],
|
||||
do: ["@" <> users[: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
|
||||
})
|
||||
end
|
||||
|
||||
defp insert_activity("hell_thread", visibility, group, user, friends, non_friends, _opts) do
|
||||
mentions =
|
||||
defp hell_thread_mentions(users) do
|
||||
with {:ok, nil} <- Cachex.get(:user_cache, "hell_thread_mentions") do
|
||||
cached =
|
||||
([user | Enum.take(friends, 10)] ++ Enum.take(non_friends, 10))
|
||||
@groups
|
||||
|> Enum.reduce([users[:user]], fn group, acc ->
|
||||
acc ++ Enum.take(users[group], 5)
|
||||
end)
|
||||
|> Enum.map(&"@#{&1.nickname}")
|
||||
|> Enum.join(", ")
|
||||
|
||||
|
@ -231,18 +231,56 @@ defp insert_activity("hell_thread", visibility, group, user, friends, non_friend
|
|||
else
|
||||
{:ok, cached} -> cached
|
||||
end
|
||||
|
||||
{:ok, _activity} =
|
||||
group
|
||||
|> get_actor(user, friends, non_friends)
|
||||
|> CommonAPI.post(%{
|
||||
status: mentions <> " hell thread status",
|
||||
visibility: visibility
|
||||
})
|
||||
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 @@ defp insert_activity("attachment", visibility, group, user, friends, non_friends
|
|||
})
|
||||
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 @@ defp insert_activity("simple_thread", "direct", group, user, friends, non_friend
|
|||
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 @@ defp other_data(actor) do
|
|||
"attributedTo" => actor.ap_id,
|
||||
"bcc" => [],
|
||||
"bto" => [],
|
||||
"content" => "Remote post",
|
||||
"content" => content,
|
||||
"context" => context_id,
|
||||
"conversation" => context_id,
|
||||
"emoji" => %{},
|
||||
|
@ -475,51 +490,65 @@ defp prepare_activity_data(_actor, "direct", mention) 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"],
|
||||
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(visibility, group)
|
||||
when visibility in ["unlisted", "private"] and group in @friends_groups do
|
||||
[:user, :friends_remote, :friends_local, :user]
|
||||
end
|
||||
|
||||
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: ~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("direct", :user), do: [:friends_local, :user, :friends_remote]
|
||||
|
||||
defp insert_replies(tasks, visibility, user, friends, non_friends, acc) do
|
||||
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 @@ defp fetch_timelines(user) 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 @@ defp fetch_public_timeline(user, :only_media) 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 @@ def generate(opts \\ []) 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 @@ defp run_stream(users, main_user) 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 @@ def run(_args) do
|
|||
Benchee.run(
|
||||
%{
|
||||
"Hashtag fetching, any" => fn tags ->
|
||||
TimelineController.hashtag_fetching(
|
||||
hashtag_fetching(
|
||||
%{
|
||||
"any" => tags
|
||||
},
|
||||
|
@ -47,7 +46,7 @@ def run(_args) 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 @@ def run(_args) do
|
|||
Benchee.run(
|
||||
%{
|
||||
"Hashtag fetching" => fn tag ->
|
||||
TimelineController.hashtag_fetching(
|
||||
hashtag_fetching(
|
||||
%{
|
||||
"tag" => tag
|
||||
},
|
||||
|
@ -80,4 +79,35 @@ def run(_args) 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 @@
|
|||
"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 @@
|
|||
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, :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 @@ def run(["remove_embedded_objects" | args]) 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 @@ def run(["prune_objects" | args]) 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 @@ def run(["fix_likes_collections"]) do
|
|||
end)
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
def run(["vacuum", args]) do
|
||||
start_pleroma()
|
||||
|
||||
Maintenance.vacuum(args)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ def run(["ls-packs" | args]) 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 @@ def run(["get-packs" | args]) 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 @@ def run(["get-packs" | args]) 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 @@ def run(["get-packs" | args]) 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 @@ def run(["get-packs" | args]) 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 @@ def run(["gen-pack" | args]) 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)
|
||||
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 @@ def by_ap_id(query \\ Activity, ap_id) 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 @@ def create_or_bump_for(activity, opts \\ []) 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 @@ defp download_archive(url, sha) 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 @@ def options(connection_opts \\ [], %URI{} = uri) 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 @@ def for_user_query(user, opts \\ %{}) 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 @@ def set_read_up_to(%{id: user_id} = user, id) 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 @@ def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, lo
|
|||
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 @@ defp headers 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 @@ defp headers 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 @@ defp csp_string 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 @@ def update_as_admin_changeset(struct, params) 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 @@ def password_update_changeset(struct, params) 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 @@ def increment_unread_conversation_count(conversation, %User{local: true} = user)
|
|||
|
||||
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 @@ def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
|
|||
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
|
||||
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}
|
||||
else
|
||||
fetch_by_ap_id(ap_id)
|
||||
|
||||
{%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 @@ defp compose_query({key, value}, query)
|
|||
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 @@ def fetch_latest_activity_id_for_context(context, opts \\ %{}) 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 @@ defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) 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 @@ defp exclude_poll_votes(query, _) 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 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> restrict_instance(opts)
|
||||
|> Activity.restrict_deactivated_users()
|
||||
|> exclude_poll_votes(opts)
|
||||
|> exclude_invisible_actors(opts)
|
||||
|> exclude_visibility(opts)
|
||||
end
|
||||
|
||||
|
@ -1145,7 +1176,7 @@ def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) 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 @@ def like(actor, object) 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 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) 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)
|
||||
|
||||
if !User.is_internal_user?(user) do
|
||||
Notification.create_notifications(object)
|
||||
ActivityPub.stream_out(object)
|
||||
end
|
||||
|
||||
{:ok, object, meta}
|
||||
end
|
||||
|
|
|
@ -1045,10 +1045,14 @@ def add_hashtags(object) 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 @@ def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname"
|
|||
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 @@ def update_user_credentials(
|
|||
%{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 @@ def update_user_credentials(
|
|||
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 @@ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" =
|
|||
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 @@ def index(%{assigns: %{user: _admin}} = conn, params) 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 @@ def user(params \\ %{}) 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 @@ def user(params \\ %{}) do
|
|||
count = Repo.aggregate(query, :count, :id)
|
||||
|
||||
results = Repo.all(paginated_query)
|
||||
|
||||
{:ok, results, count}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -80,24 +80,6 @@ def render("show.json", %{user: user}) 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 @@ defp instance 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 @@ def feed(conn, %{"nickname" => nickname} = params) 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 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _) 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 @@ def update_credentials(%{assigns: %{user: original_user}, body_params: params} =
|
|||
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 @@ def mark_as_read(%{assigns: %{user: user}} = conn, %{id: participation_id}) 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 @@ def public(%{assigns: %{user: user}} = conn, params) 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 @@ defp do_render("show.json", %{user: user} = opts) 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 @@ def register_changeset(struct, params \\ %{}) 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 @@ def update(params) 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 @@ defp update_scopes(%__MODULE__{} = app, scopes) 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 @@ def search(params) 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 @@ def download(%{body_params: %{url: url, name: name} = params} = conn, _) 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 @@ def filtered_by_user?(%User{} = user, %Activity{} = item) 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 @@ def perform(_opts, _job) do
|
|||
)
|
||||
|> Repo.all()
|
||||
|> send_emails
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -55,7 +55,11 @@ def perform(_args, _job) do
|
|||
|> Repo.all()
|
||||
|> Enum.map(&Pleroma.Emails.NewUsersDigestEmail.new_users(&1, users_and_statuses))
|
||||
|> Enum.each(&Pleroma.Emails.Mailer.deliver/1)
|
||||
end
|
||||
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 @@ test "respect connection opts and no proxy", %{uri: uri} 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 @@ test "it sets all notifications as read up to a specified notification ID" 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 @@ test "it sets all notifications as read up to a specified notification ID" 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 @@ test "it returns notifications for muted user without notifications" 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 @@ test "with a bcrypt hash, it updates to a pkbdf2 hash", %{conn: conn} 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,
|
||||
|
|
|
@ -34,7 +34,8 @@ def user_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 @@ test "download pack from default manifest" 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 @@ test "relay is unfollowed" 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 @@ test "updates an existing user, if stale" 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 @@ test "avatar fallback" 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 @@ test "it inserts an incoming activity into the database", %{conn: conn} 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!()
|
||||
|
||||
|
|
|
@ -108,6 +108,7 @@ test "returns error when object is unknown" do
|
|||
assert {:ok, %Activity{} = activity} = Relay.publish(note)
|
||||
assert activity.data["type"] == "Announce"
|
||||
assert activity.data["actor"] == service_actor.ap_id
|
||||
assert activity.data["to"] == [service_actor.follower_address]
|
||||
assert called(Pleroma.Web.Federator.publish(activity))
|
||||
end
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue