diff --git a/CHANGELOG.md b/CHANGELOG.md
index 649fbc0be..755b28d5b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
- Admin API: Return `total` when querying for reports
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
+- Admin API: Return link alongside with token on password reset
### Fixed
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
@@ -108,6 +109,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Admin API: Added moderation log
- Web response cache (currently, enabled for ActivityPub)
- Mastodon API: Added an endpoint to get multiple statuses by IDs (`GET /api/v1/statuses/?ids[]=1&ids[]=2`)
+- ActivityPub: Add ActivityPub actor's `discoverable` parameter.
+- Admin API: Added moderation log filters (user/start date/end date/search/pagination)
### Changed
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
@@ -115,6 +118,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- RichMedia: parsers and their order are configured in `rich_media` config.
- RichMedia: add the rich media ttl based on image expiration time.
+## [1.0.7] - 2019-09-26
+### Fixed
+- Broken federation on Erlang 22 (previous versions of hackney http client were using an option that got deprecated)
+### Changed
+- ActivityPub: The first page in inboxes/outboxes is no longer embedded.
+
## [1.0.6] - 2019-08-14
### Fixed
- MRF: fix use of unserializable keyword lists in describe() implementations
diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md
index 9583883d3..573111416 100644
--- a/docs/api/admin_api.md
+++ b/docs/api/admin_api.md
@@ -308,7 +308,15 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- Methods: `GET`
- Params: none
-- Response: password reset token (base64 string)
+- Response:
+
+```json
+{
+ "token": "U13DX6muOvpRsj35_ij9wLxUbkU-eFvfKttxs6gIajo=", // password reset token (base64 string)
+ "link": "https://pleroma.social/api/pleroma/password_reset/U13DX6muOvpRsj35_ij9wLxUbkU-eFvfKttxs6gIajo%3D"
+}
+```
+
## `/api/pleroma/admin/users/:nickname/force_password_reset`
@@ -723,7 +731,11 @@ Compile time settings (need instance reboot):
- Method `GET`
- Params:
- *optional* `page`: **integer** page number
- - *optional* `page_size`: **integer** number of users per page (default is `50`)
+ - *optional* `page_size`: **integer** number of log entries per page (default is `50`)
+ - *optional* `start_date`: **datetime (ISO 8601)** filter logs by creation date, start from `start_date`. Accepts datetime in ISO 8601 format (YYYY-MM-DDThh:mm:ss), e.g. `2005-08-09T18:31:42`
+ - *optional* `end_date`: **datetime (ISO 8601)** filter logs by creation date, end by from `end_date`. Accepts datetime in ISO 8601 format (YYYY-MM-DDThh:mm:ss), e.g. 2005-08-09T18:31:42
+ - *optional* `user_id`: **integer** filter logs by actor's id
+ - *optional* `search`: **string** search logs by the log message
- Response:
```json
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 974130c6a..801cbb5d7 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -4,7 +4,6 @@
defmodule Mix.Tasks.Pleroma.User do
use Mix.Task
- import Ecto.Changeset
import Mix.Pleroma
alias Pleroma.User
alias Pleroma.UserInviteToken
@@ -228,9 +227,9 @@ def run(["unsubscribe", nickname]) do
shell_info("Deactivating #{user.nickname}")
User.deactivate(user)
- {:ok, friends} = User.get_friends(user)
-
- Enum.each(friends, fn friend ->
+ user
+ |> User.get_friends()
+ |> Enum.each(fn friend ->
user = User.get_cached_by_id(user.id)
shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}")
@@ -405,7 +404,7 @@ def run(["delete_activities", nickname]) do
start_pleroma()
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
- {:ok, _} = User.delete_user_activities(user)
+ User.delete_user_activities(user)
shell_info("User #{nickname} statuses deleted.")
else
_ ->
@@ -472,39 +471,21 @@ def run(["sign_out", nickname]) do
end
defp set_moderator(user, value) do
- info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
-
- user_cng =
- Ecto.Changeset.change(user)
- |> put_embed(:info, info_cng)
-
- {:ok, user} = User.update_and_set_cache(user_cng)
+ {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_moderator: value}))
shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
user
end
defp set_admin(user, value) do
- info_cng = User.Info.admin_api_update(user.info, %{is_admin: value})
-
- user_cng =
- Ecto.Changeset.change(user)
- |> put_embed(:info, info_cng)
-
- {:ok, user} = User.update_and_set_cache(user_cng)
+ {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_admin: value}))
shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}")
user
end
defp set_locked(user, value) do
- info_cng = User.Info.user_upgrade(user.info, %{locked: value})
-
- user_cng =
- Ecto.Changeset.change(user)
- |> put_embed(:info, info_cng)
-
- {:ok, user} = User.update_and_set_cache(user_cng)
+ {:ok, user} = User.update_info(user, &User.Info.user_upgrade(&1, %{locked: value}))
shell_info("Locked status of #{user.nickname}: #{user.info.locked}")
user
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index ec558168a..2c04a26f9 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -21,7 +21,7 @@ defmodule Pleroma.Activity do
@type t :: %__MODULE__{}
@type actor :: String.t()
- @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
+ @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
# https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
@mastodon_notification_types %{
diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex
index bf57abca4..7ea5c48ca 100644
--- a/lib/pleroma/activity_expiration.ex
+++ b/lib/pleroma/activity_expiration.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.ActivityExpiration do
alias Pleroma.Activity
alias Pleroma.ActivityExpiration
- alias Pleroma.FlakeId
alias Pleroma.Repo
import Ecto.Changeset
@@ -17,7 +16,7 @@ defmodule Pleroma.ActivityExpiration do
@min_activity_lifetime :timer.hours(1)
schema "activity_expirations" do
- belongs_to(:activity, Activity, type: FlakeId)
+ belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
field(:scheduled_at, :naive_datetime)
end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index a339e2c48..7aec2c545 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -35,7 +35,6 @@ def start(_type, _args) do
Pleroma.Config.TransferTask,
Pleroma.Emoji,
Pleroma.Captcha,
- Pleroma.FlakeId,
Pleroma.Daemons.ScheduledActivityDaemon,
Pleroma.Daemons.ActivityExpirationDaemon
] ++
diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex
index d976f949c..221a94f34 100644
--- a/lib/pleroma/bookmark.ex
+++ b/lib/pleroma/bookmark.ex
@@ -10,20 +10,20 @@ defmodule Pleroma.Bookmark do
alias Pleroma.Activity
alias Pleroma.Bookmark
- alias Pleroma.FlakeId
alias Pleroma.Repo
alias Pleroma.User
@type t :: %__MODULE__{}
schema "bookmarks" do
- belongs_to(:user, User, type: FlakeId)
- belongs_to(:activity, Activity, type: FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+ belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
timestamps()
end
- @spec create(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
+ @spec create(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t()) ::
+ {:ok, Bookmark.t()} | {:error, Changeset.t()}
def create(user_id, activity_id) do
attrs = %{
user_id: user_id,
@@ -37,7 +37,7 @@ def create(user_id, activity_id) do
|> Repo.insert()
end
- @spec for_user_query(FlakeId.t()) :: Ecto.Query.t()
+ @spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t()
def for_user_query(user_id) do
Bookmark
|> where(user_id: ^user_id)
@@ -52,7 +52,8 @@ def get(user_id, activity_id) do
|> Repo.one()
end
- @spec destroy(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
+ @spec destroy(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t()) ::
+ {:ok, Bookmark.t()} | {:error, Changeset.t()}
def destroy(user_id, activity_id) do
from(b in Bookmark,
where: b.user_id == ^user_id,
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index ea5b9fe17..e946f6de2 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -13,10 +13,10 @@ defmodule Pleroma.Conversation.Participation do
import Ecto.Query
schema "conversation_participations" do
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
belongs_to(:conversation, Conversation)
field(:read, :boolean, default: false)
- field(:last_activity_id, Pleroma.FlakeId, virtual: true)
+ field(:last_activity_id, FlakeId.Ecto.CompatType, virtual: true)
has_many(:recipient_ships, RecipientShip)
has_many(:recipients, through: [:recipient_ships, :user])
diff --git a/lib/pleroma/conversation/participation_recipient_ship.ex b/lib/pleroma/conversation/participation_recipient_ship.ex
index 932cbd04c..e3d158cbc 100644
--- a/lib/pleroma/conversation/participation_recipient_ship.ex
+++ b/lib/pleroma/conversation/participation_recipient_ship.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Conversation.Participation.RecipientShip do
import Ecto.Changeset
schema "conversation_participation_recipient_ships" do
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
belongs_to(:participation, Participation)
end
diff --git a/lib/pleroma/delivery.ex b/lib/pleroma/delivery.ex
index 29a1e5a77..1d586a252 100644
--- a/lib/pleroma/delivery.ex
+++ b/lib/pleroma/delivery.ex
@@ -6,7 +6,6 @@ defmodule Pleroma.Delivery do
use Ecto.Schema
alias Pleroma.Delivery
- alias Pleroma.FlakeId
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
@@ -16,7 +15,7 @@ defmodule Pleroma.Delivery do
import Ecto.Query
schema "deliveries" do
- belongs_to(:user, User, type: FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
belongs_to(:object, Object)
end
diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex
index a29de0a33..4f4ee51d1 100644
--- a/lib/pleroma/emoji/loader.ex
+++ b/lib/pleroma/emoji/loader.ex
@@ -99,7 +99,7 @@ defp load_pack(pack_dir, emoji_groups) do
contents["files"]
|> Enum.map(fn {name, rel_file} ->
filename = Path.join("/emoji/#{pack_name}", rel_file)
- {name, filename, pack_name}
+ {name, filename, ["pack:#{pack_name}"]}
end)
else
# Load from emoji.txt / all files
diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex
index 90457dadf..c87141582 100644
--- a/lib/pleroma/filter.ex
+++ b/lib/pleroma/filter.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Filter do
alias Pleroma.User
schema "filters" do
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
field(:filter_id, :integer)
field(:hide, :boolean, default: false)
field(:whole_word, :boolean, default: true)
diff --git a/lib/pleroma/flake_id.ex b/lib/pleroma/flake_id.ex
deleted file mode 100644
index 042cf8659..000000000
--- a/lib/pleroma/flake_id.ex
+++ /dev/null
@@ -1,182 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.FlakeId do
- @moduledoc """
- Flake is a decentralized, k-ordered id generation service.
-
- Adapted from:
-
- * [flaky](https://github.com/nirvana/flaky), released under the terms of the Truly Free License,
- * [Flake](https://github.com/boundary/flake), Copyright 2012, Boundary, Apache License, Version 2.0
- """
-
- @type t :: binary
-
- use Ecto.Type
- use GenServer
- require Logger
- alias __MODULE__
- import Kernel, except: [to_string: 1]
-
- defstruct node: nil, time: 0, sq: 0
-
- @doc "Converts a binary Flake to a String"
- def to_string(<<0::integer-size(64), id::integer-size(64)>>) do
- Kernel.to_string(id)
- end
-
- def to_string(<<_::integer-size(64), _::integer-size(48), _::integer-size(16)>> = flake) do
- encode_base62(flake)
- end
-
- def to_string(s), do: s
-
- def from_string(int) when is_integer(int) do
- from_string(Kernel.to_string(int))
- end
-
- for i <- [-1, 0] do
- def from_string(unquote(i)), do: <<0::integer-size(128)>>
- def from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>>
- end
-
- def from_string(<<_::integer-size(128)>> = flake), do: flake
-
- def from_string(string) when is_binary(string) and byte_size(string) < 18 do
- case Integer.parse(string) do
- {id, ""} -> <<0::integer-size(64), id::integer-size(64)>>
- _ -> nil
- end
- end
-
- def from_string(string) do
- string |> decode_base62 |> from_integer
- end
-
- def to_integer(<>), do: integer
-
- def from_integer(integer) do
- <<_time::integer-size(64), _node::integer-size(48), _seq::integer-size(16)>> =
- <>
- end
-
- @doc "Generates a Flake"
- @spec get :: binary
- def get, do: to_string(:gen_server.call(:flake, :get))
-
- # checks that ID is is valid FlakeID
- #
- @spec is_flake_id?(String.t()) :: boolean
- def is_flake_id?(id), do: is_flake_id?(String.to_charlist(id), true)
- defp is_flake_id?([c | cs], true) when c >= ?0 and c <= ?9, do: is_flake_id?(cs, true)
- defp is_flake_id?([c | cs], true) when c >= ?A and c <= ?Z, do: is_flake_id?(cs, true)
- defp is_flake_id?([c | cs], true) when c >= ?a and c <= ?z, do: is_flake_id?(cs, true)
- defp is_flake_id?([], true), do: true
- defp is_flake_id?(_, _), do: false
-
- # -- Ecto.Type API
- @impl Ecto.Type
- def type, do: :uuid
-
- @impl Ecto.Type
- def cast(value) do
- {:ok, FlakeId.to_string(value)}
- end
-
- @impl Ecto.Type
- def load(value) do
- {:ok, FlakeId.to_string(value)}
- end
-
- @impl Ecto.Type
- def dump(value) do
- {:ok, FlakeId.from_string(value)}
- end
-
- def autogenerate, do: get()
-
- # -- GenServer API
- def start_link(_) do
- :gen_server.start_link({:local, :flake}, __MODULE__, [], [])
- end
-
- @impl GenServer
- def init([]) do
- {:ok, %FlakeId{node: worker_id(), time: time()}}
- end
-
- @impl GenServer
- def handle_call(:get, _from, state) do
- {flake, new_state} = get(time(), state)
- {:reply, flake, new_state}
- end
-
- # Matches when the calling time is the same as the state time. Incr. sq
- defp get(time, %FlakeId{time: time, node: node, sq: seq}) do
- new_state = %FlakeId{time: time, node: node, sq: seq + 1}
- {gen_flake(new_state), new_state}
- end
-
- # Matches when the times are different, reset sq
- defp get(newtime, %FlakeId{time: time, node: node}) when newtime > time do
- new_state = %FlakeId{time: newtime, node: node, sq: 0}
- {gen_flake(new_state), new_state}
- end
-
- # Error when clock is running backwards
- defp get(newtime, %FlakeId{time: time}) when newtime < time do
- {:error, :clock_running_backwards}
- end
-
- defp gen_flake(%FlakeId{time: time, node: node, sq: seq}) do
- <>
- end
-
- defp nthchar_base62(n) when n <= 9, do: ?0 + n
- defp nthchar_base62(n) when n <= 35, do: ?A + n - 10
- defp nthchar_base62(n), do: ?a + n - 36
-
- defp encode_base62(<>) do
- integer
- |> encode_base62([])
- |> List.to_string()
- end
-
- defp encode_base62(int, acc) when int < 0, do: encode_base62(-int, acc)
- defp encode_base62(int, []) when int == 0, do: '0'
- defp encode_base62(int, acc) when int == 0, do: acc
-
- defp encode_base62(int, acc) do
- r = rem(int, 62)
- id = div(int, 62)
- acc = [nthchar_base62(r) | acc]
- encode_base62(id, acc)
- end
-
- defp decode_base62(s) do
- decode_base62(String.to_charlist(s), 0)
- end
-
- defp decode_base62([c | cs], acc) when c >= ?0 and c <= ?9,
- do: decode_base62(cs, 62 * acc + (c - ?0))
-
- defp decode_base62([c | cs], acc) when c >= ?A and c <= ?Z,
- do: decode_base62(cs, 62 * acc + (c - ?A + 10))
-
- defp decode_base62([c | cs], acc) when c >= ?a and c <= ?z,
- do: decode_base62(cs, 62 * acc + (c - ?a + 36))
-
- defp decode_base62([], acc), do: acc
-
- defp time do
- {mega_seconds, seconds, micro_seconds} = :erlang.timestamp()
- 1_000_000_000 * mega_seconds + seconds * 1000 + :erlang.trunc(micro_seconds / 1000)
- end
-
- defp worker_id do
- <> = :crypto.strong_rand_bytes(6)
- worker
- end
-end
diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex
index c572380c2..c5db1cb62 100644
--- a/lib/pleroma/list.ex
+++ b/lib/pleroma/list.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.List do
alias Pleroma.User
schema "lists" do
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
field(:title, :string)
field(:following, {:array, :string}, default: [])
field(:ap_id, :string)
diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex
index 1ef6fe67a..352cad433 100644
--- a/lib/pleroma/moderation_log.ex
+++ b/lib/pleroma/moderation_log.ex
@@ -14,61 +14,143 @@ defmodule Pleroma.ModerationLog do
timestamps()
end
- def get_all(page, page_size) do
- from(q in __MODULE__,
- order_by: [desc: q.inserted_at],
+ def get_all(params) do
+ base_query =
+ get_all_query()
+ |> maybe_filter_by_date(params)
+ |> maybe_filter_by_user(params)
+ |> maybe_filter_by_search(params)
+
+ query_with_pagination = base_query |> paginate_query(params)
+
+ %{
+ items: Repo.all(query_with_pagination),
+ count: Repo.aggregate(base_query, :count, :id)
+ }
+ end
+
+ defp maybe_filter_by_date(query, %{start_date: nil, end_date: nil}), do: query
+
+ defp maybe_filter_by_date(query, %{start_date: start_date, end_date: nil}) do
+ from(q in query,
+ where: q.inserted_at >= ^parse_datetime(start_date)
+ )
+ end
+
+ defp maybe_filter_by_date(query, %{start_date: nil, end_date: end_date}) do
+ from(q in query,
+ where: q.inserted_at <= ^parse_datetime(end_date)
+ )
+ end
+
+ defp maybe_filter_by_date(query, %{start_date: start_date, end_date: end_date}) do
+ from(q in query,
+ where: q.inserted_at >= ^parse_datetime(start_date),
+ where: q.inserted_at <= ^parse_datetime(end_date)
+ )
+ end
+
+ defp maybe_filter_by_user(query, %{user_id: nil}), do: query
+
+ defp maybe_filter_by_user(query, %{user_id: user_id}) do
+ from(q in query,
+ where: fragment("(?)->'actor'->>'id' = ?", q.data, ^user_id)
+ )
+ end
+
+ defp maybe_filter_by_search(query, %{search: search}) when is_nil(search) or search == "",
+ do: query
+
+ defp maybe_filter_by_search(query, %{search: search}) do
+ from(q in query,
+ where: fragment("(?)->>'message' ILIKE ?", q.data, ^"%#{search}%")
+ )
+ end
+
+ defp paginate_query(query, %{page: page, page_size: page_size}) do
+ from(q in query,
limit: ^page_size,
offset: ^((page - 1) * page_size)
)
- |> Repo.all()
end
+ defp get_all_query do
+ from(q in __MODULE__,
+ order_by: [desc: q.inserted_at]
+ )
+ end
+
+ defp parse_datetime(datetime) do
+ {:ok, parsed_datetime, _} = DateTime.from_iso8601(datetime)
+
+ parsed_datetime
+ end
+
+ @spec insert_log(%{actor: User, subject: User, action: String.t(), permission: String.t()}) ::
+ {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
subject: %User{} = subject,
action: action,
permission: permission
}) do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- subject: user_to_map(subject),
- action: action,
- permission: permission
+ "actor" => user_to_map(actor),
+ "subject" => user_to_map(subject),
+ "action" => action,
+ "permission" => permission,
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
+ @spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
+ {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
action: "report_update",
subject: %Activity{data: %{"type" => "Flag"}} = subject
}) do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- action: "report_update",
- subject: report_to_map(subject)
+ "actor" => user_to_map(actor),
+ "action" => "report_update",
+ "subject" => report_to_map(subject),
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
+ @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
+ {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
action: "report_response",
subject: %Activity{} = subject,
text: text
}) do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- action: "report_response",
- subject: report_to_map(subject),
- text: text
+ "actor" => user_to_map(actor),
+ "action" => "report_response",
+ "subject" => report_to_map(subject),
+ "text" => text,
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
+ @spec insert_log(%{
+ actor: User,
+ subject: Activity,
+ action: String.t(),
+ sensitive: String.t(),
+ visibility: String.t()
+ }) :: {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
action: "status_update",
@@ -76,41 +158,49 @@ def insert_log(%{
sensitive: sensitive,
visibility: visibility
}) do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- action: "status_update",
- subject: status_to_map(subject),
- sensitive: sensitive,
- visibility: visibility
+ "actor" => user_to_map(actor),
+ "action" => "status_update",
+ "subject" => status_to_map(subject),
+ "sensitive" => sensitive,
+ "visibility" => visibility,
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
+ @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
+ {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
action: "status_delete",
subject_id: subject_id
}) do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- action: "status_delete",
- subject_id: subject_id
+ "actor" => user_to_map(actor),
+ "action" => "status_delete",
+ "subject_id" => subject_id,
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
@spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- action: action,
- subject: user_to_map(subject)
+ "actor" => user_to_map(actor),
+ "action" => action,
+ "subject" => user_to_map(subject),
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
@spec insert_log(%{actor: User, subjects: [User], action: String.t()}) ::
@@ -118,97 +208,128 @@ def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do
def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do
subjects = Enum.map(subjects, &user_to_map/1)
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- action: action,
- subjects: subjects
+ "actor" => user_to_map(actor),
+ "action" => action,
+ "subjects" => subjects,
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
+ @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
+ {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
followed: %User{} = followed,
follower: %User{} = follower,
action: "follow"
}) do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- action: "follow",
- followed: user_to_map(followed),
- follower: user_to_map(follower)
+ "actor" => user_to_map(actor),
+ "action" => "follow",
+ "followed" => user_to_map(followed),
+ "follower" => user_to_map(follower),
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
+ @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
+ {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
followed: %User{} = followed,
follower: %User{} = follower,
action: "unfollow"
}) do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- action: "unfollow",
- followed: user_to_map(followed),
- follower: user_to_map(follower)
+ "actor" => user_to_map(actor),
+ "action" => "unfollow",
+ "followed" => user_to_map(followed),
+ "follower" => user_to_map(follower),
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
+ @spec insert_log(%{
+ actor: User,
+ action: String.t(),
+ nicknames: [String.t()],
+ tags: [String.t()]
+ }) :: {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
nicknames: nicknames,
tags: tags,
action: action
}) do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- nicknames: nicknames,
- tags: tags,
- action: action
+ "actor" => user_to_map(actor),
+ "nicknames" => nicknames,
+ "tags" => tags,
+ "action" => action,
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
end
+ @spec insert_log(%{actor: User, action: String.t(), target: String.t()}) ::
+ {:ok, ModerationLog} | {:error, any}
def insert_log(%{
actor: %User{} = actor,
action: action,
target: target
})
when action in ["relay_follow", "relay_unfollow"] do
- Repo.insert(%ModerationLog{
+ %ModerationLog{
data: %{
- actor: user_to_map(actor),
- action: action,
- target: target
+ "actor" => user_to_map(actor),
+ "action" => action,
+ "target" => target,
+ "message" => ""
}
- })
+ }
+ |> insert_log_entry_with_message()
+ end
+
+ @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
+
+ defp insert_log_entry_with_message(entry) do
+ entry.data["message"]
+ |> put_in(get_log_entry_message(entry))
+ |> Repo.insert()
end
defp user_to_map(%User{} = user) do
user
|> Map.from_struct()
|> Map.take([:id, :nickname])
- |> Map.put(:type, "user")
+ |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
+ |> Map.put("type", "user")
end
defp report_to_map(%Activity{} = report) do
%{
- type: "report",
- id: report.id,
- state: report.data["state"]
+ "type" => "report",
+ "id" => report.id,
+ "state" => report.data["state"]
}
end
defp status_to_map(%Activity{} = status) do
%{
- type: "status",
- id: status.id
+ "type" => "status",
+ "id" => status.id
}
end
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 8012389ac..d94ae5971 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -22,8 +22,8 @@ defmodule Pleroma.Notification do
schema "notifications" do
field(:seen, :boolean, default: false)
- belongs_to(:user, User, type: Pleroma.FlakeId)
- belongs_to(:activity, Activity, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+ belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
timestamps()
end
diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex
index b55379c4a..9d279fba7 100644
--- a/lib/pleroma/pagination.ex
+++ b/lib/pleroma/pagination.ex
@@ -64,6 +64,7 @@ def paginate(query, options, :keyset) do
def paginate(query, options, :offset) do
query
+ |> restrict(:order, options)
|> restrict(:offset, options)
|> restrict(:limit, options)
end
diff --git a/lib/pleroma/password_reset_token.ex b/lib/pleroma/password_reset_token.ex
index 4a833f6a5..db398b1fc 100644
--- a/lib/pleroma/password_reset_token.ex
+++ b/lib/pleroma/password_reset_token.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.PasswordResetToken do
alias Pleroma.User
schema "password_reset_tokens" do
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
field(:token, :string)
field(:used, :boolean, default: false)
diff --git a/lib/pleroma/registration.ex b/lib/pleroma/registration.ex
index 21fd1fc3f..8544461db 100644
--- a/lib/pleroma/registration.ex
+++ b/lib/pleroma/registration.ex
@@ -11,10 +11,10 @@ defmodule Pleroma.Registration do
alias Pleroma.Repo
alias Pleroma.User
- @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
+ @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
schema "registrations" do
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
field(:provider, :string)
field(:uid, :string)
field(:info, :map, default: %{})
diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex
index de0e54699..fea2cf3ff 100644
--- a/lib/pleroma/scheduled_activity.ex
+++ b/lib/pleroma/scheduled_activity.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.ScheduledActivity do
@min_offset :timer.minutes(5)
schema "scheduled_activities" do
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
field(:scheduled_at, :naive_datetime)
field(:params, :map)
diff --git a/lib/pleroma/thread_mute.ex b/lib/pleroma/thread_mute.ex
index 10d31679d..65cbbede3 100644
--- a/lib/pleroma/thread_mute.ex
+++ b/lib/pleroma/thread_mute.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.ThreadMute do
require Ecto.Query
schema "thread_mutes" do
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
field(:context, :string)
end
@@ -24,7 +24,7 @@ def changeset(mute, params \\ %{}) do
end
def query(user_id, context) do
- user_id = Pleroma.FlakeId.from_string(user_id)
+ {:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id)
ThreadMute
|> Ecto.Query.where(user_id: ^user_id)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index ab253a274..f3dcf7ad4 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -34,7 +34,7 @@ defmodule Pleroma.User do
@type t :: %__MODULE__{}
- @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
+ @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
@@ -106,9 +106,7 @@ def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
def profile_url(%User{ap_id: ap_id}), do: ap_id
def profile_url(_), do: nil
- def ap_id(%User{nickname: nickname}) do
- "#{Web.base_url()}/users/#{nickname}"
- end
+ def ap_id(%User{nickname: nickname}), do: "#{Web.base_url()}/users/#{nickname}"
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
@@ -119,12 +117,9 @@ def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
def user_info(%User{} = user, args \\ %{}) do
following_count =
- if args[:following_count],
- do: args[:following_count],
- else: user.info.following_count || following_count(user)
+ Map.get(args, :following_count, user.info.following_count || following_count(user))
- follower_count =
- if args[:follower_count], do: args[:follower_count], else: user.info.follower_count
+ follower_count = Map.get(args, :follower_count, user.info.follower_count)
%{
note_count: user.info.note_count,
@@ -137,12 +132,11 @@ def user_info(%User{} = user, args \\ %{}) do
end
def follow_state(%User{} = user, %User{} = target) do
- follow_activity = Utils.fetch_latest_follow(user, target)
-
- if follow_activity,
- do: follow_activity.data["state"],
+ case Utils.fetch_latest_follow(user, target) do
+ %{data: %{"state" => state}} -> state
# Ideally this would be nil, but then Cachex does not commit the value
- else: false
+ _ -> false
+ end
end
def get_cached_follow_state(user, target) do
@@ -152,11 +146,7 @@ def get_cached_follow_state(user, target) do
@spec set_follow_state_cache(String.t(), String.t(), String.t()) :: {:ok | :error, boolean()}
def set_follow_state_cache(user_ap_id, target_ap_id, state) do
- Cachex.put(
- :user_cache,
- "follow_state:#{user_ap_id}|#{target_ap_id}",
- state
- )
+ Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
end
def set_info_cache(user, args) do
@@ -197,34 +187,25 @@ def remote_user_creation(params) do
|> truncate_if_exists(:name, name_limit)
|> truncate_if_exists(:bio, bio_limit)
- info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
-
- changes =
- %User{}
+ changeset =
+ %User{local: false}
|> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
|> validate_required([:name, :ap_id])
|> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit)
- |> put_change(:local, false)
- |> put_embed(:info, info_cng)
+ |> change_info(&User.Info.remote_user_creation(&1, params[:info]))
- if changes.valid? do
- case info_cng.changes[:source_data] do
- %{"followers" => followers, "following" => following} ->
- changes
- |> put_change(:follower_address, followers)
- |> put_change(:following_address, following)
+ case params[:info][:source_data] do
+ %{"followers" => followers, "following" => following} ->
+ changeset
+ |> put_change(:follower_address, followers)
+ |> put_change(:following_address, following)
- _ ->
- followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
-
- changes
- |> put_change(:follower_address, followers)
- end
- else
- changes
+ _ ->
+ followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
+ put_change(changeset, :follower_address, followers)
end
end
@@ -245,7 +226,6 @@ def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
- info_cng = User.Info.user_upgrade(struct.info, params[:info], remote?)
struct
|> cast(params, [
@@ -260,7 +240,7 @@ def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit)
- |> put_embed(:info, info_cng)
+ |> change_info(&User.Info.user_upgrade(&1, params[:info], remote?))
end
def password_update_changeset(struct, params) do
@@ -311,43 +291,39 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
opts[:need_confirmation]
end
- info_change =
- User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?)
+ struct
+ |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
+ |> validate_required([:name, :nickname, :password, :password_confirmation])
+ |> validate_confirmation(:password)
+ |> unique_constraint(:email)
+ |> unique_constraint(:nickname)
+ |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
+ |> validate_format(:nickname, local_nickname_regex())
+ |> validate_format(:email, @email_regex)
+ |> validate_length(:bio, max: bio_limit)
+ |> validate_length(:name, min: 1, max: name_limit)
+ |> change_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
+ |> maybe_validate_required_email(opts[:external])
+ |> put_password_hash
+ |> put_ap_id()
+ |> unique_constraint(:ap_id)
+ |> put_following_and_follower_address()
+ end
- changeset =
- struct
- |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
- |> validate_required([:name, :nickname, :password, :password_confirmation])
- |> validate_confirmation(:password)
- |> unique_constraint(:email)
- |> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
- |> validate_format(:nickname, local_nickname_regex())
- |> validate_format(:email, @email_regex)
- |> validate_length(:bio, max: bio_limit)
- |> validate_length(:name, min: 1, max: name_limit)
- |> put_change(:info, info_change)
+ def maybe_validate_required_email(changeset, true), do: changeset
+ def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
- changeset =
- if opts[:external] do
- changeset
- else
- validate_required(changeset, [:email])
- end
+ defp put_ap_id(changeset) do
+ ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
+ put_change(changeset, :ap_id, ap_id)
+ end
- if changeset.valid? do
- ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
- followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
+ defp put_following_and_follower_address(changeset) do
+ followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
- changeset
- |> put_password_hash
- |> put_change(:ap_id, ap_id)
- |> unique_constraint(:ap_id)
- |> put_change(:following, [followers])
- |> put_change(:follower_address, followers)
- else
- changeset
- end
+ changeset
+ |> put_change(:following, [followers])
+ |> put_change(:follower_address, followers)
end
defp autofollow_users(user) do
@@ -362,9 +338,8 @@ defp autofollow_users(user) do
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
def register(%Ecto.Changeset{} = changeset) do
- with {:ok, user} <- Repo.insert(changeset),
- {:ok, user} <- post_register_action(user) do
- {:ok, user}
+ with {:ok, user} <- Repo.insert(changeset) do
+ post_register_action(user)
end
end
@@ -410,7 +385,7 @@ def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
end
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
- if not User.ap_enabled?(followed) do
+ if not ap_enabled?(followed) do
follow(follower, followed)
else
{:ok, follower}
@@ -443,9 +418,7 @@ def follow_all(follower, followeds) do
{1, [follower]} = Repo.update_all(q, [])
- Enum.each(followeds, fn followed ->
- update_follower_count(followed)
- end)
+ Enum.each(followeds, &update_follower_count/1)
set_cache(follower)
end
@@ -555,8 +528,6 @@ def set_cache(%User{} = user) do
def update_and_set_cache(changeset) do
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
set_cache(user)
- else
- e -> e
end
end
@@ -593,9 +564,7 @@ def get_cached_by_nickname(nickname) do
key = "nickname:#{nickname}"
Cachex.fetch!(:user_cache, key, fn ->
- user_result = get_or_fetch_by_nickname(nickname)
-
- case user_result do
+ case get_or_fetch_by_nickname(nickname) do
{:ok, user} -> {:commit, user}
{:error, _error} -> {:ignore, nil}
end
@@ -606,7 +575,7 @@ def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
cond do
- is_integer(nickname_or_id) or Pleroma.FlakeId.is_flake_id?(nickname_or_id) ->
+ is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
restrict_to_local == false ->
@@ -635,13 +604,11 @@ def get_by_nickname_or_email(nickname_or_email) do
def get_cached_user_info(user) do
key = "user_info:#{user.id}"
- Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end)
+ Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
end
def fetch_by_nickname(nickname) do
- ap_try = ActivityPub.make_user_from_nickname(nickname)
-
- case ap_try do
+ case ActivityPub.make_user_from_nickname(nickname) do
{:ok, user} -> {:ok, user}
_ -> OStatus.make_user(nickname)
end
@@ -676,7 +643,8 @@ def get_followers_query(%User{} = user, nil) do
end
def get_followers_query(user, page) do
- from(u in get_followers_query(user, nil))
+ user
+ |> get_followers_query(nil)
|> User.Query.paginate(page, 20)
end
@@ -685,25 +653,24 @@ def get_followers_query(user), do: get_followers_query(user, nil)
@spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
def get_followers(user, page \\ nil) do
- q = get_followers_query(user, page)
-
- {:ok, Repo.all(q)}
+ user
+ |> get_followers_query(page)
+ |> Repo.all()
end
@spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
def get_external_followers(user, page \\ nil) do
- q =
- user
- |> get_followers_query(page)
- |> User.Query.build(%{external: true})
-
- {:ok, Repo.all(q)}
+ user
+ |> get_followers_query(page)
+ |> User.Query.build(%{external: true})
+ |> Repo.all()
end
def get_followers_ids(user, page \\ nil) do
- q = get_followers_query(user, page)
-
- Repo.all(from(u in q, select: u.id))
+ user
+ |> get_followers_query(page)
+ |> select([u], u.id)
+ |> Repo.all()
end
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
@@ -712,7 +679,8 @@ def get_friends_query(%User{} = user, nil) do
end
def get_friends_query(user, page) do
- from(u in get_friends_query(user, nil))
+ user
+ |> get_friends_query(nil)
|> User.Query.paginate(page, 20)
end
@@ -720,28 +688,27 @@ def get_friends_query(user, page) do
def get_friends_query(user), do: get_friends_query(user, nil)
def get_friends(user, page \\ nil) do
- q = get_friends_query(user, page)
-
- {:ok, Repo.all(q)}
+ user
+ |> get_friends_query(page)
+ |> Repo.all()
end
def get_friends_ids(user, page \\ nil) do
- q = get_friends_query(user, page)
-
- Repo.all(from(u in q, select: u.id))
+ user
+ |> get_friends_query(page)
+ |> select([u], u.id)
+ |> Repo.all()
end
@spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
def get_follow_requests(%User{} = user) do
- users =
- Activity.follow_requests_for_actor(user)
- |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
- |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
- |> group_by([a, u], u.id)
- |> select([a, u], u)
- |> Repo.all()
-
- {:ok, users}
+ user
+ |> Activity.follow_requests_for_actor()
+ |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
+ |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
+ |> group_by([a, u], u.id)
+ |> select([a, u], u)
+ |> Repo.all()
end
def increase_note_count(%User{} = user) do
@@ -787,21 +754,15 @@ def decrease_note_count(%User{} = user) do
end
def update_note_count(%User{} = user) do
- note_count_query =
+ note_count =
from(
a in Object,
where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
select: count(a.id)
)
+ |> Repo.one()
- note_count = Repo.one(note_count_query)
-
- info_cng = User.Info.set_note_count(user.info, note_count)
-
- user
- |> change()
- |> put_embed(:info, info_cng)
- |> update_and_set_cache()
+ update_info(user, &User.Info.set_note_count(&1, note_count))
end
@spec maybe_fetch_follow_information(User.t()) :: User.t()
@@ -818,17 +779,7 @@ def maybe_fetch_follow_information(user) do
def fetch_follow_information(user) do
with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
- info_cng = User.Info.follow_information_update(user.info, info)
-
- changeset =
- user
- |> change()
- |> put_embed(:info, info_cng)
-
- update_and_set_cache(changeset)
- else
- {:error, _} = e -> e
- e -> {:error, e}
+ update_info(user, &User.Info.follow_information_update(&1, info))
end
end
@@ -902,62 +853,28 @@ def get_recipients_from_activity(%Activity{recipients: to}) do
@spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
- info = muter.info
-
- info_cng =
- User.Info.add_to_mutes(info, ap_id)
- |> User.Info.add_to_muted_notifications(info, ap_id, notifications?)
-
- cng =
- change(muter)
- |> put_embed(:info, info_cng)
-
- update_and_set_cache(cng)
+ update_info(muter, &User.Info.add_to_mutes(&1, ap_id, notifications?))
end
def unmute(muter, %{ap_id: ap_id}) do
- info = muter.info
-
- info_cng =
- User.Info.remove_from_mutes(info, ap_id)
- |> User.Info.remove_from_muted_notifications(info, ap_id)
-
- cng =
- change(muter)
- |> put_embed(:info, info_cng)
-
- update_and_set_cache(cng)
+ update_info(muter, &User.Info.remove_from_mutes(&1, ap_id))
end
def subscribe(subscriber, %{ap_id: ap_id}) do
- deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
-
with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
- blocked = blocks?(subscribed, subscriber) and deny_follow_blocked
+ deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
- if blocked do
+ if blocks?(subscribed, subscriber) and deny_follow_blocked do
{:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
else
- info_cng =
- subscribed.info
- |> User.Info.add_to_subscribers(subscriber.ap_id)
-
- change(subscribed)
- |> put_embed(:info, info_cng)
- |> update_and_set_cache()
+ update_info(subscribed, &User.Info.add_to_subscribers(&1, subscriber.ap_id))
end
end
end
def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
with %User{} = user <- get_cached_by_ap_id(ap_id) do
- info_cng =
- user.info
- |> User.Info.remove_from_subscribers(unsubscriber.ap_id)
-
- change(user)
- |> put_embed(:info, info_cng)
- |> update_and_set_cache()
+ update_info(user, &User.Info.remove_from_subscribers(&1, unsubscriber.ap_id))
end
end
@@ -986,21 +903,11 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do
blocker
end
- if following?(blocked, blocker) do
- unfollow(blocked, blocker)
- end
+ if following?(blocked, blocker), do: unfollow(blocked, blocker)
{:ok, blocker} = update_follower_count(blocker)
- info_cng =
- blocker.info
- |> User.Info.add_to_block(ap_id)
-
- cng =
- change(blocker)
- |> put_embed(:info, info_cng)
-
- update_and_set_cache(cng)
+ update_info(blocker, &User.Info.add_to_block(&1, ap_id))
end
# helper to handle the block given only an actor's AP id
@@ -1009,15 +916,7 @@ def block(blocker, %{ap_id: ap_id}) do
end
def unblock(blocker, %{ap_id: ap_id}) do
- info_cng =
- blocker.info
- |> User.Info.remove_from_block(ap_id)
-
- cng =
- change(blocker)
- |> put_embed(:info, info_cng)
-
- update_and_set_cache(cng)
+ update_info(blocker, &User.Info.remove_from_block(&1, ap_id))
end
def mutes?(nil, _), do: false
@@ -1074,27 +973,11 @@ def subscribers(user) do
end
def block_domain(user, domain) do
- info_cng =
- user.info
- |> User.Info.add_to_domain_block(domain)
-
- cng =
- change(user)
- |> put_embed(:info, info_cng)
-
- update_and_set_cache(cng)
+ update_info(user, &User.Info.add_to_domain_block(&1, domain))
end
def unblock_domain(user, domain) do
- info_cng =
- user.info
- |> User.Info.remove_from_domain_block(domain)
-
- cng =
- change(user)
- |> put_embed(:info, info_cng)
-
- update_and_set_cache(cng)
+ update_info(user, &User.Info.remove_from_domain_block(&1, domain))
end
def deactivate_async(user, status \\ true) do
@@ -1102,28 +985,16 @@ def deactivate_async(user, status \\ true) do
end
def deactivate(%User{} = user, status \\ true) do
- info_cng = User.Info.set_activation_status(user.info, status)
-
- with {:ok, friends} <- User.get_friends(user),
- {:ok, followers} <- User.get_followers(user),
- {:ok, user} <-
- user
- |> change()
- |> put_embed(:info, info_cng)
- |> update_and_set_cache() do
- Enum.each(followers, &invalidate_cache(&1))
- Enum.each(friends, &update_follower_count(&1))
+ with {:ok, user} <- update_info(user, &User.Info.set_activation_status(&1, status)) do
+ Enum.each(get_followers(user), &invalidate_cache/1)
+ Enum.each(get_friends(user), &update_follower_count/1)
{:ok, user}
end
end
def update_notification_settings(%User{} = user, settings \\ %{}) do
- info_changeset = User.Info.update_notification_settings(user.info, settings)
-
- change(user)
- |> put_embed(:info, info_changeset)
- |> update_and_set_cache()
+ update_info(user, &User.Info.update_notification_settings(&1, settings))
end
def delete(%User{} = user) do
@@ -1137,18 +1008,18 @@ def perform(:delete, %User{} = user) do
{:ok, _user} = ActivityPub.delete(user)
# Remove all relationships
- {:ok, followers} = User.get_followers(user)
-
- Enum.each(followers, fn follower ->
+ user
+ |> get_followers()
+ |> Enum.each(fn follower ->
ActivityPub.unfollow(follower, user)
- User.unfollow(follower, user)
+ unfollow(follower, user)
end)
- {:ok, friends} = User.get_friends(user)
-
- Enum.each(friends, fn followed ->
+ user
+ |> get_friends()
+ |> Enum.each(fn followed ->
ActivityPub.unfollow(user, followed)
- User.unfollow(user, followed)
+ unfollow(user, followed)
end)
delete_user_activities(user)
@@ -1160,13 +1031,11 @@ def perform(:delete, %User{} = user) do
def perform(:fetch_initial_posts, %User{} = user) do
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
- Enum.each(
- # Insert all the posts in reverse order, so they're in the right order on the timeline
- Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
- &Pleroma.Web.Federator.incoming_ap_doc/1
- )
-
- {:ok, user}
+ # Insert all the posts in reverse order, so they're in the right order on the timeline
+ user.info.source_data["outbox"]
+ |> Utils.fetch_ordered_collection(pages)
+ |> Enum.reverse()
+ |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
end
def perform(:deactivate_async, user, status), do: deactivate(user, status)
@@ -1252,16 +1121,12 @@ def follow_import(%User{} = follower, followed_identifiers)
})
end
- def delete_user_activities(%User{ap_id: ap_id} = user) do
+ def delete_user_activities(%User{ap_id: ap_id}) do
ap_id
|> Activity.Queries.by_actor()
|> RepoStreamer.chunk_stream(50)
- |> Stream.each(fn activities ->
- Enum.each(activities, &delete_activity(&1))
- end)
+ |> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
|> Stream.run()
-
- {:ok, user}
end
defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
@@ -1271,17 +1136,19 @@ defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
end
defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
- user = get_cached_by_ap_id(activity.actor)
object = Object.normalize(activity)
- ActivityPub.unlike(user, object)
+ activity.actor
+ |> get_cached_by_ap_id()
+ |> ActivityPub.unlike(object)
end
defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
- user = get_cached_by_ap_id(activity.actor)
object = Object.normalize(activity)
- ActivityPub.unannounce(user, object)
+ activity.actor
+ |> get_cached_by_ap_id()
+ |> ActivityPub.unannounce(object)
end
defp delete_activity(_activity), do: "Doing nothing"
@@ -1293,9 +1160,7 @@ def html_filter_policy(%User{info: %{no_rich_text: true}}) do
def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
def fetch_by_ap_id(ap_id) do
- ap_try = ActivityPub.make_user_from_ap_id(ap_id)
-
- case ap_try do
+ case ActivityPub.make_user_from_ap_id(ap_id) do
{:ok, user} ->
{:ok, user}
@@ -1310,7 +1175,7 @@ def fetch_by_ap_id(ap_id) do
def get_or_fetch_by_ap_id(ap_id) do
user = get_cached_by_ap_id(ap_id)
- if !is_nil(user) and !User.needs_update?(user) do
+ if !is_nil(user) and !needs_update?(user) do
{:ok, user}
else
# Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
@@ -1330,19 +1195,20 @@ def get_or_fetch_by_ap_id(ap_id) do
@doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
- if user = get_cached_by_ap_id(uri) do
+ with %User{} = user <- get_cached_by_ap_id(uri) do
user
else
- changes =
- %User{info: %User.Info{}}
- |> cast(%{}, [:ap_id, :nickname, :local])
- |> put_change(:ap_id, uri)
- |> put_change(:nickname, nickname)
- |> put_change(:local, true)
- |> put_change(:follower_address, uri <> "/followers")
+ _ ->
+ {:ok, user} =
+ %User{info: %User.Info{}}
+ |> cast(%{}, [:ap_id, :nickname, :local])
+ |> put_change(:ap_id, uri)
+ |> put_change(:nickname, nickname)
+ |> put_change(:local, true)
+ |> put_change(:follower_address, uri <> "/followers")
+ |> Repo.insert()
- {:ok, user} = Repo.insert(changes)
- user
+ user
end
end
@@ -1399,23 +1265,21 @@ def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
# this is because we have synchronous follow APIs and need to simulate them
# with an async handshake
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
- with %User{} = a <- User.get_cached_by_id(a.id),
- %User{} = b <- User.get_cached_by_id(b.id) do
+ with %User{} = a <- get_cached_by_id(a.id),
+ %User{} = b <- get_cached_by_id(b.id) do
{:ok, a, b}
else
- _e ->
- :error
+ nil -> :error
end
end
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
with :ok <- :timer.sleep(timeout),
- %User{} = a <- User.get_cached_by_id(a.id),
- %User{} = b <- User.get_cached_by_id(b.id) do
+ %User{} = a <- get_cached_by_id(a.id),
+ %User{} = b <- get_cached_by_id(b.id) do
{:ok, a, b}
else
- _e ->
- :error
+ nil -> :error
end
end
@@ -1477,7 +1341,7 @@ defp update_tags(%User{} = user, new_tags) do
defp normalize_tags(tags) do
[tags]
|> List.flatten()
- |> Enum.map(&String.downcase(&1))
+ |> Enum.map(&String.downcase/1)
end
defp local_nickname_regex do
@@ -1570,11 +1434,7 @@ def list_inactive_users_query(inactivity_threshold \\ 7) do
@spec switch_email_notifications(t(), String.t(), boolean()) ::
{:ok, t()} | {:error, Ecto.Changeset.t()}
def switch_email_notifications(user, type, status) do
- info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
-
- change(user)
- |> put_embed(:info, info)
- |> update_and_set_cache()
+ update_info(user, &User.Info.update_email_notifications(&1, %{type => status}))
end
@doc """
@@ -1596,13 +1456,8 @@ def touch_last_digest_emailed_at(user) do
def toggle_confirmation(%User{} = user) do
need_confirmation? = !user.info.confirmation_pending
- info_changeset =
- User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
-
user
- |> change()
- |> put_embed(:info, info_changeset)
- |> update_and_set_cache()
+ |> update_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
end
def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
@@ -1625,16 +1480,11 @@ def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
}
end
- def ensure_keys_present(%User{info: info} = user) do
- if info.keys do
- {:ok, user}
- else
- {:ok, pem} = Keys.generate_rsa_pem()
+ def ensure_keys_present(%{info: %{keys: keys}} = user) when not is_nil(keys), do: {:ok, user}
- user
- |> Ecto.Changeset.change()
- |> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem))
- |> update_and_set_cache()
+ def ensure_keys_present(%User{} = user) do
+ with {:ok, pem} <- Keys.generate_rsa_pem() do
+ update_info(user, &User.Info.set_keys(&1, pem))
end
end
@@ -1680,4 +1530,26 @@ def change_email(user, email) do
|> validate_format(:email, @email_regex)
|> update_and_set_cache()
end
+
+ @doc """
+ Changes `user.info` and returns the user changeset.
+
+ `fun` is called with the `user.info`.
+ """
+ def change_info(user, fun) do
+ changeset = change(user)
+ info = get_field(changeset, :info) || %User.Info{}
+ put_embed(changeset, :info, fun.(info))
+ end
+
+ @doc """
+ Updates `user.info` and sets cache.
+
+ `fun` is called with the `user.info`.
+ """
+ def update_info(user, fun) do
+ user
+ |> change_info(fun)
+ |> update_and_set_cache()
+ end
end
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 99745f496..ebd4ddebf 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -54,6 +54,7 @@ defmodule Pleroma.User.Info do
field(:pleroma_settings_store, :map, default: %{})
field(:fields, {:array, :map}, default: nil)
field(:raw_fields, {:array, :map}, default: [])
+ field(:discoverable, :boolean, default: false)
field(:notification_settings, :map,
default: %{
@@ -187,16 +188,11 @@ def set_subscribers(info, subscribers) do
|> validate_required([:subscribers])
end
- @spec add_to_mutes(Info.t(), String.t()) :: Changeset.t()
- def add_to_mutes(info, muted) do
- set_mutes(info, Enum.uniq([muted | info.mutes]))
- end
-
- @spec add_to_muted_notifications(Changeset.t(), Info.t(), String.t(), boolean()) ::
- Changeset.t()
- def add_to_muted_notifications(changeset, info, muted, notifications?) do
- set_notification_mutes(
- changeset,
+ @spec add_to_mutes(Info.t(), String.t(), boolean()) :: Changeset.t()
+ def add_to_mutes(info, muted, notifications?) do
+ info
+ |> set_mutes(Enum.uniq([muted | info.mutes]))
+ |> set_notification_mutes(
Enum.uniq([muted | info.muted_notifications]),
notifications?
)
@@ -204,12 +200,9 @@ def add_to_muted_notifications(changeset, info, muted, notifications?) do
@spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t()
def remove_from_mutes(info, muted) do
- set_mutes(info, List.delete(info.mutes, muted))
- end
-
- @spec remove_from_muted_notifications(Changeset.t(), Info.t(), String.t()) :: Changeset.t()
- def remove_from_muted_notifications(changeset, info, muted) do
- set_notification_mutes(changeset, List.delete(info.muted_notifications, muted), true)
+ info
+ |> set_mutes(List.delete(info.mutes, muted))
+ |> set_notification_mutes(List.delete(info.muted_notifications, muted), true)
end
def add_to_block(info, blocked) do
@@ -277,7 +270,8 @@ def remote_user_creation(info, params) do
:hide_follows_count,
:follower_count,
:fields,
- :following_count
+ :following_count,
+ :discoverable
])
|> validate_fields(true)
end
@@ -295,6 +289,7 @@ def user_upgrade(info, params, remote? \\ false) do
:hide_follows,
:fields,
:hide_followers,
+ :discoverable,
:hide_followers_count,
:hide_follows_count
])
@@ -318,7 +313,8 @@ def profile_update(info, params) do
:skip_thread_containment,
:fields,
:raw_fields,
- :pleroma_settings_store
+ :pleroma_settings_store,
+ :discoverable
])
|> validate_fields()
end
@@ -342,9 +338,7 @@ defp valid_field?(%{"name" => name, "value" => value}) do
name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
- is_binary(name) &&
- is_binary(value) &&
- String.length(name) <= name_limit &&
+ is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
String.length(value) <= value_limit
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 1cf8b6151..8d0a57623 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -510,7 +510,7 @@ def fetch_activities_for_context(context, opts \\ %{}) do
end
@spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
- Pleroma.FlakeId.t() | nil
+ FlakeId.Ecto.CompatType.t() | nil
def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
context
|> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
@@ -519,13 +519,13 @@ def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
|> Repo.one()
end
- def fetch_public_activities(opts \\ %{}) do
+ def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do
opts = Map.drop(opts, ["user"])
[Pleroma.Constants.as_public()]
|> fetch_activities_query(opts)
|> restrict_unlisted()
- |> Pagination.fetch_paginated(opts)
+ |> Pagination.fetch_paginated(opts, pagination)
|> Enum.reverse()
end
@@ -834,7 +834,7 @@ defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
defp restrict_muted_reblogs(query, _), do: query
- defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query
+ defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
defp exclude_poll_votes(query, _) do
if has_named_binding?(query, :object) do
@@ -918,11 +918,11 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> exclude_poll_votes(opts)
end
- def fetch_activities(recipients, opts \\ %{}) do
+ def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
list_memberships = Pleroma.List.memberships(opts["user"])
fetch_activities_query(recipients ++ list_memberships, opts)
- |> Pagination.fetch_paginated(opts)
+ |> Pagination.fetch_paginated(opts, pagination)
|> Enum.reverse()
|> maybe_update_cc(list_memberships, opts["user"])
end
@@ -953,10 +953,15 @@ def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
)
end
- def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do
+ def fetch_activities_bounded(
+ recipients,
+ recipients_with_public,
+ opts \\ %{},
+ pagination \\ :keyset
+ ) do
fetch_activities_query([], opts)
|> fetch_activities_bounded_query(recipients, recipients_with_public)
- |> Pagination.fetch_paginated(opts)
+ |> Pagination.fetch_paginated(opts, pagination)
|> Enum.reverse()
end
@@ -996,6 +1001,7 @@ defp object_to_user_data(data) do
locked = data["manuallyApprovesFollowers"] || false
data = Transmogrifier.maybe_fix_user_object(data)
+ discoverable = data["discoverable"] || false
user_data = %{
ap_id: data["id"],
@@ -1004,7 +1010,8 @@ defp object_to_user_data(data) do
source_data: data,
banner: banner,
fields: fields,
- locked: locked
+ locked: locked,
+ discoverable: discoverable
},
avatar: avatar,
name: data["name"],
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 9eb86106f..8112f6642 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -231,13 +231,43 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
end
end
- def outbox(conn, %{"nickname" => nickname} = params) do
+ def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
+ when page? in [true, "true"] do
+ with %User{} = user <- User.get_cached_by_nickname(nickname),
+ {:ok, user} <- User.ensure_keys_present(user) do
+ activities =
+ if params["max_id"] do
+ ActivityPub.fetch_user_activities(user, nil, %{
+ "max_id" => params["max_id"],
+ # This is a hack because postgres generates inefficient queries when filtering by
+ # 'Answer', poll votes will be hidden by the visibility filter in this case anyway
+ "include_poll_votes" => true,
+ "limit" => 10
+ })
+ else
+ ActivityPub.fetch_user_activities(user, nil, %{
+ "limit" => 10,
+ "include_poll_votes" => true
+ })
+ end
+
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("activity_collection_page.json", %{
+ activities: activities,
+ iri: "#{user.ap_id}/outbox"
+ })
+ end
+ end
+
+ def outbox(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- User.ensure_keys_present(user) do
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
- |> render("outbox.json", %{user: user, max_id: params["max_id"]})
+ |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
end
end
@@ -315,12 +345,37 @@ def whoami(_conn, _params), do: {:error, :not_found}
def read_inbox(
%{assigns: %{user: %{nickname: nickname} = user}} = conn,
- %{"nickname" => nickname} = params
- ) do
+ %{"nickname" => nickname, "page" => page?} = params
+ )
+ when page? in [true, "true"] do
+ activities =
+ if params["max_id"] do
+ ActivityPub.fetch_activities([user.ap_id | user.following], %{
+ "max_id" => params["max_id"],
+ "limit" => 10
+ })
+ else
+ ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
+ end
+
conn
|> put_resp_content_type("application/activity+json")
|> put_view(UserView)
- |> render("inbox.json", user: user, max_id: params["max_id"])
+ |> render("activity_collection_page.json", %{
+ activities: activities,
+ iri: "#{user.ap_id}/inbox"
+ })
+ end
+
+ def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
+ "nickname" => nickname
+ }) do
+ with {:ok, user} <- User.ensure_keys_present(user) do
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
+ end
end
def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 114251b24..3866dacee 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -111,11 +111,11 @@ defp should_federate?(inbox, public) do
@spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
defp recipients(actor, activity) do
- {:ok, followers} =
+ followers =
if actor.follower_address in activity.recipients do
User.get_external_followers(actor)
else
- {:ok, []}
+ []
end
fetchers =
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 352d856fa..993307287 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
alias Pleroma.Keys
alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Endpoint
@@ -107,7 +106,8 @@ def render("user.json", %{user: user}) do
},
"endpoints" => endpoints,
"attachment" => fields,
- "tag" => (user.info.source_data["tag"] || []) ++ emoji_tags
+ "tag" => (user.info.source_data["tag"] || []) ++ emoji_tags,
+ "discoverable" => user.info.discoverable
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
@@ -210,20 +210,16 @@ def render("followers.json", %{user: user} = opts) do
|> Map.merge(Utils.make_json_ld_header())
end
- def render("outbox.json", %{user: user, max_id: max_qid}) do
- params = %{
- "limit" => "10"
+ def render("activity_collection.json", %{iri: iri}) do
+ %{
+ "id" => iri,
+ "type" => "OrderedCollection",
+ "first" => "#{iri}?page=true"
}
+ |> Map.merge(Utils.make_json_ld_header())
+ end
- params =
- if max_qid != nil do
- Map.put(params, "max_id", max_qid)
- else
- params
- end
-
- activities = ActivityPub.fetch_user_activities(user, nil, params)
-
+ def render("activity_collection_page.json", %{activities: activities, iri: iri}) do
# this is sorted chronologically, so first activity is the newest (max)
{max_id, min_id, collection} =
if length(activities) > 0 do
@@ -243,71 +239,14 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
}
end
- iri = "#{user.ap_id}/outbox"
-
- page = %{
- "id" => "#{iri}?max_id=#{max_id}",
+ %{
+ "id" => "#{iri}?max_id=#{max_id}&page=true",
"type" => "OrderedCollectionPage",
"partOf" => iri,
"orderedItems" => collection,
- "next" => "#{iri}?max_id=#{min_id}"
+ "next" => "#{iri}?max_id=#{min_id}&page=true"
}
-
- if max_qid == nil do
- %{
- "id" => iri,
- "type" => "OrderedCollection",
- "first" => page
- }
- |> Map.merge(Utils.make_json_ld_header())
- else
- page |> Map.merge(Utils.make_json_ld_header())
- end
- end
-
- def render("inbox.json", %{user: user, max_id: max_qid}) do
- params = %{
- "limit" => "10"
- }
-
- params =
- if max_qid != nil do
- Map.put(params, "max_id", max_qid)
- else
- params
- end
-
- activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
-
- min_id = Enum.at(Enum.reverse(activities), 0).id
- max_id = Enum.at(activities, 0).id
-
- collection =
- Enum.map(activities, fn act ->
- {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
- data
- end)
-
- iri = "#{user.ap_id}/inbox"
-
- page = %{
- "id" => "#{iri}?max_id=#{max_id}",
- "type" => "OrderedCollectionPage",
- "partOf" => iri,
- "orderedItems" => collection,
- "next" => "#{iri}?max_id=#{min_id}"
- }
-
- if max_qid == nil do
- %{
- "id" => iri,
- "type" => "OrderedCollection",
- "first" => page
- }
- |> Map.merge(Utils.make_json_ld_header())
- else
- page |> Map.merge(Utils.make_json_ld_header())
- end
+ |> Map.merge(Utils.make_json_ld_header())
end
def collection(collection, iri, page, show_items \\ true, total \\ nil) do
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index 0d1db8fa0..90aef99f7 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -18,7 +18,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Web.AdminAPI.ReportView
alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Endpoint
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.Router
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
@@ -254,18 +256,12 @@ def right_add(%{assigns: %{user: admin}} = conn, %{
"nickname" => nickname
})
when permission_group in ["moderator", "admin"] do
- user = User.get_cached_by_nickname(nickname)
+ info = Map.put(%{}, "is_" <> permission_group, true)
- info =
- %{}
- |> Map.put("is_" <> permission_group, true)
-
- info_cng = User.Info.admin_api_update(user.info, info)
-
- cng =
- user
- |> Ecto.Changeset.change()
- |> Ecto.Changeset.put_embed(:info, info_cng)
+ {:ok, user} =
+ nickname
+ |> User.get_cached_by_nickname()
+ |> User.update_info(&User.Info.admin_api_update(&1, info))
ModerationLog.insert_log(%{
action: "grant",
@@ -274,8 +270,6 @@ def right_add(%{assigns: %{user: admin}} = conn, %{
permission: permission_group
})
- {:ok, _user} = User.update_and_set_cache(cng)
-
json(conn, info)
end
@@ -293,40 +287,33 @@ def right_get(conn, %{"nickname" => nickname}) do
})
end
+ def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
+ render_error(conn, :forbidden, "You can't revoke your own admin status.")
+ end
+
def right_delete(
- %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
+ %{assigns: %{user: admin}} = conn,
%{
"permission_group" => permission_group,
"nickname" => nickname
}
)
when permission_group in ["moderator", "admin"] do
- if admin_nickname == nickname do
- render_error(conn, :forbidden, "You can't revoke your own admin status.")
- else
- user = User.get_cached_by_nickname(nickname)
+ info = Map.put(%{}, "is_" <> permission_group, false)
- info =
- %{}
- |> Map.put("is_" <> permission_group, false)
+ {:ok, user} =
+ nickname
+ |> User.get_cached_by_nickname()
+ |> User.update_info(&User.Info.admin_api_update(&1, info))
- info_cng = User.Info.admin_api_update(user.info, info)
+ ModerationLog.insert_log(%{
+ action: "revoke",
+ actor: admin,
+ subject: user,
+ permission: permission_group
+ })
- cng =
- Ecto.Changeset.change(user)
- |> Ecto.Changeset.put_embed(:info, info_cng)
-
- {:ok, _user} = User.update_and_set_cache(cng)
-
- ModerationLog.insert_log(%{
- action: "revoke",
- actor: admin,
- subject: user,
- permission: permission_group
- })
-
- json(conn, info)
- end
+ json(conn, info)
end
def right_delete(conn, _) do
@@ -450,7 +437,10 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
{:ok, token} = Pleroma.PasswordResetToken.create_token(user)
conn
- |> json(token.token)
+ |> json(%{
+ token: token.token,
+ link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
+ })
end
@doc "Force password reset for a given user"
@@ -463,13 +453,17 @@ def force_password_reset(conn, %{"nickname" => nickname}) do
end
def list_reports(conn, params) do
+ {page, page_size} = page_params(params)
+
params =
params
|> Map.put("type", "Flag")
|> Map.put("skip_preload", true)
|> Map.put("total", true)
+ |> Map.put("limit", page_size)
+ |> Map.put("offset", (page - 1) * page_size)
- reports = ActivityPub.fetch_activities([], params)
+ reports = ActivityPub.fetch_activities([], params, :offset)
conn
|> put_view(ReportView)
@@ -562,7 +556,15 @@ def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
def list_log(conn, params) do
{page, page_size} = page_params(params)
- log = ModerationLog.get_all(page, page_size)
+ log =
+ ModerationLog.get_all(%{
+ page: page,
+ page_size: page_size,
+ start_date: params["start_date"],
+ end_date: params["end_date"],
+ user_id: params["user_id"],
+ search: params["search"]
+ })
conn
|> put_view(ModerationLogView)
diff --git a/lib/pleroma/web/admin_api/views/moderation_log_view.ex b/lib/pleroma/web/admin_api/views/moderation_log_view.ex
index b3fc7cfe5..e7752d1f3 100644
--- a/lib/pleroma/web/admin_api/views/moderation_log_view.ex
+++ b/lib/pleroma/web/admin_api/views/moderation_log_view.ex
@@ -8,7 +8,10 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogView do
alias Pleroma.ModerationLog
def render("index.json", %{log: log}) do
- render_many(log, __MODULE__, "show.json", as: :log_entry)
+ %{
+ items: render_many(log.items, __MODULE__, "show.json", as: :log_entry),
+ total: log.count
+ }
end
def render("show.json", %{log_entry: log_entry}) do
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 9ee704022..4a74dc16f 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -306,16 +306,14 @@ defp put_emoji(map, text, emojis) do
# Updates the emojis for a user based on their profile
def update(user) do
+ emoji = emoji_from_profile(user)
+ source_data = user.info |> Map.get(:source_data, {}) |> Map.put("tag", emoji)
+
user =
- with emoji <- emoji_from_profile(user),
- source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
- info_cng <- User.Info.set_source_data(user.info, source_data),
- change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
- {:ok, user} <- User.update_and_set_cache(change) do
+ with {:ok, user} <- User.update_info(user, &User.Info.set_source_data(&1, source_data)) do
user
else
- _e ->
- user
+ _e -> user
end
ActivityPub.update(%{
@@ -340,34 +338,21 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
}
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- Visibility.is_public?(activity),
- %{valid?: true} = info_changeset <- User.Info.add_pinnned_activity(user.info, activity),
- changeset <-
- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
- {:ok, _user} <- User.update_and_set_cache(changeset) do
+ {:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do
{:ok, activity}
else
- %{errors: [pinned_activities: {err, _}]} ->
- {:error, err}
-
- _ ->
- {:error, dgettext("errors", "Could not pin")}
+ {:error, %{changes: %{info: %{errors: [pinned_activities: {err, _}]}}}} -> {:error, err}
+ _ -> {:error, dgettext("errors", "Could not pin")}
end
end
def unpin(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
- %{valid?: true} = info_changeset <-
- User.Info.remove_pinnned_activity(user.info, activity),
- changeset <-
- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
- {:ok, _user} <- User.update_and_set_cache(changeset) do
+ {:ok, _user} <- User.update_info(user, &User.Info.remove_pinnned_activity(&1, activity)) do
{:ok, activity}
else
- %{errors: [pinned_activities: {err, _}]} ->
- {:error, err}
-
- _ ->
- {:error, dgettext("errors", "Could not unpin")}
+ %{errors: [pinned_activities: {err, _}]} -> {:error, err}
+ _ -> {:error, dgettext("errors", "Could not unpin")}
end
end
@@ -462,23 +447,15 @@ defp set_visibility(activity, %{"visibility" => visibility}) do
defp set_visibility(activity, _), do: {:ok, activity}
- def hide_reblogs(user, muted) do
- ap_id = muted.ap_id
-
+ def hide_reblogs(user, %{ap_id: ap_id} = _muted) do
if ap_id not in user.info.muted_reblogs do
- info_changeset = User.Info.add_reblog_mute(user.info, ap_id)
- changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
- User.update_and_set_cache(changeset)
+ User.update_info(user, &User.Info.add_reblog_mute(&1, ap_id))
end
end
- def show_reblogs(user, muted) do
- ap_id = muted.ap_id
-
+ def show_reblogs(user, %{ap_id: ap_id} = _muted) do
if ap_id in user.info.muted_reblogs do
- info_changeset = User.Info.remove_reblog_mute(user.info, ap_id)
- changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
- User.update_and_set_cache(changeset)
+ User.update_info(user, &User.Info.remove_reblog_mute(&1, ap_id))
end
end
end
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 1fb95f4ab..52fbc162b 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
# This is a hack for twidere.
def get_by_id_or_ap_id(id) do
activity =
- with true <- Pleroma.FlakeId.is_flake_id?(id),
+ with true <- FlakeId.flake_id?(id),
%Activity{} = activity <- Activity.get_by_id_with_object(id) do
activity
else
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index bb81b061e..1e88ff7fe 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -16,7 +16,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Emoji
alias Pleroma.Filter
alias Pleroma.HTTP
- alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Pagination
alias Pleroma.Plugs.RateLimiter
@@ -35,7 +34,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.MastodonAPI.MastodonView
- alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.ReportView
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
alias Pleroma.Web.MastodonAPI.StatusView
@@ -153,7 +151,8 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
:hide_follows,
:hide_favorites,
:show_role,
- :skip_thread_containment
+ :skip_thread_containment,
+ :discoverable
]
|> Enum.reduce(%{}, fn key, acc ->
add_if_present(acc, params, to_string(key), key, fn value ->
@@ -188,14 +187,13 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
end)
|> Map.put(:emoji, user_info_emojis)
- info_cng = User.Info.profile_update(user.info, info_params)
+ changeset =
+ user
+ |> User.update_changeset(user_params)
+ |> User.change_info(&User.Info.profile_update(&1, info_params))
- with changeset <- User.update_changeset(user, user_params),
- changeset <- Changeset.put_embed(changeset, :info, info_cng),
- {:ok, user} <- User.update_and_set_cache(changeset) do
- if original_user != user do
- CommonAPI.update(user)
- end
+ with {:ok, user} <- User.update_and_set_cache(changeset) do
+ if original_user != user, do: CommonAPI.update(user)
json(
conn,
@@ -225,12 +223,10 @@ def update_avatar(%{assigns: %{user: user}} = conn, params) do
end
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
- with new_info <- %{"banner" => %{}},
- info_cng <- User.Info.profile_update(user.info, new_info),
- changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
- {:ok, user} <- User.update_and_set_cache(changeset) do
- CommonAPI.update(user)
+ new_info = %{"banner" => %{}}
+ with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+ CommonAPI.update(user)
json(conn, %{url: nil})
end
end
@@ -238,9 +234,7 @@ def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
def update_banner(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
new_info <- %{"banner" => object.data},
- info_cng <- User.Info.profile_update(user.info, new_info),
- changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
- {:ok, user} <- User.update_and_set_cache(changeset) do
+ {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
CommonAPI.update(user)
%{"url" => [%{"href" => href} | _]} = object.data
@@ -249,10 +243,9 @@ def update_banner(%{assigns: %{user: user}} = conn, params) do
end
def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
- with new_info <- %{"background" => %{}},
- info_cng <- User.Info.profile_update(user.info, new_info),
- changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
- {:ok, _user} <- User.update_and_set_cache(changeset) do
+ new_info = %{"background" => %{}}
+
+ with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
json(conn, %{url: nil})
end
end
@@ -260,9 +253,7 @@ def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
def update_background(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(params, type: :background),
new_info <- %{"background" => object.data},
- info_cng <- User.Info.profile_update(user.info, new_info),
- changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
- {:ok, _user} <- User.update_and_set_cache(changeset) do
+ {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
%{"url" => [%{"href" => href} | _]} = object.data
json(conn, %{url: href})
@@ -722,49 +713,6 @@ def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end
end
- def notifications(%{assigns: %{user: user}} = conn, params) do
- notifications = MastodonAPI.get_notifications(user, params)
-
- conn
- |> add_link_headers(notifications)
- |> put_view(NotificationView)
- |> render("index.json", %{notifications: notifications, for: user})
- end
-
- def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
- with {:ok, notification} <- Notification.get(user, id) do
- conn
- |> put_view(NotificationView)
- |> render("show.json", %{notification: notification, for: user})
- else
- {:error, reason} ->
- conn
- |> put_status(:forbidden)
- |> json(%{"error" => reason})
- end
- end
-
- def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
- Notification.clear(user)
- json(conn, %{})
- end
-
- def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
- with {:ok, _notif} <- Notification.dismiss(user, id) do
- json(conn, %{})
- else
- {:error, reason} ->
- conn
- |> put_status(:forbidden)
- |> json(%{"error" => reason})
- end
- end
-
- def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do
- Notification.destroy_multiple(user, ids)
- json(conn, %{})
- end
-
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
id = List.wrap(id)
q = from(u in User, where: u.id in ^id)
@@ -816,26 +764,16 @@ def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
%{} = attachment_data <- Map.put(object.data, "id", object.id),
- %{type: type} = rendered <-
+ # Reject if not an image
+ %{type: "image"} = rendered <-
StatusView.render("attachment.json", %{attachment: attachment_data}) do
- # Reject if not an image
- if type == "image" do
- # Sure!
- # Save to the user's info
- info_changeset = User.Info.mascot_update(user.info, rendered)
+ # Sure!
+ # Save to the user's info
+ {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, rendered))
- user_changeset =
- user
- |> Changeset.change()
- |> Changeset.put_embed(:info, info_changeset)
-
- {:ok, _user} = User.update_and_set_cache(user_changeset)
-
- conn
- |> json(rendered)
- else
- render_error(conn, :unsupported_media_type, "mascots can only be images")
- end
+ json(conn, rendered)
+ else
+ %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
end
end
@@ -958,11 +896,11 @@ def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
end
def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
- with {:ok, follow_requests} <- User.get_follow_requests(followed) do
- conn
- |> put_view(AccountView)
- |> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
- end
+ follow_requests = User.get_follow_requests(followed)
+
+ conn
+ |> put_view(AccountView)
+ |> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
end
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
@@ -1366,11 +1304,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do
end
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
- info_cng = User.Info.mastodon_settings_update(user.info, settings)
-
- with changeset <- Changeset.change(user),
- changeset <- Changeset.put_embed(changeset, :info, info_cng),
- {:ok, _user} <- User.update_and_set_cache(changeset) do
+ with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
json(conn, %{})
else
e ->
diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
new file mode 100644
index 000000000..7e4d7297c
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.NotificationController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+
+ alias Pleroma.Notification
+ alias Pleroma.Web.MastodonAPI.MastodonAPI
+
+ # GET /api/v1/notifications
+ def index(%{assigns: %{user: user}} = conn, params) do
+ notifications = MastodonAPI.get_notifications(user, params)
+
+ conn
+ |> add_link_headers(notifications)
+ |> render("index.json", notifications: notifications, for: user)
+ end
+
+ # GET /api/v1/notifications/:id
+ def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with {:ok, notification} <- Notification.get(user, id) do
+ render(conn, "show.json", notification: notification, for: user)
+ else
+ {:error, reason} ->
+ conn
+ |> put_status(:forbidden)
+ |> json(%{"error" => reason})
+ end
+ end
+
+ # POST /api/v1/notifications/clear
+ def clear(%{assigns: %{user: user}} = conn, _params) do
+ Notification.clear(user)
+ json(conn, %{})
+ end
+
+ # POST /api/v1/notifications/dismiss
+ def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
+ with {:ok, _notif} <- Notification.dismiss(user, id) do
+ json(conn, %{})
+ else
+ {:error, reason} ->
+ conn
+ |> put_status(:forbidden)
+ |> json(%{"error" => reason})
+ end
+ end
+
+ # DELETE /api/v1/notifications/destroy_multiple
+ def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do
+ Notification.destroy_multiple(user, ids)
+ json(conn, %{})
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 195dd124b..a23aeea9b 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -116,6 +116,8 @@ defp do_render("account.json", %{user: user} = opts) do
bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
relationship = render("relationship.json", %{user: opts[:for], target: user})
+ discoverable = user.info.discoverable
+
%{
id: to_string(user.id),
username: username_from_nickname(user.nickname),
@@ -139,7 +141,9 @@ defp do_render("account.json", %{user: user} = opts) do
note: HTML.strip_tags((user.bio || "") |> String.replace("
", "\n")),
sensitive: false,
fields: raw_fields,
- pleroma: %{}
+ pleroma: %{
+ discoverable: discoverable
+ }
},
# Pleroma extension
diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex
index d53e20d12..ed42a34f3 100644
--- a/lib/pleroma/web/oauth/authorization.ex
+++ b/lib/pleroma/web/oauth/authorization.ex
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
field(:scopes, {:array, :string}, default: [])
field(:valid_until, :naive_datetime_usec)
field(:used, :boolean, default: false)
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
belongs_to(:app, App)
timestamps()
diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex
index 40f131b57..8ea373805 100644
--- a/lib/pleroma/web/oauth/token.ex
+++ b/lib/pleroma/web/oauth/token.ex
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.OAuth.Token do
field(:refresh_token, :string)
field(:scopes, {:array, :string}, default: [])
field(:valid_until, :naive_datetime_usec)
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
belongs_to(:app, App)
timestamps()
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
index 370bee9c3..545ad80c9 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
@@ -3,12 +3,33 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
require Logger
- @emoji_dir_path Path.join(
- Pleroma.Config.get!([:instance, :static_dir]),
- "emoji"
- )
+ def emoji_dir_path do
+ Path.join(
+ Pleroma.Config.get!([:instance, :static_dir]),
+ "emoji"
+ )
+ end
- @cache_seconds_per_file Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
+ @doc """
+ Lists packs from the remote instance.
+
+ Since JS cannot ask remote instances for their packs due to CPS, it has to
+ be done by the server
+ """
+ def list_from(conn, %{"instance_address" => address}) do
+ address = String.trim(address)
+
+ if shareable_packs_available(address) do
+ list_resp =
+ "#{address}/api/pleroma/emoji/packs" |> Tesla.get!() |> Map.get(:body) |> Jason.decode!()
+
+ json(conn, list_resp)
+ else
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{error: "The requested instance does not support sharing emoji packs"})
+ end
+ end
@doc """
Lists the packs available on the instance as JSON.
@@ -17,7 +38,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
a map of "pack directory name" to pack.json contents.
"""
def list_packs(conn, _params) do
- with {:ok, results} <- File.ls(@emoji_dir_path) do
+ # Create the directory first if it does not exist. This is probably the first request made
+ # with the API so it should be sufficient
+ with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_dir_path())},
+ {:ls, {:ok, results}} <- {:ls, File.ls(emoji_dir_path())} do
pack_infos =
results
|> Enum.filter(&has_pack_json?/1)
@@ -28,24 +52,37 @@ def list_packs(conn, _params) do
|> Enum.into(%{})
json(conn, pack_infos)
+ else
+ {:create_dir, {:error, e}} ->
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{error: "Failed to create the emoji pack directory at #{emoji_dir_path()}: #{e}"})
+
+ {:ls, {:error, e}} ->
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{
+ error:
+ "Failed to get the contents of the emoji pack directory at #{emoji_dir_path()}: #{e}"
+ })
end
end
defp has_pack_json?(file) do
- dir_path = Path.join(@emoji_dir_path, file)
+ dir_path = Path.join(emoji_dir_path(), file)
# Filter to only use the pack.json packs
File.dir?(dir_path) and File.exists?(Path.join(dir_path, "pack.json"))
end
defp load_pack(pack_name) do
- pack_path = Path.join(@emoji_dir_path, pack_name)
+ pack_path = Path.join(emoji_dir_path(), pack_name)
pack_file = Path.join(pack_path, "pack.json")
{pack_name, Jason.decode!(File.read!(pack_file))}
end
defp validate_pack({name, pack}) do
- pack_path = Path.join(@emoji_dir_path, name)
+ pack_path = Path.join(emoji_dir_path(), name)
if can_download?(pack, pack_path) do
archive_for_sha = make_archive(name, pack, pack_path)
@@ -79,7 +116,8 @@ defp create_archive_and_cache(name, pack, pack_dir, md5) do
{:ok, {_, zip_result}} = :zip.zip('#{name}.zip', files, [:memory, cwd: to_charlist(pack_dir)])
- cache_ms = :timer.seconds(@cache_seconds_per_file * Enum.count(files))
+ cache_seconds_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
+ cache_ms = :timer.seconds(cache_seconds_per_file * Enum.count(files))
Cachex.put!(
:emoji_packs_cache,
@@ -115,7 +153,7 @@ defp make_archive(name, pack, pack_dir) do
to download packs that the instance shares.
"""
def download_shared(conn, %{"name" => name}) do
- pack_dir = Path.join(@emoji_dir_path, name)
+ pack_dir = Path.join(emoji_dir_path(), name)
pack_file = Path.join(pack_dir, "pack.json")
with {_, true} <- {:exists?, File.exists?(pack_file)},
@@ -139,6 +177,22 @@ def download_shared(conn, %{"name" => name}) do
end
end
+ defp shareable_packs_available(address) do
+ "#{address}/.well-known/nodeinfo"
+ |> Tesla.get!()
+ |> Map.get(:body)
+ |> Jason.decode!()
+ |> Map.get("links")
+ |> List.last()
+ |> Map.get("href")
+ # Get the actual nodeinfo address and fetch it
+ |> Tesla.get!()
+ |> Map.get(:body)
+ |> Jason.decode!()
+ |> get_in(["metadata", "features"])
+ |> Enum.member?("shareable_emoji_packs")
+ end
+
@doc """
An admin endpoint to request downloading a pack named `pack_name` from the instance
`instance_address`.
@@ -147,21 +201,9 @@ def download_shared(conn, %{"name" => name}) do
from that instance, otherwise it will be downloaded from the fallback source, if there is one.
"""
def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do
- shareable_packs_available =
- "#{address}/.well-known/nodeinfo"
- |> Tesla.get!()
- |> Map.get(:body)
- |> Jason.decode!()
- |> List.last()
- |> Map.get("href")
- # Get the actual nodeinfo address and fetch it
- |> Tesla.get!()
- |> Map.get(:body)
- |> Jason.decode!()
- |> get_in(["metadata", "features"])
- |> Enum.member?("shareable_emoji_packs")
+ address = String.trim(address)
- if shareable_packs_available do
+ if shareable_packs_available(address) do
full_pack =
"#{address}/api/pleroma/emoji/packs/list"
|> Tesla.get!()
@@ -195,7 +237,7 @@ def download_from(conn, %{"instance_address" => address, "pack_name" => name} =
%{body: emoji_archive} <- Tesla.get!(uri),
{_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, emoji_archive)} do
local_name = data["as"] || name
- pack_dir = Path.join(@emoji_dir_path, local_name)
+ pack_dir = Path.join(emoji_dir_path(), local_name)
File.mkdir_p!(pack_dir)
files = Enum.map(full_pack["files"], fn {_, path} -> to_charlist(path) end)
@@ -233,7 +275,7 @@ def download_from(conn, %{"instance_address" => address, "pack_name" => name} =
Creates an empty pack named `name` which then can be updated via the admin UI.
"""
def create(conn, %{"name" => name}) do
- pack_dir = Path.join(@emoji_dir_path, name)
+ pack_dir = Path.join(emoji_dir_path(), name)
if not File.exists?(pack_dir) do
File.mkdir_p!(pack_dir)
@@ -257,7 +299,7 @@ def create(conn, %{"name" => name}) do
Deletes the pack `name` and all it's files.
"""
def delete(conn, %{"name" => name}) do
- pack_dir = Path.join(@emoji_dir_path, name)
+ pack_dir = Path.join(emoji_dir_path(), name)
case File.rm_rf(pack_dir) do
{:ok, _} ->
@@ -276,7 +318,7 @@ def delete(conn, %{"name" => name}) do
`new_data` is the new metadata for the pack, that will replace the old metadata.
"""
def update_metadata(conn, %{"pack_name" => name, "new_data" => new_data}) do
- pack_file_p = Path.join([@emoji_dir_path, name, "pack.json"])
+ pack_file_p = Path.join([emoji_dir_path(), name, "pack.json"])
full_pack = Jason.decode!(File.read!(pack_file_p))
@@ -360,7 +402,7 @@ def update_file(
conn,
%{"pack_name" => pack_name, "action" => "add", "shortcode" => shortcode} = params
) do
- pack_dir = Path.join(@emoji_dir_path, pack_name)
+ pack_dir = Path.join(emoji_dir_path(), pack_name)
pack_file_p = Path.join(pack_dir, "pack.json")
full_pack = Jason.decode!(File.read!(pack_file_p))
@@ -408,7 +450,7 @@ def update_file(conn, %{
"action" => "remove",
"shortcode" => shortcode
}) do
- pack_dir = Path.join(@emoji_dir_path, pack_name)
+ pack_dir = Path.join(emoji_dir_path(), pack_name)
pack_file_p = Path.join(pack_dir, "pack.json")
full_pack = Jason.decode!(File.read!(pack_file_p))
@@ -443,7 +485,7 @@ def update_file(
conn,
%{"pack_name" => pack_name, "action" => "update", "shortcode" => shortcode} = params
) do
- pack_dir = Path.join(@emoji_dir_path, pack_name)
+ pack_dir = Path.join(emoji_dir_path(), pack_name)
pack_file_p = Path.join(pack_dir, "pack.json")
full_pack = Jason.decode!(File.read!(pack_file_p))
@@ -513,11 +555,11 @@ def update_file(conn, %{"action" => action}) do
assumed to be emojis and stored in the new `pack.json` file.
"""
def import_from_fs(conn, _params) do
- with {:ok, results} <- File.ls(@emoji_dir_path) do
+ with {:ok, results} <- File.ls(emoji_dir_path()) do
imported_pack_names =
results
|> Enum.filter(fn file ->
- dir_path = Path.join(@emoji_dir_path, file)
+ dir_path = Path.join(emoji_dir_path(), file)
# Find the directories that do NOT have pack.json
File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json"))
end)
@@ -533,7 +575,7 @@ def import_from_fs(conn, _params) do
end
defp write_pack_json_contents(dir) do
- dir_path = Path.join(@emoji_dir_path, dir)
+ dir_path = Path.join(emoji_dir_path(), dir)
emoji_txt_path = Path.join(dir_path, "emoji.txt")
files_for_pack = files_for_pack(emoji_txt_path, dir_path)
diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex
index da301fbbc..988fabaeb 100644
--- a/lib/pleroma/web/push/subscription.ex
+++ b/lib/pleroma/web/push/subscription.ex
@@ -15,7 +15,7 @@ defmodule Pleroma.Web.Push.Subscription do
@type t :: %__MODULE__{}
schema "push_subscriptions" do
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
belongs_to(:token, Token)
field(:endpoint, :string)
field(:key_p256dh, :string)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index e583093d2..316c895ee 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -222,6 +222,7 @@ defmodule Pleroma.Web.Router do
put("/:name", EmojiAPIController, :create)
delete("/:name", EmojiAPIController, :delete)
post("/download_from", EmojiAPIController, :download_from)
+ post("/list_from", EmojiAPIController, :list_from)
end
scope "/packs" do
@@ -324,11 +325,11 @@ defmodule Pleroma.Web.Router do
get("/favourites", MastodonAPIController, :favourites)
get("/bookmarks", MastodonAPIController, :bookmarks)
- post("/notifications/clear", MastodonAPIController, :clear_notifications)
- post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
- get("/notifications", MastodonAPIController, :notifications)
- get("/notifications/:id", MastodonAPIController, :get_notification)
- delete("/notifications/destroy_multiple", MastodonAPIController, :destroy_multiple)
+ get("/notifications", NotificationController, :index)
+ get("/notifications/:id", NotificationController, :show)
+ post("/notifications/clear", NotificationController, :clear)
+ post("/notifications/dismiss", NotificationController, :dismiss)
+ delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 42234ae09..5024ac70d 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
- alias Ecto.Changeset
alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.OAuth.Token
@@ -16,15 +15,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
action_fallback(:errors)
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
- with %User{} = user <- User.get_cached_by_id(uid),
- true <- user.local,
- true <- user.info.confirmation_pending,
- true <- user.info.confirmation_token == token,
- info_change <- User.Info.confirmation_changeset(user.info, need_confirmation: false),
- changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
- {:ok, _} <- User.update_and_set_cache(changeset) do
- conn
- |> redirect(to: "/")
+ new_info = [need_confirmation: false]
+
+ with %User{info: info} = user <- User.get_cached_by_id(uid),
+ true <- user.local and info.confirmation_pending and info.confirmation_token == token,
+ {:ok, _} <- User.update_info(user, &User.Info.confirmation_changeset(&1, new_info)) do
+ redirect(conn, to: "/")
end
end
diff --git a/lib/pleroma/web/websub/websub_client_subscription.ex b/lib/pleroma/web/websub/websub_client_subscription.ex
index 77703c496..23a04b87d 100644
--- a/lib/pleroma/web/websub/websub_client_subscription.ex
+++ b/lib/pleroma/web/websub/websub_client_subscription.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.Websub.WebsubClientSubscription do
field(:state, :string)
field(:subscribers, {:array, :string}, default: [])
field(:hub, :string)
- belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
timestamps()
end
diff --git a/mix.exs b/mix.exs
index f2635da24..861b94ad0 100644
--- a/mix.exs
+++ b/mix.exs
@@ -158,6 +158,7 @@ defp deps do
{:ex_const, "~> 0.2"},
{:plug_static_index_html, "~> 1.0.0"},
{:excoveralls, "~> 0.11.1", only: :test},
+ {:flake_id, "~> 0.1.0"},
{:mox, "~> 0.5", only: :test}
] ++ oauth_deps()
end
diff --git a/mix.lock b/mix.lock
index 24b34c09c..32443fb51 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,6 +1,7 @@
%{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]},
+ "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
@@ -17,6 +18,7 @@
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"crontab": {:hex, :crontab, "1.1.7", "b9219f0bdc8678b94143655a8f229716c5810c0636a4489f98c0956137e53985", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
+ "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm"},
"db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
@@ -34,12 +36,13 @@
"ex_rated": {:hex, :ex_rated, "1.3.3", "30ecbdabe91f7eaa9d37fa4e81c85ba420f371babeb9d1910adbcd79ec798d27", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"},
"ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]},
"excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
+ "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"floki": {:hex, :floki, "0.23.0", "956ab6dba828c96e732454809fb0bd8d43ce0979b75f34de6322e73d4c917829", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm"},
"gen_smtp": {:hex, :gen_smtp, "0.14.0", "39846a03522456077c6429b4badfd1d55e5e7d0fdfb65e935b7c5e38549d9202", [:rebar3], [], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.14.2", "6a2a578a510c5bfca8a45e6b27552f613b41cf584b58210f017088d3d17d0b14", [:mix], [], "hexpm"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm"},
- "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [: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.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
+ "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"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"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"]},
@@ -84,7 +87,7 @@
"quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
- "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
+ "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
"swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm"},
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
"swoosh": {:hex, :swoosh, "0.23.2", "7dda95ff0bf54a2298328d6899c74dae1223777b43563ccebebb4b5d2b61df38", [: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"},
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index 57ed05eba..6e4bb29b1 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -11,6 +11,7 @@
"@id": "ostatus:conversation",
"@type": "@id"
},
+ "discoverable": "toot:discoverable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"ostatus": "http://ostatus.org#",
"schema": "http://schema.org",
diff --git a/test/flake_id_test.exs b/test/flake_id_test.exs
deleted file mode 100644
index 85ed5bbdf..000000000
--- a/test/flake_id_test.exs
+++ /dev/null
@@ -1,47 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.FlakeIdTest do
- use Pleroma.DataCase
- import Kernel, except: [to_string: 1]
- import Pleroma.FlakeId
-
- describe "fake flakes (compatibility with older serial integers)" do
- test "from_string/1" do
- fake_flake = <<0::integer-size(64), 42::integer-size(64)>>
- assert from_string("42") == fake_flake
- assert from_string(42) == fake_flake
- end
-
- test "zero or -1 is a null flake" do
- fake_flake = <<0::integer-size(128)>>
- assert from_string("0") == fake_flake
- assert from_string("-1") == fake_flake
- end
-
- test "to_string/1" do
- fake_flake = <<0::integer-size(64), 42::integer-size(64)>>
- assert to_string(fake_flake) == "42"
- end
- end
-
- test "ecto type behaviour" do
- flake = <<0, 0, 1, 104, 80, 229, 2, 235, 140, 22, 69, 201, 53, 210, 0, 0>>
- flake_s = "9eoozpwTul5mjSEDRI"
-
- assert cast(flake) == {:ok, flake_s}
- assert cast(flake_s) == {:ok, flake_s}
-
- assert load(flake) == {:ok, flake_s}
- assert load(flake_s) == {:ok, flake_s}
-
- assert dump(flake_s) == {:ok, flake}
- assert dump(flake) == {:ok, flake}
- end
-
- test "is_flake_id?/1" do
- assert is_flake_id?("9eoozpwTul5mjSEDRI")
- refute is_flake_id?("http://example.com/activities/3ebbadd1-eb14-4e20-8118-b6f79c0c7b0b")
- end
-end
diff --git a/test/moderation_log_test.exs b/test/moderation_log_test.exs
index c78708471..a39a00e02 100644
--- a/test/moderation_log_test.exs
+++ b/test/moderation_log_test.exs
@@ -30,8 +30,7 @@ test "logging user deletion by moderator", %{moderator: moderator, subject1: sub
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
- "@#{moderator.nickname} deleted user @#{subject1.nickname}"
+ assert log.data["message"] == "@#{moderator.nickname} deleted user @#{subject1.nickname}"
end
test "logging user creation by moderator", %{
@@ -48,7 +47,7 @@ test "logging user creation by moderator", %{
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{moderator.nickname} created users: @#{subject1.nickname}, @#{subject2.nickname}"
end
@@ -63,7 +62,7 @@ test "logging user follow by admin", %{admin: admin, subject1: subject1, subject
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{admin.nickname} made @#{subject2.nickname} follow @#{subject1.nickname}"
end
@@ -78,7 +77,7 @@ test "logging user unfollow by admin", %{admin: admin, subject1: subject1, subje
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{admin.nickname} made @#{subject2.nickname} unfollow @#{subject1.nickname}"
end
@@ -100,8 +99,7 @@ test "logging user tagged by admin", %{admin: admin, subject1: subject1, subject
tags = ["foo", "bar"] |> Enum.join(", ")
- assert ModerationLog.get_log_entry_message(log) ==
- "@#{admin.nickname} added tags: #{tags} to users: #{users}"
+ assert log.data["message"] == "@#{admin.nickname} added tags: #{tags} to users: #{users}"
end
test "logging user untagged by admin", %{admin: admin, subject1: subject1, subject2: subject2} do
@@ -122,7 +120,7 @@ test "logging user untagged by admin", %{admin: admin, subject1: subject1, subje
tags = ["foo", "bar"] |> Enum.join(", ")
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{admin.nickname} removed tags: #{tags} from users: #{users}"
end
@@ -137,8 +135,7 @@ test "logging user grant by moderator", %{moderator: moderator, subject1: subjec
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
- "@#{moderator.nickname} made @#{subject1.nickname} moderator"
+ assert log.data["message"] == "@#{moderator.nickname} made @#{subject1.nickname} moderator"
end
test "logging user revoke by moderator", %{moderator: moderator, subject1: subject1} do
@@ -152,7 +149,7 @@ test "logging user revoke by moderator", %{moderator: moderator, subject1: subje
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{moderator.nickname} revoked moderator role from @#{subject1.nickname}"
end
@@ -166,7 +163,7 @@ test "logging relay follow", %{moderator: moderator} do
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{moderator.nickname} followed relay: https://example.org/relay"
end
@@ -180,7 +177,7 @@ test "logging relay unfollow", %{moderator: moderator} do
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{moderator.nickname} unfollowed relay: https://example.org/relay"
end
@@ -202,7 +199,7 @@ test "logging report update", %{moderator: moderator} do
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{moderator.nickname} updated report ##{report.id} with 'resolved' state"
end
@@ -224,7 +221,7 @@ test "logging report response", %{moderator: moderator} do
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{moderator.nickname} responded with 'look at this' to report ##{report.id}"
end
@@ -242,7 +239,7 @@ test "logging status sensitivity update", %{moderator: moderator} do
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{moderator.nickname} updated status ##{note.id}, set sensitive: 'true'"
end
@@ -260,7 +257,7 @@ test "logging status visibility update", %{moderator: moderator} do
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{moderator.nickname} updated status ##{note.id}, set visibility: 'private'"
end
@@ -278,7 +275,7 @@ test "logging status sensitivity & visibility update", %{moderator: moderator} d
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
+ assert log.data["message"] ==
"@#{moderator.nickname} updated status ##{note.id}, set sensitive: 'true', visibility: 'private'"
end
@@ -294,8 +291,7 @@ test "logging status deletion", %{moderator: moderator} do
log = Repo.one(ModerationLog)
- assert ModerationLog.get_log_entry_message(log) ==
- "@#{moderator.nickname} deleted status ##{note.id}"
+ assert log.data["message"] == "@#{moderator.nickname} deleted status ##{note.id}"
end
end
end
diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs
index a9925c361..b63dcac00 100644
--- a/test/tasks/database_test.exs
+++ b/test/tasks/database_test.exs
@@ -77,12 +77,10 @@ test "following and followers count are updated" do
assert length(following) == 2
assert info.follower_count == 0
- info_cng = Ecto.Changeset.change(info, %{follower_count: 3})
-
{:ok, user} =
user
|> Ecto.Changeset.change(%{following: following ++ following})
- |> Ecto.Changeset.put_embed(:info, info_cng)
+ |> User.change_info(&Ecto.Changeset.change(&1, %{follower_count: 3}))
|> Repo.update()
assert length(user.following) == 4
diff --git a/test/tasks/instance_test.exs b/test/tasks/instance_test.exs
index 70986374e..6d7eed4c1 100644
--- a/test/tasks/instance_test.exs
+++ b/test/tasks/instance_test.exs
@@ -7,7 +7,16 @@ defmodule Pleroma.InstanceTest do
setup do
File.mkdir_p!(tmp_path())
- on_exit(fn -> File.rm_rf(tmp_path()) end)
+
+ on_exit(fn ->
+ File.rm_rf(tmp_path())
+ static_dir = Pleroma.Config.get([:instance, :static_dir], "test/instance_static/")
+
+ if File.exists?(static_dir) do
+ File.rm_rf(Path.join(static_dir, "robots.txt"))
+ end
+ end)
+
:ok
end
diff --git a/test/user_test.exs b/test/user_test.exs
index aebe7aa06..126bd69e8 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -74,8 +74,8 @@ test "returns all pending follow requests" do
CommonAPI.follow(follower, unlocked)
CommonAPI.follow(follower, locked)
- assert {:ok, []} = User.get_follow_requests(unlocked)
- assert {:ok, [activity]} = User.get_follow_requests(locked)
+ assert [] = User.get_follow_requests(unlocked)
+ assert [activity] = User.get_follow_requests(locked)
assert activity
end
@@ -90,7 +90,7 @@ test "doesn't return already accepted or duplicate follow requests" do
CommonAPI.follow(accepted_follower, locked)
User.follow(accepted_follower, locked)
- assert {:ok, [activity]} = User.get_follow_requests(locked)
+ assert [activity] = User.get_follow_requests(locked)
assert activity
end
@@ -99,10 +99,10 @@ test "clears follow requests when requester is blocked" do
follower = insert(:user)
CommonAPI.follow(follower, followed)
- assert {:ok, [_activity]} = User.get_follow_requests(followed)
+ assert [_activity] = User.get_follow_requests(followed)
{:ok, _follower} = User.block(followed, follower)
- assert {:ok, []} = User.get_follow_requests(followed)
+ assert [] = User.get_follow_requests(followed)
end
test "follow_all follows mutliple users" do
@@ -560,7 +560,7 @@ test "it sets the follower_adress" do
test "it enforces the fqn format for nicknames" do
cs = User.remote_user_creation(%{@valid_remote | nickname: "bla"})
- assert cs.changes.local == false
+ assert Ecto.Changeset.get_field(cs, :local) == false
assert cs.changes.avatar
refute cs.valid?
end
@@ -584,7 +584,7 @@ test "gets all followers for a given user" do
{:ok, follower_one} = User.follow(follower_one, user)
{:ok, follower_two} = User.follow(follower_two, user)
- {:ok, res} = User.get_followers(user)
+ res = User.get_followers(user)
assert Enum.member?(res, follower_one)
assert Enum.member?(res, follower_two)
@@ -600,7 +600,7 @@ test "gets all friends (followed users) for a given user" do
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
- {:ok, res} = User.get_friends(user)
+ res = User.get_friends(user)
followed_one = User.get_cached_by_ap_id(followed_one.ap_id)
followed_two = User.get_cached_by_ap_id(followed_two.ap_id)
@@ -975,7 +975,7 @@ test "hide a user from followers " do
info = User.get_cached_user_info(user2)
assert info.follower_count == 0
- assert {:ok, []} = User.get_followers(user2)
+ assert [] = User.get_followers(user2)
end
test "hide a user from friends" do
@@ -991,7 +991,7 @@ test "hide a user from friends" do
assert info.following_count == 0
assert User.following_count(user2) == 0
- assert {:ok, []} = User.get_friends(user2)
+ assert [] = User.get_friends(user2)
end
test "hide a user's statuses from timelines and notifications" do
@@ -1034,7 +1034,7 @@ test "hide a user's statuses from timelines and notifications" do
test ".delete_user_activities deletes all create activities", %{user: user} do
{:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
- {:ok, _} = User.delete_user_activities(user)
+ User.delete_user_activities(user)
# TODO: Remove favorites, repeats, delete activities.
refute Activity.get_by_id(activity.id)
@@ -1707,4 +1707,22 @@ test "sets password_reset_pending to true", %{user: user} do
assert password_reset_pending
end
end
+
+ test "change_info/2" do
+ user = insert(:user)
+ assert user.info.hide_follows == false
+
+ changeset = User.change_info(user, &User.Info.profile_update(&1, %{hide_follows: true}))
+ assert changeset.changes.info.changes.hide_follows == true
+ end
+
+ test "update_info/2" do
+ user = insert(:user)
+ assert user.info.hide_follows == false
+
+ assert {:ok, _} = User.update_info(user, &User.Info.profile_update(&1, %{hide_follows: true}))
+
+ assert %{info: %{hide_follows: true}} = Repo.get(User, user.id)
+ assert {:ok, %{info: %{hide_follows: true}}} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
+ end
end
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 9e8e420ec..ab52044ae 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -479,7 +479,7 @@ test "it returns a note activity in a collection", %{conn: conn} do
conn
|> assign(:user, user)
|> put_req_header("accept", "application/activity+json")
- |> get("/users/#{user.nickname}/inbox")
+ |> get("/users/#{user.nickname}/inbox?page=true")
assert response(conn, 200) =~ note_object.data["content"]
end
@@ -567,7 +567,7 @@ test "it returns a note activity in a collection", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "application/activity+json")
- |> get("/users/#{user.nickname}/outbox")
+ |> get("/users/#{user.nickname}/outbox?page=true")
assert response(conn, 200) =~ note_object.data["content"]
end
@@ -579,7 +579,7 @@ test "it returns an announce activity in a collection", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "application/activity+json")
- |> get("/users/#{user.nickname}/outbox")
+ |> get("/users/#{user.nickname}/outbox?page=true")
assert response(conn, 200) =~ announce_activity.data["object"]
end
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 4100108a5..f28fd6871 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -647,6 +647,21 @@ test "retrieves ids up to max_id" do
assert last == last_expected
end
+ test "paginates via offset/limit" do
+ _first_activities = ActivityBuilder.insert_list(10)
+ activities = ActivityBuilder.insert_list(10)
+ _later_activities = ActivityBuilder.insert_list(10)
+ first_expected = List.first(activities)
+
+ activities =
+ ActivityPub.fetch_public_activities(%{"page" => "2", "page_size" => "20"}, :offset)
+
+ first = List.first(activities)
+
+ assert length(activities) == 20
+ assert first == first_expected
+ end
+
test "doesn't return reblogs for users for whom reblogs have been muted" do
activity = insert(:note_activity)
user = insert(:user)
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index 78b0408ee..3155749aa 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -159,7 +159,7 @@ test "sets correct totalItems when follows are hidden but the follow counter is
end
end
- test "outbox paginates correctly" do
+ test "activity collection page aginates correctly" do
user = insert(:user)
posts =
@@ -171,13 +171,21 @@ test "outbox paginates correctly" do
# outbox sorts chronologically, newest first, with ten per page
posts = Enum.reverse(posts)
- %{"first" => %{"next" => next_url}} =
- UserView.render("outbox.json", %{user: user, max_id: nil})
+ %{"next" => next_url} =
+ UserView.render("activity_collection_page.json", %{
+ iri: "#{user.ap_id}/outbox",
+ activities: Enum.take(posts, 10)
+ })
next_id = Enum.at(posts, 9).id
assert next_url =~ next_id
- %{"next" => next_url} = UserView.render("outbox.json", %{user: user, max_id: next_id})
+ %{"next" => next_url} =
+ UserView.render("activity_collection_page.json", %{
+ iri: "#{user.ap_id}/outbox",
+ activities: Enum.take(Enum.drop(posts, 10), 10)
+ })
+
next_id = Enum.at(posts, 19).id
assert next_url =~ next_id
end
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index f00e02a7a..b5c355e66 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -586,7 +586,9 @@ test "/api/pleroma/admin/users/:nickname/password_reset" do
|> put_req_header("accept", "application/json")
|> get("/api/pleroma/admin/users/#{user.nickname}/password_reset")
- assert conn.status == 200
+ resp = json_response(conn, 200)
+
+ assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
end
describe "GET /api/pleroma/admin/users" do
@@ -2255,8 +2257,9 @@ test "returns private statuses with godmode on", %{conn: conn, user: user} do
describe "GET /api/pleroma/admin/moderation_log" do
setup %{conn: conn} do
admin = insert(:user, info: %{is_admin: true})
+ moderator = insert(:user, info: %{is_moderator: true})
- %{conn: assign(conn, :user, admin), admin: admin}
+ %{conn: assign(conn, :user, admin), admin: admin, moderator: moderator}
end
test "returns the log", %{conn: conn, admin: admin} do
@@ -2289,9 +2292,9 @@ test "returns the log", %{conn: conn, admin: admin} do
conn = get(conn, "/api/pleroma/admin/moderation_log")
response = json_response(conn, 200)
- [first_entry, second_entry] = response
+ [first_entry, second_entry] = response["items"]
- assert response |> length() == 2
+ assert response["total"] == 2
assert first_entry["data"]["action"] == "relay_unfollow"
assert first_entry["message"] ==
@@ -2333,9 +2336,10 @@ test "returns the log with pagination", %{conn: conn, admin: admin} do
conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1")
response1 = json_response(conn1, 200)
- [first_entry] = response1
+ [first_entry] = response1["items"]
- assert response1 |> length() == 1
+ assert response1["total"] == 2
+ assert response1["items"] |> length() == 1
assert first_entry["data"]["action"] == "relay_unfollow"
assert first_entry["message"] ==
@@ -2344,14 +2348,119 @@ test "returns the log with pagination", %{conn: conn, admin: admin} do
conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2")
response2 = json_response(conn2, 200)
- [second_entry] = response2
+ [second_entry] = response2["items"]
- assert response2 |> length() == 1
+ assert response2["total"] == 2
+ assert response2["items"] |> length() == 1
assert second_entry["data"]["action"] == "relay_follow"
assert second_entry["message"] ==
"@#{admin.nickname} followed relay: https://example.org/relay"
end
+
+ test "filters log by date", %{conn: conn, admin: admin} do
+ first_date = "2017-08-15T15:47:06Z"
+ second_date = "2017-08-20T15:47:06Z"
+
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_follow",
+ target: "https://example.org/relay"
+ },
+ inserted_at: NaiveDateTime.from_iso8601!(first_date)
+ })
+
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_unfollow",
+ target: "https://example.org/relay"
+ },
+ inserted_at: NaiveDateTime.from_iso8601!(second_date)
+ })
+
+ conn1 =
+ get(
+ conn,
+ "/api/pleroma/admin/moderation_log?start_date=#{second_date}"
+ )
+
+ response1 = json_response(conn1, 200)
+ [first_entry] = response1["items"]
+
+ assert response1["total"] == 1
+ assert first_entry["data"]["action"] == "relay_unfollow"
+
+ assert first_entry["message"] ==
+ "@#{admin.nickname} unfollowed relay: https://example.org/relay"
+ end
+
+ test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_follow",
+ target: "https://example.org/relay"
+ }
+ })
+
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => moderator.id,
+ "nickname" => moderator.nickname,
+ "type" => "user"
+ },
+ action: "relay_unfollow",
+ target: "https://example.org/relay"
+ }
+ })
+
+ conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}")
+
+ response1 = json_response(conn1, 200)
+ [first_entry] = response1["items"]
+
+ assert response1["total"] == 1
+ assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id
+ end
+
+ test "returns log filtered by search", %{conn: conn, moderator: moderator} do
+ ModerationLog.insert_log(%{
+ actor: moderator,
+ action: "relay_follow",
+ target: "https://example.org/relay"
+ })
+
+ ModerationLog.insert_log(%{
+ actor: moderator,
+ action: "relay_unfollow",
+ target: "https://example.org/relay"
+ })
+
+ conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo")
+
+ response1 = json_response(conn1, 200)
+ [first_entry] = response1["items"]
+
+ assert response1["total"] == 1
+
+ assert get_in(first_entry, ["data", "message"]) ==
+ "@#{moderator.nickname} unfollowed relay: https://example.org/relay"
+ end
end
describe "PATCH /users/:nickname/force_password_reset" do
diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs
new file mode 100644
index 000000000..e4137e92c
--- /dev/null
+++ b/test/web/mastodon_api/controllers/notification_controller_test.exs
@@ -0,0 +1,299 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.Notification
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ test "list of notifications", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+ {:ok, [_notification]} = Notification.create_notifications(activity)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/notifications")
+
+ expected_response =
+ "hi @#{user.nickname}"
+
+ assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
+ assert response == expected_response
+ end
+
+ test "getting a single notification", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+ {:ok, [notification]} = Notification.create_notifications(activity)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/notifications/#{notification.id}")
+
+ expected_response =
+ "hi @#{user.nickname}"
+
+ assert %{"status" => %{"content" => response}} = json_response(conn, 200)
+ assert response == expected_response
+ end
+
+ test "dismissing a single notification", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+ {:ok, [notification]} = Notification.create_notifications(activity)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
+
+ assert %{} = json_response(conn, 200)
+ end
+
+ test "clearing all notifications", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+ {:ok, [_notification]} = Notification.create_notifications(activity)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/notifications/clear")
+
+ assert %{} = json_response(conn, 200)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> get("/api/v1/notifications")
+
+ assert all = json_response(conn, 200)
+ assert all == []
+ end
+
+ test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+
+ notification1_id = get_notification_id_by_activity(activity1)
+ notification2_id = get_notification_id_by_activity(activity2)
+ notification3_id = get_notification_id_by_activity(activity3)
+ notification4_id = get_notification_id_by_activity(activity4)
+
+ conn = assign(conn, :user, user)
+
+ # min_id
+ result =
+ conn
+ |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
+ |> json_response(:ok)
+
+ assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
+
+ # since_id
+ result =
+ conn
+ |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
+ |> json_response(:ok)
+
+ assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
+
+ # max_id
+ result =
+ conn
+ |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
+ |> json_response(:ok)
+
+ assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
+ end
+
+ test "filters notifications using exclude_types", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
+ {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
+ {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
+ {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
+
+ mention_notification_id = get_notification_id_by_activity(mention_activity)
+ favorite_notification_id = get_notification_id_by_activity(favorite_activity)
+ reblog_notification_id = get_notification_id_by_activity(reblog_activity)
+ follow_notification_id = get_notification_id_by_activity(follow_activity)
+
+ conn = assign(conn, :user, user)
+
+ conn_res =
+ get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
+
+ assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
+
+ conn_res =
+ get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
+
+ assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
+
+ conn_res =
+ get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
+
+ assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
+
+ conn_res =
+ get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
+
+ assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
+ end
+
+ test "destroy multiple", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
+ {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
+ {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
+
+ notification1_id = get_notification_id_by_activity(activity1)
+ notification2_id = get_notification_id_by_activity(activity2)
+ notification3_id = get_notification_id_by_activity(activity3)
+ notification4_id = get_notification_id_by_activity(activity4)
+
+ conn = assign(conn, :user, user)
+
+ result =
+ conn
+ |> get("/api/v1/notifications")
+ |> json_response(:ok)
+
+ assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
+
+ conn2 =
+ conn
+ |> assign(:user, other_user)
+
+ result =
+ conn2
+ |> get("/api/v1/notifications")
+ |> json_response(:ok)
+
+ assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
+
+ conn_destroy =
+ conn
+ |> delete("/api/v1/notifications/destroy_multiple", %{
+ "ids" => [notification1_id, notification2_id]
+ })
+
+ assert json_response(conn_destroy, 200) == %{}
+
+ result =
+ conn2
+ |> get("/api/v1/notifications")
+ |> json_response(:ok)
+
+ assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
+ end
+
+ test "doesn't see notifications after muting user with notifications", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(user, user2)
+ {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+ conn = assign(conn, :user, user)
+
+ conn = get(conn, "/api/v1/notifications")
+
+ assert length(json_response(conn, 200)) == 1
+
+ {:ok, user} = User.mute(user, user2)
+
+ conn = assign(build_conn(), :user, user)
+ conn = get(conn, "/api/v1/notifications")
+
+ assert json_response(conn, 200) == []
+ end
+
+ test "see notifications after muting user without notifications", %{conn: conn} do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(user, user2)
+ {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+ conn = assign(conn, :user, user)
+
+ conn = get(conn, "/api/v1/notifications")
+
+ assert length(json_response(conn, 200)) == 1
+
+ {:ok, user} = User.mute(user, user2, false)
+
+ conn = assign(build_conn(), :user, user)
+ conn = get(conn, "/api/v1/notifications")
+
+ assert length(json_response(conn, 200)) == 1
+ end
+
+ test "see notifications after muting user with notifications and with_muted parameter", %{
+ conn: conn
+ } do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(user, user2)
+ {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+ conn = assign(conn, :user, user)
+
+ conn = get(conn, "/api/v1/notifications")
+
+ assert length(json_response(conn, 200)) == 1
+
+ {:ok, user} = User.mute(user, user2)
+
+ conn = assign(build_conn(), :user, user)
+ conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
+
+ assert length(json_response(conn, 200)) == 1
+ end
+
+ defp get_notification_id_by_activity(%{id: id}) do
+ Notification
+ |> Repo.get_by(activity_id: id)
+ |> Map.get(:id)
+ |> to_string()
+ end
+end
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index 73a3bf135..cd672132b 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -999,299 +999,6 @@ test "list timeline does not leak non-public statuses for unfollowed users", %{c
end
end
- describe "notifications" do
- test "list of notifications", %{conn: conn} do
- user = insert(:user)
- other_user = insert(:user)
-
- {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
-
- {:ok, [_notification]} = Notification.create_notifications(activity)
-
- conn =
- conn
- |> assign(:user, user)
- |> get("/api/v1/notifications")
-
- expected_response =
- ~s(hi @#{user.nickname})
-
- assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
- assert response == expected_response
- end
-
- test "getting a single notification", %{conn: conn} do
- user = insert(:user)
- other_user = insert(:user)
-
- {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
-
- {:ok, [notification]} = Notification.create_notifications(activity)
-
- conn =
- conn
- |> assign(:user, user)
- |> get("/api/v1/notifications/#{notification.id}")
-
- expected_response =
- ~s(hi @#{user.nickname})
-
- assert %{"status" => %{"content" => response}} = json_response(conn, 200)
- assert response == expected_response
- end
-
- test "dismissing a single notification", %{conn: conn} do
- user = insert(:user)
- other_user = insert(:user)
-
- {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
-
- {:ok, [notification]} = Notification.create_notifications(activity)
-
- conn =
- conn
- |> assign(:user, user)
- |> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
-
- assert %{} = json_response(conn, 200)
- end
-
- test "clearing all notifications", %{conn: conn} do
- user = insert(:user)
- other_user = insert(:user)
-
- {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
-
- {:ok, [_notification]} = Notification.create_notifications(activity)
-
- conn =
- conn
- |> assign(:user, user)
- |> post("/api/v1/notifications/clear")
-
- assert %{} = json_response(conn, 200)
-
- conn =
- build_conn()
- |> assign(:user, user)
- |> get("/api/v1/notifications")
-
- assert all = json_response(conn, 200)
- assert all == []
- end
-
- test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
- user = insert(:user)
- other_user = insert(:user)
-
- {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
- {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
- {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
- {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
-
- notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
- notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
- notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
- notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
-
- conn =
- conn
- |> assign(:user, user)
-
- # min_id
- conn_res =
- conn
- |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
-
- result = json_response(conn_res, 200)
- assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
-
- # since_id
- conn_res =
- conn
- |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
-
- result = json_response(conn_res, 200)
- assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
-
- # max_id
- conn_res =
- conn
- |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
-
- result = json_response(conn_res, 200)
- assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
- end
-
- test "filters notifications using exclude_types", %{conn: conn} do
- user = insert(:user)
- other_user = insert(:user)
-
- {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
- {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
- {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
- {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
- {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
-
- mention_notification_id =
- Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
-
- favorite_notification_id =
- Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
-
- reblog_notification_id =
- Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
-
- follow_notification_id =
- Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
-
- conn =
- conn
- |> assign(:user, user)
-
- conn_res =
- get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
-
- assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
-
- conn_res =
- get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
-
- assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
-
- conn_res =
- get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
-
- assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
-
- conn_res =
- get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
-
- assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
- end
-
- test "destroy multiple", %{conn: conn} do
- user = insert(:user)
- other_user = insert(:user)
-
- {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
- {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
- {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
- {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
-
- notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
- notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
- notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
- notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
-
- conn =
- conn
- |> assign(:user, user)
-
- conn_res =
- conn
- |> get("/api/v1/notifications")
-
- result = json_response(conn_res, 200)
- assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
-
- conn2 =
- conn
- |> assign(:user, other_user)
-
- conn_res =
- conn2
- |> get("/api/v1/notifications")
-
- result = json_response(conn_res, 200)
- assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
-
- conn_destroy =
- conn
- |> delete("/api/v1/notifications/destroy_multiple", %{
- "ids" => [notification1_id, notification2_id]
- })
-
- assert json_response(conn_destroy, 200) == %{}
-
- conn_res =
- conn2
- |> get("/api/v1/notifications")
-
- result = json_response(conn_res, 200)
- assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
- end
-
- test "doesn't see notifications after muting user with notifications", %{conn: conn} do
- user = insert(:user)
- user2 = insert(:user)
-
- {:ok, _, _, _} = CommonAPI.follow(user, user2)
- {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
-
- conn = assign(conn, :user, user)
-
- conn = get(conn, "/api/v1/notifications")
-
- assert length(json_response(conn, 200)) == 1
-
- {:ok, user} = User.mute(user, user2)
-
- conn = assign(build_conn(), :user, user)
- conn = get(conn, "/api/v1/notifications")
-
- assert json_response(conn, 200) == []
- end
-
- test "see notifications after muting user without notifications", %{conn: conn} do
- user = insert(:user)
- user2 = insert(:user)
-
- {:ok, _, _, _} = CommonAPI.follow(user, user2)
- {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
-
- conn = assign(conn, :user, user)
-
- conn = get(conn, "/api/v1/notifications")
-
- assert length(json_response(conn, 200)) == 1
-
- {:ok, user} = User.mute(user, user2, false)
-
- conn = assign(build_conn(), :user, user)
- conn = get(conn, "/api/v1/notifications")
-
- assert length(json_response(conn, 200)) == 1
- end
-
- test "see notifications after muting user with notifications and with_muted parameter", %{
- conn: conn
- } do
- user = insert(:user)
- user2 = insert(:user)
-
- {:ok, _, _, _} = CommonAPI.follow(user, user2)
- {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
-
- conn = assign(conn, :user, user)
-
- conn = get(conn, "/api/v1/notifications")
-
- assert length(json_response(conn, 200)) == 1
-
- {:ok, user} = User.mute(user, user2)
-
- conn = assign(build_conn(), :user, user)
- conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
-
- assert length(json_response(conn, 200)) == 1
- end
- end
-
describe "reblogging" do
test "reblogs and returns the reblogged status", %{conn: conn} do
activity = insert(:note_activity)
@@ -2613,14 +2320,11 @@ test "get instance stats", %{conn: conn} do
{:ok, _} = CommonAPI.post(user, %{"status" => "cofe"})
# Stats should count users with missing or nil `info.deactivated` value
- user = User.get_cached_by_id(user.id)
- info_change = Changeset.change(user.info, %{deactivated: nil})
{:ok, _user} =
- user
- |> Changeset.change()
- |> Changeset.put_embed(:info, info_change)
- |> User.update_and_set_cache()
+ user.id
+ |> User.get_cached_by_id()
+ |> User.update_info(&Changeset.change(&1, %{deactivated: nil}))
Pleroma.Stats.force_update()
@@ -3953,13 +3657,9 @@ test "it returns 400 when user is not local", %{conn: conn, user: user} do
describe "POST /api/v1/pleroma/accounts/confirmation_resend" do
setup do
- user = insert(:user)
- info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
-
{:ok, user} =
- user
- |> Changeset.change()
- |> Changeset.put_embed(:info, info_change)
+ insert(:user)
+ |> User.change_info(&User.Info.confirmation_changeset(&1, need_confirmation: true))
|> Repo.update()
assert user.info.confirmation_pending
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index 6206107f7..f2f334992 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -67,7 +67,9 @@ test "Represent a user account" do
source: %{
note: "valid html",
sensitive: false,
- pleroma: %{},
+ pleroma: %{
+ discoverable: false
+ },
fields: []
},
pleroma: %{
@@ -137,7 +139,9 @@ test "Represent a Service(bot) account" do
source: %{
note: user.bio,
sensitive: false,
- pleroma: %{},
+ pleroma: %{
+ discoverable: false
+ },
fields: []
},
pleroma: %{
@@ -310,7 +314,9 @@ test "represent an embedded relationship" do
source: %{
note: user.bio,
sensitive: false,
- pleroma: %{},
+ pleroma: %{
+ discoverable: false
+ },
fields: []
},
pleroma: %{
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index 8b88fd784..0cf755806 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
import Pleroma.Factory
alias Pleroma.Repo
+ alias Pleroma.User
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.Token
@@ -775,15 +776,11 @@ test "rejects token exchange with invalid client credentials" do
test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do
Pleroma.Config.put([:instance, :account_activation_required], true)
-
password = "testpassword"
- user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
- info_change = Pleroma.User.Info.confirmation_changeset(user.info, need_confirmation: true)
{:ok, user} =
- user
- |> Ecto.Changeset.change()
- |> Ecto.Changeset.put_embed(:info, info_change)
+ insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
+ |> User.change_info(&User.Info.confirmation_changeset(&1, need_confirmation: true))
|> Repo.update()
refute Pleroma.User.auth_active?(user)
diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs
index ec96f0012..2b40fb47e 100644
--- a/test/web/ostatus/ostatus_controller_test.exs
+++ b/test/web/ostatus/ostatus_controller_test.exs
@@ -50,20 +50,16 @@ test "decodes a salmon with a changed magic key", %{conn: conn} do
assert response(conn, 200)
end) =~ "[error]"
- # Set a wrong magic-key for a user so it has to refetch
- salmon_user = User.get_cached_by_ap_id("http://gs.example.org:4040/index.php/user/1")
-
# Wrong key
- info_cng =
- User.Info.remote_user_creation(salmon_user.info, %{
- magic_key:
- "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
- })
+ info = %{
+ magic_key:
+ "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
+ }
- salmon_user
- |> Ecto.Changeset.change()
- |> Ecto.Changeset.put_embed(:info, info_cng)
- |> User.update_and_set_cache()
+ # Set a wrong magic-key for a user so it has to refetch
+ "http://gs.example.org:4040/index.php/user/1"
+ |> User.get_cached_by_ap_id()
+ |> User.update_info(&User.Info.remote_user_creation(&1, info))
assert capture_log(fn ->
conn =
diff --git a/test/web/pleroma_api/emoji_api_controller_test.exs b/test/web/pleroma_api/emoji_api_controller_test.exs
index c5a553692..93a507a01 100644
--- a/test/web/pleroma_api/emoji_api_controller_test.exs
+++ b/test/web/pleroma_api/emoji_api_controller_test.exs
@@ -33,6 +33,28 @@ test "shared & non-shared pack information in list_packs is ok" do
refute pack["pack"]["can-download"]
end
+ test "listing remote packs" do
+ admin = insert(:user, info: %{is_admin: true})
+ conn = build_conn() |> assign(:user, admin)
+
+ resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
+
+ mock(fn
+ %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
+ json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
+
+ %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
+ json(%{metadata: %{features: ["shareable_emoji_packs"]}})
+
+ %{method: :get, url: "https://example.com/api/pleroma/emoji/packs"} ->
+ json(resp)
+ end)
+
+ assert conn
+ |> post(emoji_api_path(conn, :list_from), %{instance_address: "https://example.com"})
+ |> json_response(200) == resp
+ end
+
test "downloading a shared pack from download_shared" do
conn = build_conn()
@@ -55,13 +77,13 @@ test "downloading shared & unshared packs from another instance via download_fro
mock(fn
%{method: :get, url: "https://old-instance/.well-known/nodeinfo"} ->
- json([%{href: "https://old-instance/nodeinfo/2.1.json"}])
+ json(%{links: [%{href: "https://old-instance/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://old-instance/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: []}})
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
- json([%{href: "https://example.com/nodeinfo/2.1.json"}])
+ json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
%{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: ["shareable_emoji_packs"]}})