Merge remote-tracking branch 'pleroma/develop' into feature/addressable-lists
This commit is contained in:
commit
64a946643e
75 changed files with 2247 additions and 1365 deletions
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
alias Pleroma.Conversation
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Repo
|
||||
|
|
@ -125,6 +126,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
{:ok, map} <- MRF.filter(map),
|
||||
{recipients, _, _} = get_recipients(map),
|
||||
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
|
||||
:ok <- Containment.contain_child(map),
|
||||
{:ok, map, object} <- insert_full_object(map) do
|
||||
{:ok, activity} =
|
||||
Repo.insert(%Activity{
|
||||
|
|
@ -1011,6 +1013,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
avatar: avatar,
|
||||
name: data["name"],
|
||||
follower_address: data["followers"],
|
||||
following_address: data["following"],
|
||||
bio: data["summary"]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,43 +103,57 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
end
|
||||
end
|
||||
|
||||
def following(conn, %{"nickname" => nickname, "page" => page}) do
|
||||
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- User.ensure_keys_present(user) do
|
||||
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
|
||||
{:show_follows, true} <-
|
||||
{:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
|
||||
{page, _} = Integer.parse(page)
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(UserView.render("following.json", %{user: user, page: page}))
|
||||
|> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
|
||||
else
|
||||
{:show_follows, _} ->
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> send_resp(403, "")
|
||||
end
|
||||
end
|
||||
|
||||
def following(conn, %{"nickname" => nickname}) do
|
||||
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- User.ensure_keys_present(user) do
|
||||
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(UserView.render("following.json", %{user: user}))
|
||||
|> json(UserView.render("following.json", %{user: user, for: for_user}))
|
||||
end
|
||||
end
|
||||
|
||||
def followers(conn, %{"nickname" => nickname, "page" => page}) do
|
||||
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- User.ensure_keys_present(user) do
|
||||
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
|
||||
{:show_followers, true} <-
|
||||
{:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
|
||||
{page, _} = Integer.parse(page)
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(UserView.render("followers.json", %{user: user, page: page}))
|
||||
|> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
|
||||
else
|
||||
{:show_followers, _} ->
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> send_resp(403, "")
|
||||
end
|
||||
end
|
||||
|
||||
def followers(conn, %{"nickname" => nickname}) do
|
||||
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, user} <- User.ensure_keys_present(user) do
|
||||
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(UserView.render("followers.json", %{user: user}))
|
||||
|> json(UserView.render("followers.json", %{user: user, for: for_user}))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -325,4 +339,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
|
||||
conn
|
||||
end
|
||||
|
||||
defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
|
||||
{:ok, new_user} = User.ensure_keys_present(user)
|
||||
|
||||
for_user =
|
||||
if new_user != user and match?(%User{}, for_user) do
|
||||
User.get_cached_by_nickname(for_user.nickname)
|
||||
else
|
||||
for_user
|
||||
end
|
||||
|
||||
{new_user, for_user}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1090,6 +1090,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
|
||||
end
|
||||
|
||||
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
||||
update_following_followers_counters(user)
|
||||
end
|
||||
|
||||
{:ok, user}
|
||||
else
|
||||
%User{} = user -> {:ok, user}
|
||||
|
|
@ -1122,4 +1126,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
data
|
||||
|> maybe_fix_user_url
|
||||
end
|
||||
|
||||
def update_following_followers_counters(user) do
|
||||
info = %{}
|
||||
|
||||
following = fetch_counter(user.following_address)
|
||||
info = if following, do: Map.put(info, :following_count, following), else: info
|
||||
|
||||
followers = fetch_counter(user.follower_address)
|
||||
info = if followers, do: Map.put(info, :follower_count, followers), else: info
|
||||
|
||||
User.set_info_cache(user, info)
|
||||
end
|
||||
|
||||
defp fetch_counter(url) do
|
||||
with {:ok, %{body: body, status: code}} when code in 200..299 <-
|
||||
Pleroma.HTTP.get(
|
||||
url,
|
||||
[{:Accept, "application/activity+json"}]
|
||||
),
|
||||
{:ok, data} <- Jason.decode(body) do
|
||||
data["totalItems"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -98,29 +98,31 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("following.json", %{user: user, page: page}) do
|
||||
def render("following.json", %{user: user, page: page} = opts) do
|
||||
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
|
||||
query = User.get_friends_query(user)
|
||||
query = from(user in query, select: [:ap_id])
|
||||
following = Repo.all(query)
|
||||
|
||||
total =
|
||||
if !user.info.hide_follows do
|
||||
if showing do
|
||||
length(following)
|
||||
else
|
||||
0
|
||||
end
|
||||
|
||||
collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
|
||||
collection(following, "#{user.ap_id}/following", page, showing, total)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("following.json", %{user: user}) do
|
||||
def render("following.json", %{user: user} = opts) do
|
||||
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
|
||||
query = User.get_friends_query(user)
|
||||
query = from(user in query, select: [:ap_id])
|
||||
following = Repo.all(query)
|
||||
|
||||
total =
|
||||
if !user.info.hide_follows do
|
||||
if showing do
|
||||
length(following)
|
||||
else
|
||||
0
|
||||
|
|
@ -130,34 +132,43 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"id" => "#{user.ap_id}/following",
|
||||
"type" => "OrderedCollection",
|
||||
"totalItems" => total,
|
||||
"first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
|
||||
"first" =>
|
||||
if showing do
|
||||
collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
|
||||
else
|
||||
"#{user.ap_id}/following?page=1"
|
||||
end
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("followers.json", %{user: user, page: page}) do
|
||||
def render("followers.json", %{user: user, page: page} = opts) do
|
||||
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
|
||||
|
||||
query = User.get_followers_query(user)
|
||||
query = from(user in query, select: [:ap_id])
|
||||
followers = Repo.all(query)
|
||||
|
||||
total =
|
||||
if !user.info.hide_followers do
|
||||
if showing do
|
||||
length(followers)
|
||||
else
|
||||
0
|
||||
end
|
||||
|
||||
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
|
||||
collection(followers, "#{user.ap_id}/followers", page, showing, total)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("followers.json", %{user: user}) do
|
||||
def render("followers.json", %{user: user} = opts) do
|
||||
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
|
||||
|
||||
query = User.get_followers_query(user)
|
||||
query = from(user in query, select: [:ap_id])
|
||||
followers = Repo.all(query)
|
||||
|
||||
total =
|
||||
if !user.info.hide_followers do
|
||||
if showing do
|
||||
length(followers)
|
||||
else
|
||||
0
|
||||
|
|
@ -168,7 +179,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"type" => "OrderedCollection",
|
||||
"totalItems" => total,
|
||||
"first" =>
|
||||
collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
|
||||
if showing do
|
||||
collection(followers, "#{user.ap_id}/followers", 1, showing, total)
|
||||
else
|
||||
"#{user.ap_id}/followers?page=1"
|
||||
end
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
|
|
|||
|
|
@ -371,13 +371,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
|
||||
updated =
|
||||
Enum.map(configs, fn
|
||||
%{"group" => group, "key" => key, "value" => value} ->
|
||||
{:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
|
||||
config
|
||||
|
||||
%{"group" => group, "key" => key, "delete" => "true"} ->
|
||||
{:ok, _} = Config.delete(%{group: group, key: key})
|
||||
nil
|
||||
|
||||
%{"group" => group, "key" => key, "value" => value} ->
|
||||
{:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
|
||||
config
|
||||
end)
|
||||
|> Enum.reject(&is_nil(&1))
|
||||
|
||||
|
|
|
|||
|
|
@ -67,99 +67,86 @@ defmodule Pleroma.Web.AdminAPI.Config do
|
|||
end
|
||||
|
||||
@spec from_binary(binary()) :: term()
|
||||
def from_binary(value), do: :erlang.binary_to_term(value)
|
||||
def from_binary(binary), do: :erlang.binary_to_term(binary)
|
||||
|
||||
@spec from_binary_to_map(binary()) :: any()
|
||||
def from_binary_to_map(binary) do
|
||||
@spec from_binary_with_convert(binary()) :: any()
|
||||
def from_binary_with_convert(binary) do
|
||||
from_binary(binary)
|
||||
|> do_convert()
|
||||
end
|
||||
|
||||
defp do_convert([{k, v}] = value) when is_list(value) and length(value) == 1,
|
||||
do: %{k => do_convert(v)}
|
||||
defp do_convert(entity) when is_list(entity) do
|
||||
for v <- entity, into: [], do: do_convert(v)
|
||||
end
|
||||
|
||||
defp do_convert(values) when is_list(values), do: for(val <- values, do: do_convert(val))
|
||||
defp do_convert(entity) when is_map(entity) do
|
||||
for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)}
|
||||
end
|
||||
|
||||
defp do_convert({k, v} = value) when is_tuple(value),
|
||||
do: %{k => do_convert(v)}
|
||||
defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]}
|
||||
|
||||
defp do_convert(value) when is_tuple(value), do: %{"tuple" => do_convert(Tuple.to_list(value))}
|
||||
defp do_convert(entity) when is_tuple(entity),
|
||||
do: %{"tuple" => do_convert(Tuple.to_list(entity))}
|
||||
|
||||
defp do_convert(value) when is_binary(value) or is_map(value) or is_number(value), do: value
|
||||
defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity),
|
||||
do: entity
|
||||
|
||||
defp do_convert(value) when is_atom(value) do
|
||||
string = to_string(value)
|
||||
defp do_convert(entity) when is_atom(entity) do
|
||||
string = to_string(entity)
|
||||
|
||||
if String.starts_with?(string, "Elixir."),
|
||||
do: String.trim_leading(string, "Elixir."),
|
||||
else: value
|
||||
do: do_convert(string),
|
||||
else: ":" <> string
|
||||
end
|
||||
|
||||
defp do_convert("Elixir." <> module_name), do: module_name
|
||||
|
||||
defp do_convert(entity) when is_binary(entity), do: entity
|
||||
|
||||
@spec transform(any()) :: binary()
|
||||
def transform(%{"tuple" => _} = entity), do: :erlang.term_to_binary(do_transform(entity))
|
||||
|
||||
def transform(entity) when is_map(entity) do
|
||||
tuples =
|
||||
for {k, v} <- entity,
|
||||
into: [],
|
||||
do: {if(is_atom(k), do: k, else: String.to_atom(k)), do_transform(v)}
|
||||
|
||||
Enum.reject(tuples, fn {_k, v} -> is_nil(v) end)
|
||||
|> Enum.sort()
|
||||
|> :erlang.term_to_binary()
|
||||
end
|
||||
|
||||
def transform(entity) when is_list(entity) do
|
||||
list = Enum.map(entity, &do_transform(&1))
|
||||
:erlang.term_to_binary(list)
|
||||
def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do
|
||||
:erlang.term_to_binary(do_transform(entity))
|
||||
end
|
||||
|
||||
def transform(entity), do: :erlang.term_to_binary(entity)
|
||||
|
||||
defp do_transform(%Regex{} = value) when is_map(value), do: value
|
||||
defp do_transform(%Regex{} = entity) when is_map(entity), do: entity
|
||||
|
||||
defp do_transform(%{"tuple" => [k, values] = entity}) when length(entity) == 2 do
|
||||
{do_transform(k), do_transform(values)}
|
||||
defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
|
||||
cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
|
||||
{dispatch_settings, []} = Code.eval_string(cleaned_string, [], requires: [], macros: [])
|
||||
{:dispatch, [dispatch_settings]}
|
||||
end
|
||||
|
||||
defp do_transform(%{"tuple" => values}) do
|
||||
Enum.reduce(values, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
|
||||
defp do_transform(%{"tuple" => entity}) do
|
||||
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
|
||||
end
|
||||
|
||||
defp do_transform(value) when is_map(value) do
|
||||
values = for {key, val} <- value, into: [], do: {String.to_atom(key), do_transform(val)}
|
||||
|
||||
Enum.sort(values)
|
||||
defp do_transform(entity) when is_map(entity) do
|
||||
for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)}
|
||||
end
|
||||
|
||||
defp do_transform(value) when is_list(value) do
|
||||
Enum.map(value, &do_transform(&1))
|
||||
defp do_transform(entity) when is_list(entity) do
|
||||
for v <- entity, into: [], do: do_transform(v)
|
||||
end
|
||||
|
||||
defp do_transform(entity) when is_list(entity) and length(entity) == 1, do: hd(entity)
|
||||
|
||||
defp do_transform(value) when is_binary(value) do
|
||||
String.trim(value)
|
||||
defp do_transform(entity) when is_binary(entity) do
|
||||
String.trim(entity)
|
||||
|> do_transform_string()
|
||||
end
|
||||
|
||||
defp do_transform(value), do: value
|
||||
defp do_transform(entity), do: entity
|
||||
|
||||
defp do_transform_string(value) when byte_size(value) == 0, do: nil
|
||||
defp do_transform_string("~r/" <> pattern) do
|
||||
pattern = String.trim_trailing(pattern, "/")
|
||||
~r/#{pattern}/
|
||||
end
|
||||
|
||||
defp do_transform_string(":" <> atom), do: String.to_atom(atom)
|
||||
|
||||
defp do_transform_string(value) do
|
||||
cond do
|
||||
String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix") ->
|
||||
String.to_existing_atom("Elixir." <> value)
|
||||
|
||||
String.starts_with?(value, ":") ->
|
||||
String.replace(value, ":", "") |> String.to_existing_atom()
|
||||
|
||||
String.starts_with?(value, "i:") ->
|
||||
String.replace(value, "i:", "") |> String.to_integer()
|
||||
|
||||
true ->
|
||||
value
|
||||
end
|
||||
if String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix"),
|
||||
do: String.to_existing_atom("Elixir." <> value),
|
||||
else: value
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do
|
|||
%{
|
||||
key: config.key,
|
||||
group: config.group,
|
||||
value: Pleroma.Web.AdminAPI.Config.from_binary_to_map(config.value)
|
||||
value: Pleroma.Web.AdminAPI.Config.from_binary_with_convert(config.value)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def unfollow(follower, unfollowed) do
|
||||
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
||||
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do
|
||||
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
|
||||
{:ok, _unfollowed} <- User.unsubscribe(follower, unfollowed) do
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
|||
options = cast_params(params)
|
||||
|
||||
user
|
||||
|> Notification.for_user_query()
|
||||
|> Notification.for_user_query(options)
|
||||
|> restrict(:exclude_types, options)
|
||||
|> Pagination.fetch_paginated(params)
|
||||
end
|
||||
|
|
@ -67,7 +67,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
|||
defp cast_params(params) do
|
||||
param_types = %{
|
||||
exclude_types: {:array, :string},
|
||||
reblogs: :boolean
|
||||
reblogs: :boolean,
|
||||
with_muted: :boolean
|
||||
}
|
||||
|
||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.Stats
|
||||
|
|
@ -46,8 +47,24 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Plugs.RateLimiter, :app_account_creation when action == :account_register)
|
||||
plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||
@rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
|
||||
post_status delete_status)a
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
{:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
|
||||
when action in ~w(reblog_status unreblog_status)a
|
||||
)
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
{:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
|
||||
when action in ~w(fav_status unfav_status)a
|
||||
)
|
||||
|
||||
plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
|
||||
plug(RateLimiter, :app_account_creation when action == :account_register)
|
||||
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||
|
||||
@local_mastodon_name "Mastodon-Local"
|
||||
|
||||
|
|
@ -1051,9 +1068,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id} = params) do
|
||||
notifications =
|
||||
if Map.has_key?(params, "notifications"),
|
||||
do: params["notifications"] in [true, "True", "true", "1"],
|
||||
else: true
|
||||
|
||||
with %User{} = muted <- User.get_cached_by_id(id),
|
||||
{:ok, muter} <- User.mute(muter, muted) do
|
||||
{:ok, muter} <- User.mute(muter, muted, notifications) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: muter, target: muted})
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Plugs.RateLimiter
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
|
|
@ -16,43 +17,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
require Logger
|
||||
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||
|
||||
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, [])
|
||||
statuses = with_fallback(fn -> Activity.search(user, query) end, [])
|
||||
|
||||
tags_path = Web.base_url() <> "/tag/"
|
||||
|
||||
tags =
|
||||
query
|
||||
|> prepare_tags
|
||||
|> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
|
||||
|
||||
res = %{
|
||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
||||
"statuses" =>
|
||||
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
||||
"hashtags" => tags
|
||||
}
|
||||
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end)
|
||||
statuses = with_fallback(fn -> Activity.search(user, query) end)
|
||||
|
||||
tags = prepare_tags(query)
|
||||
|
||||
res = %{
|
||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
||||
"statuses" =>
|
||||
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
||||
"hashtags" => tags
|
||||
}
|
||||
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, search_options(params, user))
|
||||
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
|
||||
|
|
@ -60,12 +24,36 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
json(conn, res)
|
||||
end
|
||||
|
||||
defp prepare_tags(query) do
|
||||
query
|
||||
|> String.split()
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|
||||
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|
||||
def search2(conn, params), do: do_search(:v2, conn, params)
|
||||
def search(conn, params), do: do_search(:v1, conn, params)
|
||||
|
||||
defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
options = search_options(params, user)
|
||||
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
|
||||
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
|
||||
|
||||
result =
|
||||
default_values
|
||||
|> Enum.map(fn {resource, default_value} ->
|
||||
if params["type"] == nil or params["type"] == resource do
|
||||
{resource, fn -> resource_search(version, resource, query, options) end}
|
||||
else
|
||||
{resource, fn -> default_value end}
|
||||
end
|
||||
end)
|
||||
|> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end,
|
||||
timeout: timeout,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.reduce(default_values, fn
|
||||
{:ok, {resource, result}}, acc ->
|
||||
Map.put(acc, resource, result)
|
||||
|
||||
_error, acc ->
|
||||
acc
|
||||
end)
|
||||
|
||||
json(conn, result)
|
||||
end
|
||||
|
||||
defp search_options(params, user) do
|
||||
|
|
@ -74,8 +62,45 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
following: params["following"] == "true",
|
||||
limit: ControllerHelper.fetch_integer_param(params, "limit"),
|
||||
offset: ControllerHelper.fetch_integer_param(params, "offset"),
|
||||
type: params["type"],
|
||||
author: get_author(params),
|
||||
for_user: user
|
||||
]
|
||||
|> Enum.filter(&elem(&1, 1))
|
||||
end
|
||||
|
||||
defp resource_search(_, "accounts", query, options) do
|
||||
accounts = with_fallback(fn -> User.search(query, options) end)
|
||||
AccountView.render("accounts.json", users: accounts, for: options[:for_user], as: :user)
|
||||
end
|
||||
|
||||
defp resource_search(_, "statuses", query, options) do
|
||||
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
|
||||
StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
|
||||
end
|
||||
|
||||
defp resource_search(:v2, "hashtags", query, _options) do
|
||||
tags_path = Web.base_url() <> "/tag/"
|
||||
|
||||
query
|
||||
|> prepare_tags()
|
||||
|> Enum.map(fn tag ->
|
||||
tag = String.trim_leading(tag, "#")
|
||||
%{name: tag, url: tags_path <> tag}
|
||||
end)
|
||||
end
|
||||
|
||||
defp resource_search(:v1, "hashtags", query, _options) do
|
||||
query
|
||||
|> prepare_tags()
|
||||
|> Enum.map(fn tag -> String.trim_leading(tag, "#") end)
|
||||
end
|
||||
|
||||
defp prepare_tags(query) do
|
||||
query
|
||||
|> String.split()
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|
||||
end
|
||||
|
||||
defp with_fallback(f, fallback \\ []) do
|
||||
|
|
@ -87,4 +112,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
fallback
|
||||
end
|
||||
end
|
||||
|
||||
defp get_author(%{"account_id" => account_id}) when is_binary(account_id),
|
||||
do: User.get_cached_by_id(account_id)
|
||||
|
||||
defp get_author(_params), do: nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
followed_by: User.following?(target, user),
|
||||
blocking: User.blocks?(user, target),
|
||||
muting: User.mutes?(user, target),
|
||||
muting_notifications: false,
|
||||
muting_notifications: User.muted_notifications?(user, target),
|
||||
subscribing: User.subscribed_to?(user, target),
|
||||
requested: requested,
|
||||
domain_blocking: false,
|
||||
|
|
|
|||
|
|
@ -3,68 +3,71 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MediaProxy do
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web
|
||||
|
||||
@base64_opts [padding: false]
|
||||
|
||||
def url(nil), do: nil
|
||||
|
||||
def url(""), do: nil
|
||||
|
||||
def url(url) when is_nil(url) or url == "", do: nil
|
||||
def url("/" <> _ = url), do: url
|
||||
|
||||
def url(url) do
|
||||
if !enabled?() or local?(url) or whitelisted?(url) do
|
||||
if disabled?() or local?(url) or whitelisted?(url) do
|
||||
url
|
||||
else
|
||||
encode_url(url)
|
||||
end
|
||||
end
|
||||
|
||||
defp enabled?, do: Pleroma.Config.get([:media_proxy, :enabled], false)
|
||||
defp disabled?, do: !Config.get([:media_proxy, :enabled], false)
|
||||
|
||||
defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
|
||||
|
||||
defp whitelisted?(url) do
|
||||
%{host: domain} = URI.parse(url)
|
||||
|
||||
Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern ->
|
||||
Enum.any?(Config.get([:media_proxy, :whitelist]), fn pattern ->
|
||||
String.equivalent?(domain, pattern)
|
||||
end)
|
||||
end
|
||||
|
||||
def encode_url(url) do
|
||||
secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
|
||||
base64 = Base.url_encode64(url, @base64_opts)
|
||||
sig = :crypto.hmac(:sha, secret, base64)
|
||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
||||
|
||||
sig64 =
|
||||
base64
|
||||
|> signed_url
|
||||
|> Base.url_encode64(@base64_opts)
|
||||
|
||||
build_url(sig64, base64, filename(url))
|
||||
end
|
||||
|
||||
def decode_url(sig, url) do
|
||||
secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
|
||||
sig = Base.url_decode64!(sig, @base64_opts)
|
||||
local_sig = :crypto.hmac(:sha, secret, url)
|
||||
|
||||
if local_sig == sig do
|
||||
with {:ok, sig} <- Base.url_decode64(sig, @base64_opts),
|
||||
signature when signature == sig <- signed_url(url) do
|
||||
{:ok, Base.url_decode64!(url, @base64_opts)}
|
||||
else
|
||||
{:error, :invalid_signature}
|
||||
_ -> {:error, :invalid_signature}
|
||||
end
|
||||
end
|
||||
|
||||
defp signed_url(url) do
|
||||
:crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
|
||||
end
|
||||
|
||||
def filename(url_or_path) do
|
||||
if path = URI.parse(url_or_path).path, do: Path.basename(path)
|
||||
end
|
||||
|
||||
def build_url(sig_base64, url_base64, filename \\ nil) do
|
||||
[
|
||||
Pleroma.Config.get([:media_proxy, :base_url], Pleroma.Web.base_url()),
|
||||
Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
|
||||
"proxy",
|
||||
sig_base64,
|
||||
url_base64,
|
||||
filename
|
||||
]
|
||||
|> Enum.filter(fn value -> value end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Path.join()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
with config <- Pleroma.Config.get([:media_proxy], []),
|
||||
true <- Keyword.get(config, :enabled, false),
|
||||
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
|
||||
:ok <- filename_matches(Map.has_key?(params, "filename"), conn.request_path, url) do
|
||||
:ok <- filename_matches(params, conn.request_path, url) do
|
||||
ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
|
||||
else
|
||||
false ->
|
||||
|
|
@ -27,18 +27,15 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
end
|
||||
end
|
||||
|
||||
def filename_matches(has_filename, path, url) do
|
||||
filename =
|
||||
url
|
||||
|> MediaProxy.filename()
|
||||
|> URI.decode()
|
||||
def filename_matches(%{"filename" => _} = _, path, url) do
|
||||
filename = MediaProxy.filename(url)
|
||||
|
||||
path = URI.decode(path)
|
||||
|
||||
if has_filename && filename && Path.basename(path) != filename do
|
||||
if filename && Path.basename(path) != filename do
|
||||
{:wrong_filename, filename}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def filename_matches(_, _, _), do: :ok
|
||||
end
|
||||
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
|
|||
alias Pleroma.Web.Metadata.Utils
|
||||
|
||||
@behaviour Provider
|
||||
@media_types ["image", "audio", "video"]
|
||||
|
||||
@impl Provider
|
||||
def build_tags(%{
|
||||
|
|
@ -81,26 +82,19 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
|
|||
Enum.reduce(attachments, [], fn attachment, acc ->
|
||||
rendered_tags =
|
||||
Enum.reduce(attachment["url"], [], fn url, acc ->
|
||||
media_type =
|
||||
Enum.find(["image", "audio", "video"], fn media_type ->
|
||||
String.starts_with?(url["mediaType"], media_type)
|
||||
end)
|
||||
|
||||
# TODO: Add additional properties to objects when we have the data available.
|
||||
# Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
|
||||
# object when a Video or GIF is attached it will display that in Whatsapp Rich Preview.
|
||||
case media_type do
|
||||
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
|
||||
"audio" ->
|
||||
[
|
||||
{:meta,
|
||||
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
|
||||
{:meta, [property: "og:audio", content: Utils.attachment_url(url["href"])], []}
|
||||
| acc
|
||||
]
|
||||
|
||||
"image" ->
|
||||
[
|
||||
{:meta,
|
||||
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []},
|
||||
{:meta, [property: "og:image", content: Utils.attachment_url(url["href"])], []},
|
||||
{:meta, [property: "og:image:width", content: 150], []},
|
||||
{:meta, [property: "og:image:height", content: 150], []}
|
||||
| acc
|
||||
|
|
@ -108,8 +102,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
|
|||
|
||||
"video" ->
|
||||
[
|
||||
{:meta,
|
||||
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
|
||||
{:meta, [property: "og:video", content: Utils.attachment_url(url["href"])], []}
|
||||
| acc
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
|
|
@ -9,13 +10,10 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
|
|||
alias Pleroma.Web.Metadata.Utils
|
||||
|
||||
@behaviour Provider
|
||||
@media_types ["image", "audio", "video"]
|
||||
|
||||
@impl Provider
|
||||
def build_tags(%{
|
||||
activity_id: id,
|
||||
object: object,
|
||||
user: user
|
||||
}) do
|
||||
def build_tags(%{activity_id: id, object: object, user: user}) do
|
||||
attachments = build_attachments(id, object)
|
||||
scrubbed_content = Utils.scrub_html_and_truncate(object)
|
||||
# Zero width space
|
||||
|
|
@ -27,21 +25,12 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
|
|||
end
|
||||
|
||||
[
|
||||
{:meta,
|
||||
[
|
||||
property: "twitter:title",
|
||||
content: Utils.user_name_string(user)
|
||||
], []},
|
||||
{:meta,
|
||||
[
|
||||
property: "twitter:description",
|
||||
content: content
|
||||
], []}
|
||||
title_tag(user),
|
||||
{:meta, [property: "twitter:description", content: content], []}
|
||||
] ++
|
||||
if attachments == [] or Metadata.activity_nsfw?(object) do
|
||||
[
|
||||
{:meta,
|
||||
[property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []},
|
||||
image_tag(user),
|
||||
{:meta, [property: "twitter:card", content: "summary_large_image"], []}
|
||||
]
|
||||
else
|
||||
|
|
@ -53,30 +42,28 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
|
|||
def build_tags(%{user: user}) do
|
||||
with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
|
||||
[
|
||||
{:meta,
|
||||
[
|
||||
property: "twitter:title",
|
||||
content: Utils.user_name_string(user)
|
||||
], []},
|
||||
title_tag(user),
|
||||
{:meta, [property: "twitter:description", content: truncated_bio], []},
|
||||
{:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))],
|
||||
[]},
|
||||
image_tag(user),
|
||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
defp title_tag(user) do
|
||||
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}
|
||||
end
|
||||
|
||||
def image_tag(user) do
|
||||
{:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []}
|
||||
end
|
||||
|
||||
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||
Enum.reduce(attachments, [], fn attachment, acc ->
|
||||
rendered_tags =
|
||||
Enum.reduce(attachment["url"], [], fn url, acc ->
|
||||
media_type =
|
||||
Enum.find(["image", "audio", "video"], fn media_type ->
|
||||
String.starts_with?(url["mediaType"], media_type)
|
||||
end)
|
||||
|
||||
# TODO: Add additional properties to objects when we have the data available.
|
||||
case media_type do
|
||||
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
|
||||
"audio" ->
|
||||
[
|
||||
{:meta, [property: "twitter:card", content: "player"], []},
|
||||
|
|
|
|||
|
|
@ -39,4 +39,11 @@ defmodule Pleroma.Web.Metadata.Utils do
|
|||
"(@#{user.nickname})"
|
||||
end
|
||||
end
|
||||
|
||||
@spec fetch_media_type(list(String.t()), String.t()) :: String.t() | nil
|
||||
def fetch_media_type(supported_types, media_type) do
|
||||
Enum.find(supported_types, fn support_type ->
|
||||
String.starts_with?(media_type, support_type)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,8 +34,11 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
def raw_nodeinfo do
|
||||
stats = Stats.get_stats()
|
||||
|
||||
exclusions = Config.get([:instance, :mrf_transparency_exclusions])
|
||||
|
||||
mrf_simple =
|
||||
Config.get(:mrf_simple)
|
||||
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
# This horror is needed to convert regex sigils to strings
|
||||
|
|
@ -86,7 +89,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
mrf_simple: mrf_simple,
|
||||
mrf_keyword: mrf_keyword,
|
||||
mrf_user_allowlist: mrf_user_allowlist,
|
||||
quarantined_instances: quarantined
|
||||
quarantined_instances: quarantined,
|
||||
exclusions: length(exclusions) > 0
|
||||
}
|
||||
else
|
||||
%{}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,6 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.RichMedia.Parser do
|
||||
@parsers [
|
||||
Pleroma.Web.RichMedia.Parsers.OGP,
|
||||
Pleroma.Web.RichMedia.Parsers.TwitterCard,
|
||||
Pleroma.Web.RichMedia.Parsers.OEmbed
|
||||
]
|
||||
|
||||
@hackney_options [
|
||||
pool: :media,
|
||||
recv_timeout: 2_000,
|
||||
|
|
@ -16,6 +10,10 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
with_body: true
|
||||
]
|
||||
|
||||
defp parsers do
|
||||
Pleroma.Config.get([:rich_media, :parsers])
|
||||
end
|
||||
|
||||
def parse(nil), do: {:error, "No URL provided"}
|
||||
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
|
|
@ -48,7 +46,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
end
|
||||
|
||||
defp maybe_parse(html) do
|
||||
Enum.reduce_while(@parsers, %{}, fn parser, acc ->
|
||||
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
|
||||
case parser.parse(html, acc) do
|
||||
{:ok, data} -> {:halt, data}
|
||||
{:error, _msg} -> {:cont, acc}
|
||||
|
|
|
|||
|
|
@ -322,10 +322,6 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
|
||||
|
||||
patch("/accounts/update_avatar", MastodonAPIController, :update_avatar)
|
||||
patch("/accounts/update_banner", MastodonAPIController, :update_banner)
|
||||
patch("/accounts/update_background", MastodonAPIController, :update_background)
|
||||
|
||||
post("/statuses", MastodonAPIController, :post_status)
|
||||
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
||||
|
||||
|
|
@ -360,6 +356,10 @@ defmodule Pleroma.Web.Router do
|
|||
put("/filters/:id", MastodonAPIController, :update_filter)
|
||||
delete("/filters/:id", MastodonAPIController, :delete_filter)
|
||||
|
||||
patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar)
|
||||
patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner)
|
||||
patch("/pleroma/accounts/update_background", MastodonAPIController, :update_background)
|
||||
|
||||
get("/pleroma/mascot", MastodonAPIController, :get_mascot)
|
||||
put("/pleroma/mascot", MastodonAPIController, :set_mascot)
|
||||
|
||||
|
|
@ -623,8 +623,6 @@ defmodule Pleroma.Web.Router do
|
|||
# XXX: not really ostatus
|
||||
pipe_through(:ostatus)
|
||||
|
||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||
get("/users/:nickname/following", ActivityPubController, :following)
|
||||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||
get("/objects/:uuid/likes", ActivityPubController, :object_likes)
|
||||
end
|
||||
|
|
@ -656,6 +654,12 @@ defmodule Pleroma.Web.Router do
|
|||
pipe_through(:oauth_write)
|
||||
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_public)
|
||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||
get("/users/:nickname/following", ActivityPubController, :following)
|
||||
end
|
||||
end
|
||||
|
||||
scope "/relay", Pleroma.Web.ActivityPub do
|
||||
|
|
|
|||
|
|
@ -192,6 +192,13 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
end
|
||||
|
||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||
params =
|
||||
if Map.has_key?(params, "with_muted") do
|
||||
Map.put(params, :with_muted, params["with_muted"] in [true, "True", "true", "1"])
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
notifications = Notification.for_user(user, params)
|
||||
|
||||
conn
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue