Add webpush front-end support

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2021-05-06 18:39:59 +02:00
parent 8c6b0003bc
commit 938f698b7a
99 changed files with 2594 additions and 1536 deletions

View File

@@ -5,9 +5,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Activity do
import Mobilizon.Users.Guards
alias Mobilizon.{Activities, Actors, Users}
alias Mobilizon.Activities.Activity
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Activity, as: ActivityService
alias Mobilizon.Service.Activity.Utils
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
@@ -27,12 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Activity do
limit
)
elements =
Enum.map(elements, fn %Activity{} = activity ->
activity
|> Map.update(:subject_params, %{}, &transform_params/1)
|> Map.put(:object, ActivityService.object(activity))
end)
elements = Enum.map(elements, &Utils.transform_activity/1)
{:ok, %Page{total: total, elements: elements}}
else
@@ -44,15 +38,4 @@ defmodule Mobilizon.GraphQL.Resolvers.Activity do
def group_activity(_, _, _) do
{:error, :unauthenticated}
end
@spec transform_params(map()) :: list()
defp transform_params(params) do
Enum.map(params, fn {key, value} -> %{key: key, value: transform_value(value)} end)
end
defp transform_value(value) when is_list(value) do
Enum.join(value, ",")
end
defp transform_value(value), do: value
end

View File

@@ -143,6 +143,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
},
instance_feeds: %{
enabled: Config.get([:instance, :enable_instance_feeds])
},
web_push: %{
enabled: !is_nil(Application.get_env(:web_push_encryption, :vapid_details)),
public_key:
get_in(Application.get_env(:web_push_encryption, :vapid_details), [:public_key])
}
}
end

View File

