forked from AkkomaGang/akkoma
Merge branch 'poll-notification' into 'develop'
MastodonAPI: Support poll notification See merge request pleroma/pleroma!3484
This commit is contained in:
commit
901204df22
17 changed files with 314 additions and 28 deletions
|
@ -35,6 +35,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- AdminAPI: return `created_at` date with users.
|
- AdminAPI: return `created_at` date with users.
|
||||||
- `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time.
|
- `AnalyzeMetadata` upload filter for extracting image/video attachment dimensions and generating blurhashes for images. Blurhashes for videos are not generated at this time.
|
||||||
- Attachment dimensions and blurhashes are federated when available.
|
- Attachment dimensions and blurhashes are federated when available.
|
||||||
|
- Mastodon API: support `poll` notification.
|
||||||
- Pinned posts federation
|
- Pinned posts federation
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -560,6 +560,7 @@
|
||||||
mailer: 10,
|
mailer: 10,
|
||||||
transmogrifier: 20,
|
transmogrifier: 20,
|
||||||
scheduled_activities: 10,
|
scheduled_activities: 10,
|
||||||
|
poll_notifications: 10,
|
||||||
background: 5,
|
background: 5,
|
||||||
remote_fetcher: 2,
|
remote_fetcher: 2,
|
||||||
attachments_cleanup: 1,
|
attachments_cleanup: 1,
|
||||||
|
|
|
@ -72,6 +72,7 @@ def unread_notifications_count(%User{id: user_id}) do
|
||||||
pleroma:emoji_reaction
|
pleroma:emoji_reaction
|
||||||
pleroma:report
|
pleroma:report
|
||||||
reblog
|
reblog
|
||||||
|
poll
|
||||||
}
|
}
|
||||||
|
|
||||||
def changeset(%Notification{} = notification, attrs) do
|
def changeset(%Notification{} = notification, attrs) do
|
||||||
|
@ -379,7 +380,7 @@ defp do_create_notifications(%Activity{} = activity, options) do
|
||||||
notifications =
|
notifications =
|
||||||
Enum.map(potential_receivers, fn user ->
|
Enum.map(potential_receivers, fn user ->
|
||||||
do_send = do_send && user in enabled_receivers
|
do_send = do_send && user in enabled_receivers
|
||||||
create_notification(activity, user, do_send)
|
create_notification(activity, user, do_send: do_send)
|
||||||
end)
|
end)
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|
||||||
|
@ -435,15 +436,18 @@ defp type_from_activity_object(%{data: %{"type" => "Create"}} = activity) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO move to sql, too.
|
# TODO move to sql, too.
|
||||||
def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do
|
def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do
|
||||||
unless skip?(activity, user) do
|
do_send = Keyword.get(opts, :do_send, true)
|
||||||
|
type = Keyword.get(opts, :type, type_from_activity(activity))
|
||||||
|
|
||||||
|
unless skip?(activity, user, opts) do
|
||||||
{:ok, %{notification: notification}} =
|
{:ok, %{notification: notification}} =
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.insert(:notification, %Notification{
|
|> Multi.insert(:notification, %Notification{
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
activity: activity,
|
activity: activity,
|
||||||
seen: mark_as_read?(activity, user),
|
seen: mark_as_read?(activity, user),
|
||||||
type: type_from_activity(activity)
|
type: type
|
||||||
})
|
})
|
||||||
|> Marker.multi_set_last_read_id(user, "notifications")
|
|> Marker.multi_set_last_read_id(user, "notifications")
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
|
@ -457,6 +461,28 @@ def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_poll_notifications(%Activity{} = activity) do
|
||||||
|
with %Object{data: %{"type" => "Question", "actor" => actor} = data} <-
|
||||||
|
Object.normalize(activity) do
|
||||||
|
voters =
|
||||||
|
case data do
|
||||||
|
%{"voters" => voters} when is_list(voters) -> voters
|
||||||
|
_ -> []
|
||||||
|
end
|
||||||
|
|
||||||
|
notifications =
|
||||||
|
Enum.reduce([actor | voters], [], fn ap_id, acc ->
|
||||||
|
with %User{local: true} = user <- User.get_by_ap_id(ap_id) do
|
||||||
|
[create_notification(activity, user, type: "poll") | acc]
|
||||||
|
else
|
||||||
|
_ -> acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, notifications}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns a tuple with 2 elements:
|
Returns a tuple with 2 elements:
|
||||||
{notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)}
|
{notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)}
|
||||||
|
@ -572,8 +598,10 @@ def exclude_thread_muter_ap_ids(ap_ids, %Activity{} = activity) do
|
||||||
Enum.uniq(ap_ids) -- thread_muter_ap_ids
|
Enum.uniq(ap_ids) -- thread_muter_ap_ids
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec skip?(Activity.t(), User.t()) :: boolean()
|
def skip?(activity, user, opts \\ [])
|
||||||
def skip?(%Activity{} = activity, %User{} = user) do
|
|
||||||
|
@spec skip?(Activity.t(), User.t(), Keyword.t()) :: boolean()
|
||||||
|
def skip?(%Activity{} = activity, %User{} = user, opts) do
|
||||||
[
|
[
|
||||||
:self,
|
:self,
|
||||||
:invisible,
|
:invisible,
|
||||||
|
@ -581,17 +609,21 @@ def skip?(%Activity{} = activity, %User{} = user) do
|
||||||
:recently_followed,
|
:recently_followed,
|
||||||
:filtered
|
:filtered
|
||||||
]
|
]
|
||||||
|> Enum.find(&skip?(&1, activity, user))
|
|> Enum.find(&skip?(&1, activity, user, opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(_, _), do: false
|
def skip?(_activity, _user, _opts), do: false
|
||||||
|
|
||||||
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
|
@spec skip?(atom(), Activity.t(), User.t(), Keyword.t()) :: boolean()
|
||||||
def skip?(:self, %Activity{} = activity, %User{} = user) do
|
def skip?(:self, %Activity{} = activity, %User{} = user, opts) do
|
||||||
activity.data["actor"] == user.ap_id
|
cond do
|
||||||
|
opts[:type] == "poll" -> false
|
||||||
|
activity.data["actor"] == user.ap_id -> true
|
||||||
|
true -> false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(:invisible, %Activity{} = activity, _) do
|
def skip?(:invisible, %Activity{} = activity, _user, _opts) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
user = User.get_cached_by_ap_id(actor)
|
user = User.get_cached_by_ap_id(actor)
|
||||||
User.invisible?(user)
|
User.invisible?(user)
|
||||||
|
@ -600,15 +632,27 @@ def skip?(:invisible, %Activity{} = activity, _) do
|
||||||
def skip?(
|
def skip?(
|
||||||
:block_from_strangers,
|
:block_from_strangers,
|
||||||
%Activity{} = activity,
|
%Activity{} = activity,
|
||||||
%User{notification_settings: %{block_from_strangers: true}} = user
|
%User{notification_settings: %{block_from_strangers: true}} = user,
|
||||||
|
opts
|
||||||
) do
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
follower = User.get_cached_by_ap_id(actor)
|
follower = User.get_cached_by_ap_id(actor)
|
||||||
!User.following?(follower, user)
|
|
||||||
|
cond do
|
||||||
|
opts[:type] == "poll" -> false
|
||||||
|
user.ap_id == actor -> false
|
||||||
|
!User.following?(follower, user) -> true
|
||||||
|
true -> false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
|
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
|
||||||
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
|
def skip?(
|
||||||
|
:recently_followed,
|
||||||
|
%Activity{data: %{"type" => "Follow"}} = activity,
|
||||||
|
%User{} = user,
|
||||||
|
_opts
|
||||||
|
) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
|
|
||||||
Notification.for_user(user)
|
Notification.for_user(user)
|
||||||
|
@ -618,9 +662,10 @@ def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity,
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
|
def skip?(:filtered, %{data: %{"type" => type}}, _user, _opts) when type in ["Follow", "Move"],
|
||||||
|
do: false
|
||||||
|
|
||||||
def skip?(:filtered, activity, user) do
|
def skip?(:filtered, activity, user, _opts) do
|
||||||
object = Object.normalize(activity, fetch: false)
|
object = Object.normalize(activity, fetch: false)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
|
@ -638,7 +683,7 @@ def skip?(:filtered, activity, user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(_, _, _), do: false
|
def skip?(_type, _activity, _user, _opts), do: false
|
||||||
|
|
||||||
def mark_as_read?(activity, target_user) do
|
def mark_as_read?(activity, target_user) do
|
||||||
user = Activity.user_actor(activity)
|
user = Activity.user_actor(activity)
|
||||||
|
|
|
@ -25,6 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
|
alias Pleroma.Workers.PollWorker
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Pleroma.Web.ActivityPub.Utils
|
import Pleroma.Web.ActivityPub.Utils
|
||||||
|
@ -288,6 +289,7 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
|
||||||
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
|
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
|
||||||
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
||||||
_ <- notify_and_stream(activity),
|
_ <- notify_and_stream(activity),
|
||||||
|
:ok <- maybe_schedule_poll_notifications(activity),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
|
@ -302,6 +304,11 @@ defp do_create(%{to: to, actor: actor, context: context, object: object} = param
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_schedule_poll_notifications(activity) do
|
||||||
|
PollWorker.schedule_poll_end(activity)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
@spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||||
def listen(%{to: to, actor: actor, context: context, object: object} = params) do
|
def listen(%{to: to, actor: actor, context: context, object: object} = params) do
|
||||||
additional = params[:additional] || %{}
|
additional = params[:additional] || %{}
|
||||||
|
|
|
@ -24,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.Push
|
alias Pleroma.Web.Push
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
|
alias Pleroma.Workers.PollWorker
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -195,7 +196,7 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||||
# - Set up notifications
|
# - Set up notifications
|
||||||
@impl true
|
@impl true
|
||||||
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
|
||||||
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
|
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),
|
||||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
|
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
|
||||||
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
|
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
|
||||||
|
@ -389,7 +390,7 @@ def handle(object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
|
def handle_object_creation(%{"type" => "ChatMessage"} = object, _activity, meta) do
|
||||||
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||||
actor = User.get_cached_by_ap_id(object.data["actor"])
|
actor = User.get_cached_by_ap_id(object.data["actor"])
|
||||||
recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
|
recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
|
||||||
|
@ -424,7 +425,14 @@ def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
|
def handle_object_creation(%{"type" => "Question"} = object, activity, meta) do
|
||||||
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||||
|
PollWorker.schedule_poll_end(activity)
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_object_creation(%{"type" => "Answer"} = object_map, _activity, meta) do
|
||||||
with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
|
||||||
Object.increase_vote_count(
|
Object.increase_vote_count(
|
||||||
object.data["inReplyTo"],
|
object.data["inReplyTo"],
|
||||||
|
@ -436,15 +444,15 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_object_creation(%{"type" => objtype} = object, meta)
|
def handle_object_creation(%{"type" => objtype} = object, _activity, meta)
|
||||||
when objtype in ~w[Audio Video Question Event Article Note Page] do
|
when objtype in ~w[Audio Video Event Article Note Page] do
|
||||||
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
def handle_object_creation(object, meta) do
|
def handle_object_creation(object, _activity, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,8 @@ defp notification_type do
|
||||||
"pleroma:chat_mention",
|
"pleroma:chat_mention",
|
||||||
"pleroma:report",
|
"pleroma:report",
|
||||||
"move",
|
"move",
|
||||||
"follow_request"
|
"follow_request",
|
||||||
|
"poll"
|
||||||
],
|
],
|
||||||
description: """
|
description: """
|
||||||
The type of event that resulted in the notification.
|
The type of event that resulted in the notification.
|
||||||
|
|
|
@ -50,6 +50,7 @@ def index(conn, %{account_id: account_id} = params) do
|
||||||
favourite
|
favourite
|
||||||
move
|
move
|
||||||
pleroma:emoji_reaction
|
pleroma:emoji_reaction
|
||||||
|
poll
|
||||||
}
|
}
|
||||||
def index(%{assigns: %{user: user}} = conn, params) do
|
def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
params =
|
params =
|
||||||
|
|
|
@ -112,6 +112,9 @@ def render(
|
||||||
"move" ->
|
"move" ->
|
||||||
put_target(response, activity, reading_user, %{})
|
put_target(response, activity, reading_user, %{})
|
||||||
|
|
||||||
|
"poll" ->
|
||||||
|
put_status(response, activity, reading_user, status_render_opts)
|
||||||
|
|
||||||
"pleroma:emoji_reaction" ->
|
"pleroma:emoji_reaction" ->
|
||||||
response
|
response
|
||||||
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)
|
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)
|
||||||
|
|
|
@ -26,7 +26,7 @@ defmodule Pleroma.Web.Push.Subscription do
|
||||||
end
|
end
|
||||||
|
|
||||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||||
@supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention pleroma:emoji_reaction]a
|
@supported_alert_types ~w[follow favourite mention reblog poll pleroma:chat_mention pleroma:emoji_reaction]a
|
||||||
|
|
||||||
defp alerts(%{data: %{alerts: alerts}}) do
|
defp alerts(%{data: %{alerts: alerts}}) do
|
||||||
alerts = Map.take(alerts, @supported_alert_types)
|
alerts = Map.take(alerts, @supported_alert_types)
|
||||||
|
|
45
lib/pleroma/workers/poll_worker.ex
Normal file
45
lib/pleroma/workers/poll_worker.ex
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Workers.PollWorker do
|
||||||
|
@moduledoc """
|
||||||
|
Generates notifications when a poll ends.
|
||||||
|
"""
|
||||||
|
use Pleroma.Workers.WorkerHelper, queue: "poll_notifications"
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Notification
|
||||||
|
alias Pleroma.Object
|
||||||
|
|
||||||
|
@impl Oban.Worker
|
||||||
|
def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do
|
||||||
|
with %Activity{} = activity <- find_poll_activity(activity_id) do
|
||||||
|
Notification.create_poll_notifications(activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp find_poll_activity(activity_id) do
|
||||||
|
with nil <- Activity.get_by_id(activity_id) do
|
||||||
|
{:error, :poll_activity_not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} = activity) do
|
||||||
|
with %Object{data: %{"type" => "Question", "closed" => closed}} when is_binary(closed) <-
|
||||||
|
Object.normalize(activity),
|
||||||
|
{:ok, end_time} <- NaiveDateTime.from_iso8601(closed),
|
||||||
|
:gt <- NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) do
|
||||||
|
%{
|
||||||
|
op: "poll_end",
|
||||||
|
activity_id: activity_id
|
||||||
|
}
|
||||||
|
|> new(scheduled_at: end_time)
|
||||||
|
|> Oban.insert()
|
||||||
|
else
|
||||||
|
_ -> {:error, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def schedule_poll_end(activity), do: {:error, activity}
|
||||||
|
end
|
|
@ -0,0 +1,49 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddPollToNotificationsEnum do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
@disable_ddl_transaction true
|
||||||
|
|
||||||
|
def up do
|
||||||
|
"""
|
||||||
|
alter type notification_type add value 'poll'
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:notifications) do
|
||||||
|
modify(:type, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
delete from notifications where type = 'poll'
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
drop type if exists notification_type
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
create type notification_type as enum (
|
||||||
|
'follow',
|
||||||
|
'follow_request',
|
||||||
|
'mention',
|
||||||
|
'move',
|
||||||
|
'pleroma:emoji_reaction',
|
||||||
|
'pleroma:chat_mention',
|
||||||
|
'reblog',
|
||||||
|
'favourite',
|
||||||
|
'pleroma:report'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
alter table notifications
|
||||||
|
alter column type type notification_type using (type::notification_type)
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
end
|
||||||
|
end
|
|
@ -129,6 +129,19 @@ test "does not create a notification for subscribed users if status is a reply"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "create_poll_notifications/1" do
|
||||||
|
[user1, user2, user3, _, _] = insert_list(5, :user)
|
||||||
|
question = insert(:question, user: user1)
|
||||||
|
activity = insert(:question_activity, question: question)
|
||||||
|
|
||||||
|
{:ok, _, _} = CommonAPI.vote(user2, question, [0])
|
||||||
|
{:ok, _, _} = CommonAPI.vote(user3, question, [1])
|
||||||
|
|
||||||
|
{:ok, notifications} = Notification.create_poll_notifications(activity)
|
||||||
|
|
||||||
|
assert [user2.id, user3.id, user1.id] == Enum.map(notifications, & &1.user_id)
|
||||||
|
end
|
||||||
|
|
||||||
describe "CommonApi.post/2 notification-related functionality" do
|
describe "CommonApi.post/2 notification-related functionality" do
|
||||||
test_with_mock "creates but does NOT send notification to blocker user",
|
test_with_mock "creates but does NOT send notification to blocker user",
|
||||||
Push,
|
Push,
|
||||||
|
|
|
@ -157,6 +157,30 @@ test "creates a notification", %{emoji_react: emoji_react, poster: poster} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Question objects" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
question = build(:question, user: user)
|
||||||
|
question_activity = build(:question_activity, question: question)
|
||||||
|
activity_data = Map.put(question_activity.data, "object", question.data["id"])
|
||||||
|
meta = [object_data: question.data, local: false]
|
||||||
|
|
||||||
|
{:ok, activity, meta} = ActivityPub.persist(activity_data, meta)
|
||||||
|
|
||||||
|
%{activity: activity, meta: meta}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "enqueues the poll end", %{activity: activity, meta: meta} do
|
||||||
|
{:ok, activity, meta} = SideEffects.handle(activity, meta)
|
||||||
|
|
||||||
|
assert_enqueued(
|
||||||
|
worker: Pleroma.Workers.PollWorker,
|
||||||
|
args: %{op: "poll_end", activity_id: activity.id},
|
||||||
|
scheduled_at: NaiveDateTime.from_iso8601!(meta[:object_data]["closed"])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "delete users with confirmation pending" do
|
describe "delete users with confirmation pending" do
|
||||||
setup do
|
setup do
|
||||||
user = insert(:user, is_confirmed: false)
|
user = insert(:user, is_confirmed: false)
|
||||||
|
|
|
@ -18,6 +18,7 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Workers.PollWorker
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import Mock
|
import Mock
|
||||||
|
@ -48,6 +49,12 @@ test "it posts a poll" do
|
||||||
|
|
||||||
assert object.data["type"] == "Question"
|
assert object.data["type"] == "Question"
|
||||||
assert object.data["oneOf"] |> length() == 2
|
assert object.data["oneOf"] |> length() == 2
|
||||||
|
|
||||||
|
assert_enqueued(
|
||||||
|
worker: PollWorker,
|
||||||
|
args: %{op: "poll_end", activity_id: activity.id},
|
||||||
|
scheduled_at: NaiveDateTime.from_iso8601!(object.data["closed"])
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Workers.ScheduledActivityWorker
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
@ -705,11 +706,11 @@ test "scheduled poll", %{conn: conn} do
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert {:ok, %{id: activity_id}} =
|
assert {:ok, %{id: activity_id}} =
|
||||||
perform_job(Pleroma.Workers.ScheduledActivityWorker, %{
|
perform_job(ScheduledActivityWorker, %{
|
||||||
activity_id: scheduled_id
|
activity_id: scheduled_id
|
||||||
})
|
})
|
||||||
|
|
||||||
assert Repo.all(Oban.Job) == []
|
refute_enqueued(worker: ScheduledActivityWorker)
|
||||||
|
|
||||||
object =
|
object =
|
||||||
Activity
|
Activity
|
||||||
|
|
|
@ -196,6 +196,27 @@ test "EmojiReact notification" do
|
||||||
test_notifications_rendering([notification], user, [expected])
|
test_notifications_rendering([notification], user, [expected])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "Poll notification" do
|
||||||
|
user = insert(:user)
|
||||||
|
activity = insert(:question_activity, user: user)
|
||||||
|
{:ok, [notification]} = Notification.create_poll_notifications(activity)
|
||||||
|
|
||||||
|
expected = %{
|
||||||
|
id: to_string(notification.id),
|
||||||
|
pleroma: %{is_seen: false, is_muted: false},
|
||||||
|
type: "poll",
|
||||||
|
account:
|
||||||
|
AccountView.render("show.json", %{
|
||||||
|
user: user,
|
||||||
|
for: user
|
||||||
|
}),
|
||||||
|
status: StatusView.render("show.json", %{activity: activity, for: user}),
|
||||||
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_notifications_rendering([notification], user, [expected])
|
||||||
|
end
|
||||||
|
|
||||||
test "Report notification" do
|
test "Report notification" do
|
||||||
reporting_user = insert(:user)
|
reporting_user = insert(:user)
|
||||||
reported_user = insert(:user)
|
reported_user = insert(:user)
|
||||||
|
|
|
@ -213,6 +213,38 @@ def tombstone_factory do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def question_factory(attrs \\ %{}) do
|
||||||
|
user = attrs[:user] || insert(:user)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
|
||||||
|
"type" => "Question",
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"attributedTo" => user.ap_id,
|
||||||
|
"attachment" => [],
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"cc" => [user.follower_address],
|
||||||
|
"context" => Pleroma.Web.ActivityPub.Utils.generate_context_id(),
|
||||||
|
"closed" => DateTime.utc_now() |> DateTime.add(86_400) |> DateTime.to_iso8601(),
|
||||||
|
"oneOf" => [
|
||||||
|
%{
|
||||||
|
"type" => "Note",
|
||||||
|
"name" => "chocolate",
|
||||||
|
"replies" => %{"totalItems" => 0, "type" => "Collection"}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"type" => "Note",
|
||||||
|
"name" => "vanilla",
|
||||||
|
"replies" => %{"totalItems" => 0, "type" => "Collection"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
%Pleroma.Object{
|
||||||
|
data: merge_attributes(data, Map.get(attrs, :data, %{}))
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def direct_note_activity_factory do
|
def direct_note_activity_factory do
|
||||||
dm = insert(:direct_note)
|
dm = insert(:direct_note)
|
||||||
|
|
||||||
|
@ -428,6 +460,33 @@ def report_activity_factory(attrs \\ %{}) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def question_activity_factory(attrs \\ %{}) do
|
||||||
|
user = attrs[:user] || insert(:user)
|
||||||
|
question = attrs[:question] || insert(:question, user: user)
|
||||||
|
|
||||||
|
data_attrs = attrs[:data_attrs] || %{}
|
||||||
|
attrs = Map.drop(attrs, [:user, :question, :data_attrs])
|
||||||
|
|
||||||
|
data =
|
||||||
|
%{
|
||||||
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => question.data["actor"],
|
||||||
|
"to" => question.data["to"],
|
||||||
|
"object" => question.data["id"],
|
||||||
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
|
"context" => question.data["context"]
|
||||||
|
}
|
||||||
|
|> Map.merge(data_attrs)
|
||||||
|
|
||||||
|
%Pleroma.Activity{
|
||||||
|
data: data,
|
||||||
|
actor: data["actor"],
|
||||||
|
recipients: data["to"]
|
||||||
|
}
|
||||||
|
|> Map.merge(attrs)
|
||||||
|
end
|
||||||
|
|
||||||
def oauth_app_factory do
|
def oauth_app_factory do
|
||||||
%Pleroma.Web.OAuth.App{
|
%Pleroma.Web.OAuth.App{
|
||||||
client_name: sequence(:client_name, &"Some client #{&1}"),
|
client_name: sequence(:client_name, &"Some client #{&1}"),
|
||||||
|
|
Loading…
Reference in a new issue