@@ -25,25 +25,39 @@ defmodule Mobilizon.GraphQL.Resolvers.PushSubscription do
def register_push_subscription(_parent, args, %{
context: %{current_user: %User{id: user_id}}
}) do
Users.create_push_subscription(Map.put(args, :user_id, user_id))
case Users.create_push_subscription(Map.put(args, :user_id, user_id)) do
{:ok, %PushSubscription{}} ->
{:ok, "OK"}
{:error, err} ->
require Logger
Logger.error(inspect(err))
{:error, "Something went wrong"}
end
end
@spec unregister_push_subscription(map(), map(), map()) ::
{:ok, PushSubscription.t()} | {:error, :unauthorized} | {:error, :not_found}
def unregister_push_subscription(_parent, %{id: push_subscription_id}, %{
def unregister_push_subscription(_parent, %{endpoint: push_subscription_endpoint}, %{
context: %{current_user: %User{id: user_id}}
}) do
with %PushSubscription{user: %User{id: push_subscription_user_id}} = push_subscription <-
Users.get_push_subscription(push_subscription_id),
Users.get_push_subscription_by_endpoint(push_subscription_endpoint),
{:user_owns_push_subscription, true} <-
{:user_owns_push_subscription, push_subscription_user_id == user_id} do
Users.delete_push_subscription(push_subscription)
{:user_owns_push_subscription, push_subscription_user_id == user_id},
{:ok, %PushSubscription{}} <- Users.delete_push_subscription(push_subscription) do
{:ok, "OK"}
else
{:user_owns_push_subscription, false} ->
{:error, :unauthorized}
nil ->
{:error, :not_found}
{:error, err} ->
require Logger
Logger.error(inspect(err))
{:error, "Something went wrong"}
end
end
end

View File

@@ -47,6 +47,7 @@ defmodule Mobilizon.GraphQL.Schema do
import_types(Schema.ReportType)
import_types(Schema.AdminType)
import_types(Schema.StatisticsType)
import_types(Schema.Users.PushSubscription)
@desc "A struct containing the id of the deleted object"
object :deleted_object do
@@ -155,6 +156,7 @@ defmodule Mobilizon.GraphQL.Schema do
import_fields(:resource_queries)
import_fields(:post_queries)
import_fields(:statistics_queries)
# import_fields(:push_queries)
end
@desc """
@@ -179,6 +181,7 @@ defmodule Mobilizon.GraphQL.Schema do
import_fields(:post_mutations)
import_fields(:actor_mutations)
import_fields(:follower_mutations)
import_fields(:push_mutations)
end
@desc """

View File

@@ -64,6 +64,7 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
field(:auth, :auth, description: "The instance auth methods")
field(:instance_feeds, :instance_feeds, description: "The instance's feed settings")
field(:web_push, :web_push, description: "Web Push settings for the instance")
end
@desc """
@@ -301,6 +302,11 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
field(:enabled, :boolean, description: "Whether the instance-wide feeds are enabled")
end
object :web_push do
field(:enabled, :boolean, description: "Whether the WebPush feature is enabled")
field(:public_key, :string, description: "The server's public WebPush VAPID key")
end
object :config_queries do
@desc "Get the instance config"
field :config, :config do

View File

@@ -5,29 +5,32 @@ defmodule Mobilizon.GraphQL.Schema.Users.PushSubscription do
use Absinthe.Schema.Notation
alias Mobilizon.GraphQL.Resolvers.PushSubscription
@desc """
An object representing the keys for a push subscription
"""
input_object :push_subscription_keys do
field(:p256dh, non_null(:string))
field(:auth, non_null(:string))
end
# object :push_subscription do
# field(:id, :id)
# end
object :push_queries do
field :list_push_subscriptions, :paginated_push_subscription_list do
resolve(&PushSubscription.list_user_push_subscriptions/3)
end
end
# @desc "A paginated list of subscriptions"
# object :paginated_push_subscription_list do
# field(:elements, list_of(:push_subscription), description: "A list of push subscriptions")
# field(:total, :integer, description: "The total number of push subscriptions in the list")
# end
# object :push_queries do
# field :list_push_subscriptions, :paginated_push_subscription_list do
# resolve(&PushSubscription.list_user_push_subscriptions/3)
# end
# end
object :push_mutations do
field :register_push_mutation, :string do
field :register_push, :string do
arg(:endpoint, non_null(:string))
arg(:keys, non_null(:push_subscription_keys))
arg(:auth, non_null(:string))
arg(:p256dh, non_null(:string))
resolve(&PushSubscription.register_push_subscription/3)
end
field :unregister_push_mutation, :string do
arg(:id, non_null(:id))
field :unregister_push, :string do
arg(:endpoint, non_null(:string))
resolve(&PushSubscription.unregister_push_subscription/3)
end
end

View File

@@ -3,45 +3,29 @@ defmodule Mobilizon.Users.PushSubscription do
alias Mobilizon.Users.User
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
schema "user_push_subscriptions" do
field(:digest, :string)
belongs_to(:user, User)
embeds_one :data, Data, on_replace: :delete do
field(:endpoint, :string)
embeds_one :keys, Keys, on_replace: :delete do
field(:auth, :string)
field(:p256dh, :string)
end
end
field(:endpoint, :string)
field(:auth, :string)
field(:p256dh, :string)
timestamps()
end
@doc false
def changeset(push_subscription, attrs) do
push_subscription
|> cast(attrs, [:user_id])
|> cast_embed(:data, with: &cast_data/2)
|> put_change(:digest, compute_digest(attrs.data))
|> validate_required([:digest, :user_id, :data])
|> cast(attrs, [:user_id, :endpoint, :auth, :p256dh])
|> put_change(:digest, compute_digest(attrs))
|> validate_required([:digest, :user_id, :endpoint, :auth, :p256dh])
|> unique_constraint([:digest, :user_id], name: :user_push_subscriptions_user_id_digest_index)
end
defp cast_data(schema, attrs) do
schema
|> cast(attrs, [:endpoint])
|> cast_embed(:keys, with: &cast_keys/2)
|> validate_required([:endpoint, :keys])
end
defp compute_digest(attrs) do
data =
Jason.encode!(%{endpoint: attrs.endpoint, keys: %{auth: attrs.auth, p256dh: attrs.p256dh}})
defp cast_keys(schema, attrs) do
schema
|> cast(attrs, [:auth, :p256dh])
|> validate_required([:auth, :p256dh])
end
defp compute_digest(data) do
:sha256
|> :crypto.hash(data)
|> Base.encode16()

View File

@@ -413,17 +413,16 @@ defmodule Mobilizon.Users do
def list_user_push_subscriptions(user_id, page \\ nil, limit \\ nil) do
PushSubscription
|> where([p], p.user_id == ^user_id)
|> preload([:user])
|> Page.build_page(page, limit)
end
@doc """
Get a push subscription by their ID
Get a push subscription by their endpoint
"""
@spec get_push_subscription(String.t() | integer()) :: PushSubscription.t() | nil
def get_push_subscription(push_subscription_id) do
@spec get_push_subscription_by_endpoint(String.t()) :: PushSubscription.t() | nil
def get_push_subscription_by_endpoint(endpoint) do
PushSubscription
|> Repo.get(push_subscription_id)
|> Repo.get_by(endpoint: endpoint)
|> Repo.preload([:user])
end

View File

@@ -0,0 +1,83 @@
defmodule Mobilizon.Service.Activity.Renderer.Discussion do
@moduledoc """
Insert a comment activity
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Activity.Renderer
alias Mobilizon.Web.{Endpoint, Gettext}
alias Mobilizon.Web.Router.Helpers, as: Routes
import Mobilizon.Web.Gettext, only: [dgettext: 3]
@behaviour Renderer
@impl Renderer
def render(%Activity{} = activity, options) do
locale = Keyword.get(options, :locale, "en")
Gettext.put_locale(locale)
case activity.subject do
:discussion_created ->
%{
body:
dgettext("activity", "%{profile} created the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: discussion_url(activity)
}
:discussion_replied ->
%{
body:
dgettext("activity", "%{profile} replied to the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: discussion_url(activity)
}
:discussion_renamed ->
%{
body:
dgettext("activity", "%{profile} renamed the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: discussion_url(activity)
}
:discussion_archived ->
%{
body:
dgettext("activity", "%{profile} archived the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: discussion_url(activity)
}
:discussion_deleted ->
%{
body:
dgettext("activity", "%{profile} deleted the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: nil
}
end
end
defp discussion_url(activity) do
Routes.page_url(
Endpoint,
:discussion,
Actor.preferred_username_and_domain(activity.group),
activity.subject_params["discussion_slug"]
)
end
defp profile(activity), do: Actor.display_name_and_username(activity.author)
defp title(activity), do: activity.subject_params["discussion_title"]
end

View File

@@ -0,0 +1,83 @@
defmodule Mobilizon.Service.Activity.Renderer.Event do
@moduledoc """
Insert a comment activity
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Activity.Renderer
alias Mobilizon.Web.{Endpoint, Gettext}
alias Mobilizon.Web.Router.Helpers, as: Routes
import Mobilizon.Web.Gettext, only: [dgettext: 3]
@behaviour Renderer
@impl Renderer
def render(%Activity{} = activity, options) do
locale = Keyword.get(options, :locale, "en")
Gettext.put_locale(locale)
case activity.subject do
:event_created ->
%{
body:
dgettext("activity", "The event %{event} was created by %{profile}.", %{
profile: profile(activity),
event: title(activity)
}),
url: event_url(activity)
}
:event_updated ->
%{
body:
dgettext("activity", "The event %{event} was updated by %{profile}.", %{
profile: profile(activity),
event: title(activity)
}),
url: event_url(activity)
}
:event_deleted ->
%{
body:
dgettext("activity", "The event %{event} was deleted by %{profile}.", %{
profile: profile(activity),
event: title(activity)
}),
url: nil
}
:comment_posted ->
if activity.subject_params["comment_reply_to"] do
%{
body:
dgettext("activity", "%{profile} replied to a comment on the event %{event}.", %{
profile: profile(activity),
event: title(activity)
}),
url: event_url(activity)
}
else
%{
body:
dgettext("activity", "%{profile} posted a comment on the event %{event}.", %{
profile: profile(activity),
event: title(activity)
}),
url: event_url(activity)
}
end
end
end
defp event_url(activity) do
Routes.page_url(
Endpoint,
:event,
activity.subject_params["event_uuid"]
)
end
defp profile(activity), do: Actor.display_name_and_username(activity.author)
defp title(activity), do: activity.subject_params["event_title"]
end

View File

@@ -0,0 +1,58 @@
defmodule Mobilizon.Service.Activity.Renderer.Group do
@moduledoc """
Insert a comment activity
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Activity.Renderer
alias Mobilizon.Web.{Endpoint, Gettext}
alias Mobilizon.Web.Router.Helpers, as: Routes
import Mobilizon.Web.Gettext, only: [dgettext: 3]
@behaviour Renderer
@impl Renderer
def render(%Activity{} = activity, options) do
locale = Keyword.get(options, :locale, "en")
Gettext.put_locale(locale)
case activity.subject do
:post_created ->
%{
body:
dgettext("activity", "The post %{post} was created by %{profile}.", %{
profile: profile(activity),
post: title(activity)
}),
url: post_url(activity)
}
:post_updated ->
%{
body:
dgettext("activity", "The post %{post} was updated by %{profile}.", %{
profile: profile(activity),
post: title(activity)
}),
url: post_url(activity)
}
:post_deleted ->
%{
body:
dgettext("activity", "The post %{post} was deleted by %{profile}.", %{
profile: profile(activity),
post: title(activity)
}),
url: post_url(activity)
}
end
end
defp post_url(activity) do
Routes.page_url(Endpoint, :post, activity.subject_params["post_slug"])
end
defp profile(activity), do: Actor.display_name_and_username(activity.author)
defp title(activity), do: activity.subject_params["post_title"]
end

View File

@@ -0,0 +1,122 @@
defmodule Mobilizon.Service.Activity.Renderer.Member do
@moduledoc """
Insert a comment activity
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Activity.Renderer
alias Mobilizon.Web.{Endpoint, Gettext}
alias Mobilizon.Web.Router.Helpers, as: Routes
import Mobilizon.Web.Gettext, only: [dgettext: 3]
@behaviour Renderer
@impl Renderer
def render(%Activity{} = activity, options) do
locale = Keyword.get(options, :locale, "en")
Gettext.put_locale(locale)
case activity.subject do
:member_request ->
%{
body:
dgettext("activity", "%{member} requested to join the group.", %{
profile: profile(activity),
member: title(activity)
}),
url: member_url(activity)
}
:member_invited ->
%{
body:
dgettext("activity", "%{member} was invited by %{profile}.", %{
profile: profile(activity),
member: title(activity)
}),
url: member_url(activity)
}
:member_accepted_invitation ->
%{
body:
dgettext("activity", "%{member} accepted the invitation to join the group.", %{
profile: profile(activity),
member: title(activity)
}),
url: member_url(activity)
}
:member_rejected_invitation ->
%{
body:
dgettext("activity", "%{member} rejected the invitation to join the group.", %{
profile: profile(activity),
member: title(activity)
}),
url: member_url(activity)
}
:member_joined ->
%{
body:
dgettext("activity", "%{member} joined the group.", %{
member: title(activity)
}),
url: member_url(activity)
}
:member_added ->
%{
body:
dgettext("activity", "%{profile} added the member %{member}.", %{
profile: profile(activity),
member: title(activity)
}),
url: member_url(activity)
}
:member_updated ->
%{
body:
dgettext("activity", "%{profile} updated the member %{member}.", %{
profile: profile(activity),
member: title(activity)
}),
url: member_url(activity)
}
:member_removed ->
%{
body:
dgettext("activity", "%{profile} excluded member %{member}.", %{
profile: profile(activity),
member: title(activity)
}),
url: member_url(activity)
}
:member_quit ->
%{
body:
dgettext("activity", "%{profile} quit the group.", %{
profile: profile(activity),
member: title(activity)
}),
url: member_url(activity)
}
end
end
defp member_url(activity) do
Routes.page_url(
Endpoint,
:discussion,
Actor.preferred_username_and_domain(activity.group),
activity.subject_params["discussion_slug"]
)
end
defp profile(activity), do: Actor.display_name_and_username(activity.author)
defp title(activity), do: activity.subject_params["discussion_title"]
end

View File

@@ -0,0 +1,83 @@
defmodule Mobilizon.Service.Activity.Renderer.Post do
@moduledoc """
Insert a comment activity
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Activity.Renderer
alias Mobilizon.Web.{Endpoint, Gettext}
alias Mobilizon.Web.Router.Helpers, as: Routes
import Mobilizon.Web.Gettext, only: [dgettext: 3]
@behaviour Renderer
@impl Renderer
def render(%Activity{} = activity, options) do
locale = Keyword.get(options, :locale, "en")
Gettext.put_locale(locale)
case activity.subject do
:discussion_created ->
%{
body:
dgettext("activity", "%{profile} created the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: discussion_url(activity)
}
:discussion_replied ->
%{
body:
dgettext("activity", "%{profile} replied to the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: discussion_url(activity)
}
:discussion_renamed ->
%{
body:
dgettext("activity", "%{profile} renamed the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: discussion_url(activity)
}
:discussion_archived ->
%{
body:
dgettext("activity", "%{profile} archived the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: discussion_url(activity)
}
:discussion_deleted ->
%{
body:
dgettext("activity", "%{profile} deleted the discussion %{discussion}.", %{
profile: profile(activity),
discussion: title(activity)
}),
url: nil
}
end
end
defp discussion_url(activity) do
Routes.page_url(
Endpoint,
:discussion,
Actor.preferred_username_and_domain(activity.group),
activity.subject_params["discussion_slug"]
)
end
defp profile(activity), do: Actor.display_name_and_username(activity.author)
defp title(activity), do: activity.subject_params["discussion_title"]
end

View File

@@ -0,0 +1,47 @@
defmodule Mobilizon.Service.Activity.Renderer do
@moduledoc """
Behavior for Activity renderers
"""
alias Mobilizon.Config
alias Mobilizon.Activities.Activity
alias Mobilizon.Service.Activity.Renderer.{Discussion, Event, Group, Member, Post, Resource}
require Logger
import Mobilizon.Web.Gettext, only: [dgettext: 3]
@type render :: %{body: String.t(), url: String.t()}
@callback render(entity :: Activity.t(), Keyword.t()) :: render()
@spec render(Activity.t()) :: render()
def render(%Activity{} = activity, options \\ []) do
locale = Keyword.get(options, :locale, "en")
Gettext.put_locale(locale)
res =
activity
|> do_render(options)
|> Map.put(:timestamp, DateTime.utc_now() |> DateTime.to_iso8601())
|> Map.put(:locale, Keyword.get(options, :locale, "en"))
|> Map.put(
:title,
dgettext("activity", "Activity on %{instance}", %{instance: Config.instance_name()})
)
Logger.debug("notification to be sent")
Logger.debug(inspect(res))
res
end
defp do_render(%Activity{type: type} = activity, options) do
case type do
:discussion -> Discussion.render(activity, options)
:event -> Event.render(activity, options)
:group -> Group.render(activity, options)
:member -> Member.render(activity, options)
:post -> Post.render(activity, options)
:resource -> Resource.render(activity, options)
_ -> nil
end
end
end

View File

@@ -0,0 +1,122 @@
defmodule Mobilizon.Service.Activity.Renderer.Resource do
@moduledoc """
Insert a comment activity
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Activity.Renderer
alias Mobilizon.Web.{Endpoint, Gettext}
alias Mobilizon.Web.Router.Helpers, as: Routes
import Mobilizon.Web.Gettext, only: [dgettext: 3]
@behaviour Renderer
@impl Renderer
def render(%Activity{} = activity, options) do
locale = Keyword.get(options, :locale, "en")
Gettext.put_locale(locale)
case activity.subject do
:resource_created ->
if activity.subject_params["is_folder"] do
%{
body:
dgettext("activity", "%{profile} created the folder %{resource}.", %{
profile: profile(activity),
resource: title(activity)
}),
url: resource_url(activity)
}
else
%{
body:
dgettext("activity", "%{profile} created the resource %{resource}.", %{
profile: profile(activity),
resource: title(activity)
}),
url: resource_url(activity)
}
end
:resource_renamed ->
if activity.subject_params["is_folder"] do
%{
body:
dgettext(
"activity",
"%{profile} renamed the folder from %{old_resource_title} to %{resource}.",
%{
profile: profile(activity),
resource: title(activity),
old_resource_title: activity.subject_params["old_resource_title"]
}
),
url: resource_url(activity)
}
else
%{
body:
dgettext(
"activity",
"%{profile} renamed the resource from %{old_resource_title} to %{resource}.",
%{
profile: profile(activity),
resource: title(activity),
old_resource_title: activity.subject_params["old_resource_title"]
}
),
url: resource_url(activity)
}
end
:resource_moved ->
if activity.subject_params["is_folder"] do
%{
body:
dgettext("activity", "%{profile} moved the folder %{resource}.", %{
profile: profile(activity),
resource: title(activity)
}),
url: resource_url(activity)
}
else
%{
body:
dgettext("activity", "%{profile} moved the resource %{resource}.", %{
profile: profile(activity),
resource: title(activity)
}),
url: resource_url(activity)
}
end
:resource_deleted ->
if activity.subject_params["is_folder"] do
%{
body:
dgettext("activity", "%{profile} deleted the folder %{resource}.", %{
profile: profile(activity),
resource: title(activity)
}),
url: resource_url(activity)
}
else
%{
body:
dgettext("activity", "%{profile} deleted the resource %{resource}.", %{
profile: profile(activity),
resource: title(activity)
}),
url: resource_url(activity)
}
end
end
end
defp resource_url(activity) do
Routes.page_url(Endpoint, :resource, activity.subject_params["resource_uuid"])
end
defp profile(activity), do: Actor.display_name_and_username(activity.author)
defp title(activity), do: activity.subject_params["resource_title"]
end

View File

@@ -0,0 +1,30 @@
defmodule Mobilizon.Service.Activity.Utils do
@moduledoc """
Utils for activities
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.Service.Activity, as: ActivityService
def transform_activity(%Activity{} = activity) do
activity
|> Map.update(:subject_params, %{}, &transform_params/1)
|> add_activity_object()
end
@spec add_activity_object(Activity.t()) :: Activity.t()
def add_activity_object(%Activity{} = activity) do
Map.put(activity, :object, ActivityService.object(activity))
end
@spec transform_params(map()) :: list()
defp transform_params(params) do
Enum.map(params, fn {key, value} -> %{key: key, value: transform_value(value)} end)
end
defp transform_value(value) when is_list(value) do
Enum.join(value, ",")
end
defp transform_value(value), do: value
end

View File

@@ -4,10 +4,11 @@ defmodule Mobilizon.Service.Notifier.Push do
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.{Config, Users}
alias Mobilizon.Service.Activity.{Renderer, Utils}
alias Mobilizon.Service.Notifier
alias Mobilizon.Service.Notifier.Push
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
alias Mobilizon.Users.{PushSubscription, User}
@behaviour Notifier
@@ -17,26 +18,37 @@ defmodule Mobilizon.Service.Notifier.Push do
end
@impl Notifier
def send(%User{id: user_id} = _user, %Activity{} = activity, _opts) do
def send(user, activity, options \\ [])
def send(%User{id: user_id, locale: locale} = _user, %Activity{} = activity, options) do
options = Keyword.put_new(options, :locale, locale)
%Page{elements: subscriptions} = Users.list_user_push_subscriptions(user_id, 1, 100)
Enum.each(subscriptions, &send_subscription(activity, &1))
Enum.map(subscriptions, &send_subscription(activity, convert_subscription(&1), options))
end
@impl Notifier
def send(%User{} = user, activities, opts) when is_list(activities) do
Enum.each(activities, &Push.send(user, &1, opts))
def send(%User{} = user, activities, options) when is_list(activities) do
Enum.map(activities, &Push.send(user, &1, options))
end
defp payload(%Activity{subject: subject}) do
%{
title: subject
}
defp send_subscription(activity, subscription, options) do
activity
|> payload(options)
|> WebPushEncryption.send_web_push(subscription)
end
defp payload(%Activity{} = activity, options) do
activity
|> Utils.add_activity_object()
|> Renderer.render(options)
|> Jason.encode!()
end
defp send_subscription(activity, subscription) do
activity
|> payload()
|> WebPushEncryption.send_web_push(subscription)
defp convert_subscription(%PushSubscription{} = subscription) do
%{
endpoint: subscription.endpoint,
keys: %{auth: subscription.auth, p256dh: subscription.p256dh}
}
end
end