batch mention email notifications in timeframe
This commit is contained in:
parent
b050adb5e2
commit
69f8f9446e
@ -85,6 +85,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Ability to define custom HTTP headers per each frontend
|
||||
- MRF (`NoEmptyPolicy`): New MRF Policy which will deny empty statuses or statuses of only mentions from being created by local users
|
||||
- New users will receive a simple email confirming their registration if no other emails will be dispatched. (e.g., Welcome, Confirmation, or Approval Required)
|
||||
- Email with missed mentions in a specific period.
|
||||
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
@ -563,10 +563,12 @@ config :pleroma, Oban,
|
||||
remote_fetcher: 2,
|
||||
attachments_cleanup: 1,
|
||||
new_users_digest: 1,
|
||||
mute_expire: 5
|
||||
mute_expire: 5,
|
||||
email_mentions: 1
|
||||
],
|
||||
plugins: [Oban.Plugins.Pruner],
|
||||
crontab: [
|
||||
{"*/15 * * * *", Pleroma.Workers.Cron.EmailMentionsWorker},
|
||||
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
|
||||
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
|
||||
]
|
||||
@ -851,6 +853,10 @@ config :pleroma, ConcurrentLimiter, [
|
||||
{Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]}
|
||||
]
|
||||
|
||||
config :pleroma, Pleroma.Workers.Cron.EmailMentionsWorker,
|
||||
enabled: false,
|
||||
timeframe: 30
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
@ -300,3 +300,36 @@
|
||||
```sh
|
||||
mix pleroma.user unconfirm_all
|
||||
```
|
||||
|
||||
## Update email notifications settings for user
|
||||
|
||||
=== "OTP"
|
||||
|
||||
```sh
|
||||
./bin/pleroma_ctl user email_notifications <nickname> [option ...]
|
||||
```
|
||||
|
||||
=== "From Source"
|
||||
|
||||
```sh
|
||||
mix pleroma.user email_notifications <nickname> [option ...]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `--digest`/`--no-digest` - whether the user should receive digest emails
|
||||
- `--notifications` - what types of email notifications user should receive (can be aliased with `-n`). To disable all types pass `off` value.
|
||||
|
||||
Example:
|
||||
|
||||
=== "OTP"
|
||||
|
||||
```sh
|
||||
./bin/pleroma_ctl user email_notifications lain --digest -n mention
|
||||
```
|
||||
|
||||
=== "From Source"
|
||||
|
||||
```sh
|
||||
mix pleroma.user email_notifications lain --digest -n mention
|
||||
```
|
||||
|
@ -715,6 +715,7 @@ Pleroma has these periodic job workers:
|
||||
|
||||
* `Pleroma.Workers.Cron.DigestEmailsWorker` - digest emails for users with new mentions and follows
|
||||
* `Pleroma.Workers.Cron.NewUsersDigestWorker` - digest emails for admins with new registrations
|
||||
* `Pleroma.Workers.Cron.EmailMentionsWorker` - email with missed mentions notifications in special timeframe
|
||||
|
||||
```elixir
|
||||
config :pleroma, Oban,
|
||||
@ -726,6 +727,7 @@ config :pleroma, Oban,
|
||||
federator_outgoing: 50
|
||||
],
|
||||
crontab: [
|
||||
{"*/15 * * * *", Pleroma.Workers.Cron.EmailMentionsWorker},
|
||||
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
|
||||
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
|
||||
]
|
||||
@ -1154,3 +1156,10 @@ Each job has these settings:
|
||||
|
||||
* `:max_running` - max concurrently runnings jobs
|
||||
* `:max_waiting` - max waiting jobs
|
||||
|
||||
## Mention emails (Pleroma.Workers.Cron.EmailMentionsWorker)
|
||||
|
||||
The worker sends email notifications not read in a certain timeframe.
|
||||
|
||||
* `:enabled` - enables email notifications for missed mentions & chat mentions
|
||||
* `:timeframe` - the period after which the sending of emails begins for missed mentions (in minutes)
|
||||
|
@ -107,6 +107,7 @@ Has these additional fields under the `pleroma` object:
|
||||
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
||||
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
||||
- `favicon`: nullable URL string, Favicon image of the user's instance
|
||||
- `email_notifications`: map with settings for `digest` emails (boolean) and `notifications` setting (list with notification types).
|
||||
|
||||
### Source
|
||||
|
||||
|
@ -21,7 +21,7 @@ defmodule Mix.Tasks.Pleroma.App do
|
||||
|
||||
scopes =
|
||||
if opts[:scopes] do
|
||||
String.split(opts[:scopes], ",")
|
||||
String.split(opts[:scopes], ",", trim: true)
|
||||
else
|
||||
["read", "write", "follow", "push"]
|
||||
end
|
||||
|
@ -433,6 +433,35 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
def run(["email_notifications", nickname | options]) do
|
||||
start_pleroma()
|
||||
|
||||
{opts, _} =
|
||||
OptionParser.parse!(options,
|
||||
strict: [digest: :boolean, notifications: :string],
|
||||
aliases: [n: :notifications]
|
||||
)
|
||||
|
||||
params =
|
||||
Map.new(opts, fn
|
||||
{:digest, v} ->
|
||||
{"digest", v}
|
||||
|
||||
{:notifications, v} ->
|
||||
types = if v == "off", do: [], else: String.split(v, ",", trim: true)
|
||||
{"notifications", types}
|
||||
end)
|
||||
|
||||
with keys when keys != [] <- Map.keys(params),
|
||||
%User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
{:ok, user} = User.update_email_notifications(user, params)
|
||||
shell_info("Email notifications for user #{user.nickname} were successfully updated.")
|
||||
else
|
||||
[] -> shell_error("No changes passed")
|
||||
_ -> shell_error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
defp set_moderator(user, value) do
|
||||
{:ok, user} =
|
||||
user
|
||||
|
@ -8,6 +8,7 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Router
|
||||
@ -120,6 +121,27 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
|> html_body(html_body)
|
||||
end
|
||||
|
||||
defp prepare_mention(%Notification{type: type} = notification, acc)
|
||||
when type in ["mention", "pleroma:chat_mention"] do
|
||||
object = Pleroma.Object.normalize(notification.activity, fetch: false)
|
||||
|
||||
if object do
|
||||
object = update_in(object.data["content"], &format_links/1)
|
||||
|
||||
mention = %{
|
||||
data: notification,
|
||||
object: object,
|
||||
from: User.get_by_ap_id(notification.activity.actor)
|
||||
}
|
||||
|
||||
[mention | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
defp prepare_mention(_, acc), do: acc
|
||||
|
||||
@doc """
|
||||
Email used in digest email notifications
|
||||
Includes Mentions and New Followers data
|
||||
@ -127,25 +149,12 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
"""
|
||||
@spec digest_email(User.t()) :: Swoosh.Email.t() | nil
|
||||
def digest_email(user) do
|
||||
notifications = Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
|
||||
notifications = Notification.for_user_since(user, user.last_digest_emailed_at)
|
||||
|
||||
mentions =
|
||||
notifications
|
||||
|> Enum.filter(&(&1.activity.data["type"] == "Create"))
|
||||
|> Enum.map(fn notification ->
|
||||
object = Pleroma.Object.normalize(notification.activity, fetch: false)
|
||||
|
||||
if not is_nil(object) do
|
||||
object = update_in(object.data["content"], &format_links/1)
|
||||
|
||||
%{
|
||||
data: notification,
|
||||
object: object,
|
||||
from: User.get_by_ap_id(notification.activity.actor)
|
||||
}
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.reduce([], &prepare_mention/2)
|
||||
|
||||
followers =
|
||||
notifications
|
||||
@ -165,7 +174,6 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
|
||||
unless Enum.empty?(mentions) do
|
||||
styling = Config.get([__MODULE__, :styling])
|
||||
logo = Config.get([__MODULE__, :logo])
|
||||
|
||||
html_data = %{
|
||||
instance: instance_name(),
|
||||
@ -176,20 +184,15 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
styling: styling
|
||||
}
|
||||
|
||||
logo_path =
|
||||
if is_nil(logo) do
|
||||
Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
|
||||
else
|
||||
Path.join(Config.get([:instance, :static_dir]), logo)
|
||||
end
|
||||
{logo_path, logo} = logo_path()
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> subject("Your digest from #{instance_name()}")
|
||||
|> put_layout(false)
|
||||
|> render_body("digest.html", html_data)
|
||||
|> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline))
|
||||
|> render_body("digest.html", Map.put(html_data, :logo, logo))
|
||||
|> attachment(Swoosh.Attachment.new(logo_path, filename: logo, type: :inline))
|
||||
end
|
||||
end
|
||||
|
||||
@ -242,4 +245,42 @@ defmodule Pleroma.Emails.UserEmail do
|
||||
|> subject("Your account archive is ready")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
|
||||
@spec mentions_notification_email(User.t(), [Notification.t()]) :: Swoosh.Email.t()
|
||||
def mentions_notification_email(user, mentions) do
|
||||
html_data = %{
|
||||
instance: instance_name(),
|
||||
user: user,
|
||||
mentions: Enum.reduce(mentions, [], &prepare_mention/2),
|
||||
unsubscribe_link: unsubscribe_url(user, "mentions_email"),
|
||||
styling: Config.get([__MODULE__, :styling])
|
||||
}
|
||||
|
||||
now = NaiveDateTime.utc_now()
|
||||
|
||||
{logo_path, logo} = logo_path()
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> subject(
|
||||
"[Pleroma] New mentions from #{instance_name()} for #{
|
||||
Timex.format!(now, "{Mfull} {D}, {YYYY} at {h12}:{m} {AM}")
|
||||
}"
|
||||
)
|
||||
|> put_layout(false)
|
||||
|> render_body("mentions.html", Map.put(html_data, :logo, logo))
|
||||
|> attachment(Swoosh.Attachment.new(logo_path, filename: logo, type: :inline))
|
||||
end
|
||||
|
||||
defp logo_path do
|
||||
logo_path =
|
||||
if logo = Config.get([__MODULE__, :logo]) do
|
||||
Path.join(Config.get([:instance, :static_dir]), logo)
|
||||
else
|
||||
Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg")
|
||||
end
|
||||
|
||||
{logo_path, Path.basename(logo_path)}
|
||||
end
|
||||
end
|
||||
|
@ -11,9 +11,11 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
||||
|
||||
def fill_in_notification_types do
|
||||
query =
|
||||
from(n in Pleroma.Notification,
|
||||
from(n in "notifications",
|
||||
where: is_nil(n.type),
|
||||
preload: :activity
|
||||
join: a in "activities",
|
||||
on: n.activity_id == a.id,
|
||||
select: %{id: n.id, activity: %{id: a.id, data: a.data}}
|
||||
)
|
||||
|
||||
query
|
||||
@ -22,9 +24,8 @@ defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
||||
if notification.activity do
|
||||
type = type_from_activity(notification.activity)
|
||||
|
||||
notification
|
||||
|> Ecto.Changeset.change(%{type: type})
|
||||
|> Repo.update()
|
||||
from(n in "notifications", where: n.id == ^notification.id)
|
||||
|> Repo.update_all(set: [type: type])
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
@ -37,6 +37,7 @@ defmodule Pleroma.Notification do
|
||||
field(:type, :string)
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||
field(:notified_at, :naive_datetime)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
@ -249,7 +250,7 @@ defmodule Pleroma.Notification do
|
||||
iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
|
||||
[]
|
||||
"""
|
||||
@spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
|
||||
@spec for_user_since(User.t(), NaiveDateTime.t()) :: [t()]
|
||||
def for_user_since(user, date) do
|
||||
from(n in for_user_query(user),
|
||||
where: n.updated_at > ^date
|
||||
@ -664,4 +665,48 @@ defmodule Pleroma.Notification do
|
||||
)
|
||||
|> Repo.update_all(set: [seen: true])
|
||||
end
|
||||
|
||||
defp unread_mentions_in_timeframe_query(query \\ __MODULE__, args) do
|
||||
types = args[:types] || ["mention", "pleroma:chat_mention"]
|
||||
max_at = args[:max_at]
|
||||
|
||||
from(n in query,
|
||||
where: n.seen == false,
|
||||
where: is_nil(n.notified_at),
|
||||
where: n.type in ^types,
|
||||
where: n.inserted_at <= ^max_at
|
||||
)
|
||||
end
|
||||
|
||||
@spec users_ids_with_unread_mentions(NaiveDateTime.t()) :: [String.t()]
|
||||
def users_ids_with_unread_mentions(max_at) do
|
||||
from(n in unread_mentions_in_timeframe_query(%{max_at: max_at}),
|
||||
join: u in assoc(n, :user),
|
||||
where: not is_nil(u.email),
|
||||
distinct: n.user_id,
|
||||
select: n.user_id
|
||||
)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec for_user_unread_mentions(User.t(), NaiveDateTime.t()) :: [t()]
|
||||
def for_user_unread_mentions(%User{} = user, max_at) do
|
||||
args = %{
|
||||
max_at: max_at,
|
||||
types: user.email_notifications["notifications"]
|
||||
}
|
||||
|
||||
user
|
||||
|> for_user_query()
|
||||
|> unread_mentions_in_timeframe_query(args)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec update_notified_at([pos_integer()]) :: {non_neg_integer(), nil}
|
||||
def update_notified_at(ids \\ []) do
|
||||
from(n in __MODULE__,
|
||||
where: n.id in ^ids
|
||||
)
|
||||
|> Repo.update_all(set: [notified_at: NaiveDateTime.utc_now()])
|
||||
end
|
||||
end
|
||||
|
@ -131,7 +131,11 @@ defmodule Pleroma.User do
|
||||
field(:hide_followers, :boolean, default: false)
|
||||
field(:hide_follows, :boolean, default: false)
|
||||
field(:hide_favorites, :boolean, default: true)
|
||||
field(:email_notifications, :map, default: %{"digest" => false})
|
||||
|
||||
field(:email_notifications, :map,
|
||||
default: %{"digest" => false, "notifications" => ["mention", "pleroma:chat_mention"]}
|
||||
)
|
||||
|
||||
field(:mascot, :map, default: nil)
|
||||
field(:emoji, :map, default: %{})
|
||||
field(:pleroma_settings_store, :map, default: %{})
|
||||
@ -525,7 +529,8 @@ defmodule Pleroma.User do
|
||||
:is_discoverable,
|
||||
:actor_type,
|
||||
:accepts_chat_messages,
|
||||
:disclose_client
|
||||
:disclose_client,
|
||||
:email_notifications
|
||||
]
|
||||
)
|
||||
|> unique_constraint(:nickname)
|
||||
@ -2390,17 +2395,14 @@ defmodule Pleroma.User do
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
@spec update_email_notifications(t(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def update_email_notifications(user, settings) do
|
||||
email_notifications =
|
||||
user.email_notifications
|
||||
|> Map.merge(settings)
|
||||
|> Map.take(["digest"])
|
||||
email_notifications = Map.merge(user.email_notifications, settings)
|
||||
|
||||
params = %{email_notifications: email_notifications}
|
||||
fields = [:email_notifications]
|
||||
|
||||
user
|
||||
|> cast(params, fields)
|
||||
|> cast(%{email_notifications: email_notifications}, fields)
|
||||
|> validate_required(fields)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
@ -2431,8 +2433,8 @@ defmodule Pleroma.User do
|
||||
end
|
||||
end
|
||||
|
||||
@spec add_to_block(User.t(), User.t()) ::
|
||||
{:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
|
||||
@spec remove_from_block(User.t(), User.t()) ::
|
||||
{:ok, UserRelationship.t() | nil} | {:error, Ecto.Changeset.t()}
|
||||
defp remove_from_block(%User{} = user, %User{} = blocked) do
|
||||
with {:ok, relationship} <- UserRelationship.delete_block(user, blocked) do
|
||||
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
|
||||
|
@ -635,7 +635,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
||||
description:
|
||||
"Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
|
||||
},
|
||||
actor_type: ActorType
|
||||
actor_type: ActorType,
|
||||
email_notifications: email_notifications()
|
||||
},
|
||||
example: %{
|
||||
bot: false,
|
||||
@ -760,6 +761,31 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
||||
}
|
||||
end
|
||||
|
||||
defp email_notifications do
|
||||
%Schema{
|
||||
title: "EmailNotificationsObject",
|
||||
description: "User Email notification settings",
|
||||
type: :object,
|
||||
properties: %{
|
||||
digest: %Schema{
|
||||
allOf: [BooleanLike],
|
||||
nullable: true,
|
||||
description: "Whether the account receives digest email"
|
||||
},
|
||||
notifications: %Schema{
|
||||
type: :array,
|
||||
nullable: true,
|
||||
description: "List of notification types to receive by Email",
|
||||
items: %Schema{type: :string}
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"digest" => true,
|
||||
"notifications" => ["mention", "pleroma:chat_mention"]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp array_of_lists do
|
||||
%Schema{
|
||||
title: "ArrayOfLists",
|
||||
|
@ -213,6 +213,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||
|> Maps.put_if_present(:is_locked, params[:locked])
|
||||
# Note: param name is indeed :discoverable (not an error)
|
||||
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
||||
|> Maps.put_if_present(:email_notifications, params[:email_notifications])
|
||||
|
||||
# What happens here:
|
||||
#
|
||||
|
@ -279,7 +279,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||
skip_thread_containment: user.skip_thread_containment,
|
||||
background_image: image_url(user.background) |> MediaProxy.url(),
|
||||
accepts_chat_messages: user.accepts_chat_messages,
|
||||
favicon: favicon
|
||||
favicon: favicon,
|
||||
email_notifications: user.email_notifications
|
||||
}
|
||||
}
|
||||
|> maybe_put_role(user, opts[:for])
|
||||
|
@ -126,7 +126,7 @@
|
||||
<div align="center" class="img-container center"
|
||||
style="padding-right: 0px;padding-left: 0px;">
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img
|
||||
align="center" alt="Image" border="0" class="center" src="cid:logo.svg"
|
||||
align="center" alt="Image" border="0" class="center" src="cid:<%= @logo %>"
|
||||
style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;"
|
||||
title="Image" height="80" />
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
|
439
lib/pleroma/web/templates/email/mentions.html.eex
Normal file
439
lib/pleroma/web/templates/email/mentions.html.eex
Normal file
@ -0,0 +1,439 @@
|
||||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:v="urn:schemas-microsoft-com:vml">
|
||||
|
||||
<head>
|
||||
<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
|
||||
<meta content="width=device-width" name="viewport" />
|
||||
<!--[if !mso]><!-->
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
|
||||
<!--<![endif]-->
|
||||
<title><%= @email.subject %><</title>
|
||||
<!--[if !mso]><!-->
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
color: <%= @styling.link_color %>;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table,
|
||||
td,
|
||||
tr {
|
||||
vertical-align: top;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
* {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
a[x-apple-data-detectors=true] {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
</style>
|
||||
<style id="media-query" type="text/css">
|
||||
@media (max-width: 610px) {
|
||||
|
||||
.block-grid,
|
||||
.col {
|
||||
min-width: 320px !important;
|
||||
max-width: 100% !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.block-grid {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.col {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.col>div {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.no-stack .col {
|
||||
min-width: 0 !important;
|
||||
display: table-cell !important;
|
||||
}
|
||||
|
||||
.no-stack.two-up .col {
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num4 {
|
||||
width: 33% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num8 {
|
||||
width: 66% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num4 {
|
||||
width: 33% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num3 {
|
||||
width: 25% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num6 {
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num9 {
|
||||
width: 75% !important;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: <%= @styling.background_color %>;">
|
||||
<!--[if IE]><div class="ie-browser"><![endif]-->
|
||||
<table bgcolor="<%= @styling.background_color %>" cellpadding="0" cellspacing="0" class="nl-container" role="presentation"
|
||||
style="table-layout: fixed; vertical-align: top; min-width: 320px; Margin: 0 auto; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: <%= @styling.background_color %>; width: 100%;"
|
||||
valign="top" width="100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top;" valign="top">
|
||||
<td style="word-break: break-word; vertical-align: top;" valign="top">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color:<%= @styling.background_color %>"><![endif]-->
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
|
||||
<!--<![endif]-->
|
||||
<div align="center" class="img-container center"
|
||||
style="padding-right: 0px;padding-left: 0px;">
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img
|
||||
align="center" alt="Image" border="0" class="center" src="cid:<%= @logo %>"
|
||||
style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;"
|
||||
title="Image" height="80" />
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
|
||||
<!--<![endif]-->
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
|
||||
<div
|
||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;">
|
||||
<p style="line-height: 36px; text-align: center; margin: 0;"><span
|
||||
style="font-size: 30px; color: <%= @styling.header_color %>;">Hey <%= @user.nickname %>, here is what you've missed!</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;">
|
||||
<!--<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation"
|
||||
style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
|
||||
valign="top" width="100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top;" valign="top">
|
||||
<td class="divider_inner"
|
||||
style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;"
|
||||
valign="top">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content"
|
||||
height="0" role="presentation"
|
||||
style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;"
|
||||
valign="top" width="100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top;" valign="top">
|
||||
<td height="0"
|
||||
style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
|
||||
valign="top"><span></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
|
||||
<p
|
||||
style="font-size: 12px; line-height: 24px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
|
||||
<span style="font-size: 20px;">Mentions</span></p>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= for %{data: mention, object: object, from: from} <- @mentions do %>
|
||||
<%# mention START %>
|
||||
<%# user card START %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid mixed-two-up no-stack"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="147" style="background-color:<%= @styling.content_background_color%>;width:76px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 20px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num3"
|
||||
style="display: table-cell; vertical-align: top; max-width: 320px; min-width: 76px; width: 76px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 20px;">
|
||||
<!--<![endif]-->
|
||||
<div align="left" class="img-container left "
|
||||
style="padding-right: 0px;padding-left: 0px;">
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="left"><![endif]--><img
|
||||
alt="<%= from.name %>" border="0" class="left " src="<%= avatar_url(from) %>"
|
||||
style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: auto; width: 100%; max-width: 76px; display: block;"
|
||||
title="<%= from.name %>" width="76" />
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td><td align="center" width="442" style="background-color:<%= @styling.content_background_color%>;width:442px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num9"
|
||||
style="display: table-cell; vertical-align: top; min-width: 320px; max-width: 441px; width: 442px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
|
||||
<!--<![endif]-->
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
|
||||
<div
|
||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
|
||||
<p style="font-size: 14px; line-height: 19px; margin: 0;"><span
|
||||
style="font-size: 16px; color: <%= @styling.text_color %>;"><%= from.name %></span></p>
|
||||
<p style="font-size: 14px; line-height: 19px; margin: 0;"><span
|
||||
style="font-size: 16px;"><%= link "@" <> from.nickname, style: "color: #{@styling.link_color};text-decoration: none;", to: mention.activity.actor %></span></p>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%# user card END %>
|
||||
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;">
|
||||
<!--<![endif]-->
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
|
||||
<div
|
||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
|
||||
<span style="font-size: 16px; line-height: 19px;"><%= raw object.data["content"] %></span></div>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 15px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="color:<%= @styling.text_muted_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:15px;">
|
||||
<div
|
||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_muted_color %>;">
|
||||
<p style="font-size: 14px; line-height: 16px; margin: 0;"><%= format_date object.data["published"] %></p>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%# mention END %>
|
||||
<% end %>
|
||||
|
||||
<%# divider start %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
|
||||
<!--<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation"
|
||||
style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
|
||||
valign="top" width="100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top;" valign="top">
|
||||
<td class="divider_inner"
|
||||
style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;"
|
||||
valign="top">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content"
|
||||
height="0" role="presentation"
|
||||
style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;"
|
||||
valign="top" width="100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top;" valign="top">
|
||||
<td height="0"
|
||||
style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
|
||||
valign="top"><span></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# divider end %>
|
||||
|
||||
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
|
||||
<!--<![endif]-->
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
|
||||
<p
|
||||
style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
|
||||
<span style="font-size: 14px;">You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</span></p>
|
||||
<p
|
||||
style="font-size: 12px; line-height: 14px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
|
||||
</p>
|
||||
<p
|
||||
style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
|
||||
<span style="font-size: 14px;">The email address you are subscribed as is <a href="mailto:<%= @user.email %>" style="color: <%= @styling.link_color %>;text-decoration: none;"><%= @user.email %></a>. </span></p>
|
||||
<p
|
||||
style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;">
|
||||
<span style="font-size: 14px;">To unsubscribe, please go <%= link "here", style: "color: #{@styling.link_color};text-decoration: none;", to: @unsubscribe_link %>.</span></p>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if (IE)]></div><![endif]-->
|
||||
</body>
|
||||
|
||||
</html>
|
67
lib/pleroma/workers/cron/email_mentions_worker.ex
Normal file
67
lib/pleroma/workers/cron/email_mentions_worker.ex
Normal file
@ -0,0 +1,67 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.Cron.EmailMentionsWorker do
|
||||
use Pleroma.Workers.WorkerHelper, queue: "email_mentions"
|
||||
|
||||
@impl true
|
||||
def perform(%Job{args: %{"op" => "email_mentions", "user_id" => id}}) do
|
||||
user = Pleroma.User.get_cached_by_id(id)
|
||||
|
||||
timeframe =
|
||||
Pleroma.Config.get([__MODULE__, :timeframe], 30)
|
||||
|> :timer.minutes()
|
||||
|
||||
max_inserted_at =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(-timeframe, :millisecond)
|
||||
|> NaiveDateTime.truncate(:second)
|
||||
|
||||
mentions = Pleroma.Notification.for_user_unread_mentions(user, max_inserted_at)
|
||||
|
||||
if mentions != [] do
|
||||
user
|
||||
|> Pleroma.Emails.UserEmail.mentions_notification_email(mentions)
|
||||
|> Pleroma.Emails.Mailer.deliver()
|
||||
|> case do
|
||||
{:ok, _} ->
|
||||
Enum.map(mentions, & &1.id)
|
||||
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
|> Pleroma.Notification.update_notified_at()
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@impl true
|
||||
def perform(_) do
|
||||
config = Pleroma.Config.get(__MODULE__, [])
|
||||
|
||||
if Keyword.get(config, :enabled, false) do
|
||||
timeframe = Keyword.get(config, :timeframe, 30)
|
||||
period = timeframe * 60
|
||||
|
||||
max_at =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(-:timer.minutes(timeframe), :millisecond)
|
||||
|> NaiveDateTime.truncate(:second)
|
||||
|
||||
Pleroma.Notification.users_ids_with_unread_mentions(max_at)
|
||||
|> Enum.each(&insert_job(&1, unique: [period: period]))
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp insert_job(user_id, args) do
|
||||
Pleroma.Workers.Cron.EmailMentionsWorker.enqueue(
|
||||
"email_mentions",
|
||||
%{"user_id" => user_id},
|
||||
args
|
||||
)
|
||||
end
|
||||
end
|
@ -0,0 +1,32 @@
|
||||
defmodule Pleroma.Repo.Migrations.ChangeUserEmailNotificationsSetting do
|
||||
use Ecto.Migration
|
||||
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
def up, do: stream_and_update_users(:up)
|
||||
|
||||
def down, do: stream_and_update_users(:down)
|
||||
|
||||
defp stream_and_update_users(direction) do
|
||||
from(u in Pleroma.User, select: [:id, :email_notifications])
|
||||
|> Pleroma.Repo.stream()
|
||||
|> Stream.each(&update_user_email_notifications_settings(&1, direction))
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
defp update_user_email_notifications_settings(user, direction) do
|
||||
email_notifications = change_email_notifications(user.email_notifications, direction)
|
||||
|
||||
user
|
||||
|> Ecto.Changeset.change(email_notifications: email_notifications)
|
||||
|> Pleroma.Repo.update()
|
||||
end
|
||||
|
||||
defp change_email_notifications(email_notifications, :up) do
|
||||
Map.put(email_notifications, "notifications", ["mention", "pleroma:chat_mention"])
|
||||
end
|
||||
|
||||
defp change_email_notifications(email_notifications, :down) do
|
||||
Map.delete(email_notifications, "notifications")
|
||||
end
|
||||
end
|
@ -0,0 +1,15 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddNotifiedAtToNotifications do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:notifications) do
|
||||
add_if_not_exists(:notified_at, :naive_datetime)
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:notifications) do
|
||||
remove_if_exists(:notified_at, :naive_datetime)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,23 @@
|
||||
defmodule Pleroma.Repo.Migrations.FillNotificationsNotifiedAt do
|
||||
use Ecto.Migration
|
||||
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
@types ["mention", "pleroma:chat_mention"]
|
||||
|
||||
def up do
|
||||
from(n in "notifications",
|
||||
where: is_nil(n.notified_at),
|
||||
where: n.type in ^@types
|
||||
)
|
||||
|> Pleroma.Repo.update_all(set: [notified_at: NaiveDateTime.utc_now()])
|
||||
end
|
||||
|
||||
def down do
|
||||
from(n in "notifications",
|
||||
where: not is_nil(n.notified_at),
|
||||
where: n.type in ^@types
|
||||
)
|
||||
|> Pleroma.Repo.update_all(set: [notified_at: nil])
|
||||
end
|
||||
end
|
@ -0,0 +1,7 @@
|
||||
defmodule Pleroma.Repo.Migrations.AddIndexToNotifications do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create_if_not_exists(index(:notifications, [:seen, :notified_at, :type, :inserted_at]))
|
||||
end
|
||||
end
|
@ -617,4 +617,93 @@ defmodule Mix.Tasks.Pleroma.UserTest do
|
||||
assert mod.is_confirmed
|
||||
end
|
||||
end
|
||||
|
||||
describe "email_notifications" do
|
||||
setup do
|
||||
user = insert(:user, email_notifications: %{"digest" => false, "notifications" => []})
|
||||
[user: user]
|
||||
end
|
||||
|
||||
test "no changes error", %{user: user} do
|
||||
Mix.Tasks.Pleroma.User.run(["email_notifications", user.nickname])
|
||||
|
||||
assert_received {:mix_shell, :error, ["No changes passed"]}
|
||||
end
|
||||
|
||||
test "user not found" do
|
||||
Mix.Tasks.Pleroma.User.run(["email_notifications", "nickname", "--digest"])
|
||||
|
||||
assert_received {:mix_shell, :error, ["No local user nickname"]}
|
||||
end
|
||||
|
||||
test "all settings", %{user: user} do
|
||||
assert user.email_notifications == %{"digest" => false, "notifications" => []}
|
||||
|
||||
Mix.Tasks.Pleroma.User.run([
|
||||
"email_notifications",
|
||||
user.nickname,
|
||||
"--digest",
|
||||
"-n",
|
||||
"mention,pleroma:chat_mention,"
|
||||
])
|
||||
|
||||
from_db = User.get_cached_by_nickname(user.nickname)
|
||||
|
||||
assert from_db.email_notifications == %{
|
||||
"digest" => true,
|
||||
"notifications" => ["mention", "pleroma:chat_mention"]
|
||||
}
|
||||
|
||||
Mix.Tasks.Pleroma.User.run([
|
||||
"email_notifications",
|
||||
user.nickname,
|
||||
"--no-digest",
|
||||
"-n",
|
||||
"off"
|
||||
])
|
||||
|
||||
from_db = User.get_cached_by_nickname(user.nickname)
|
||||
assert from_db.email_notifications == %{"digest" => false, "notifications" => []}
|
||||
end
|
||||
|
||||
test "partial update", %{user: user} do
|
||||
Mix.Tasks.Pleroma.User.run([
|
||||
"email_notifications",
|
||||
user.nickname,
|
||||
"--digest"
|
||||
])
|
||||
|
||||
from_db = User.get_cached_by_nickname(user.nickname)
|
||||
assert from_db.email_notifications == %{"digest" => true, "notifications" => []}
|
||||
|
||||
Mix.Tasks.Pleroma.User.run([
|
||||
"email_notifications",
|
||||
user.nickname,
|
||||
"--no-digest"
|
||||
])
|
||||
|
||||
from_db = User.get_cached_by_nickname(user.nickname)
|
||||
assert from_db.email_notifications == %{"digest" => false, "notifications" => []}
|
||||
|
||||
Mix.Tasks.Pleroma.User.run([
|
||||
"email_notifications",
|
||||
user.nickname,
|
||||
"-n",
|
||||
"mention"
|
||||
])
|
||||
|
||||
from_db = User.get_cached_by_nickname(user.nickname)
|
||||
assert from_db.email_notifications == %{"digest" => false, "notifications" => ["mention"]}
|
||||
|
||||
Mix.Tasks.Pleroma.User.run([
|
||||
"email_notifications",
|
||||
user.nickname,
|
||||
"-n",
|
||||
"off"
|
||||
])
|
||||
|
||||
from_db = User.get_cached_by_nickname(user.nickname)
|
||||
assert from_db.email_notifications == %{"digest" => false, "notifications" => []}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1155,4 +1155,94 @@ defmodule Pleroma.NotificationTest do
|
||||
assert length(Notification.for_user(user)) == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "users_ids_with_unread_mentions/0" do
|
||||
setup do
|
||||
now = NaiveDateTime.utc_now()
|
||||
inserted_at = NaiveDateTime.add(now, -61)
|
||||
|
||||
insert(:notification, seen: true, type: "mention", inserted_at: inserted_at)
|
||||
insert(:notification, type: "follow", inserted_at: inserted_at)
|
||||
insert(:notification, type: "mention")
|
||||
mention = insert(:notification, type: "mention", inserted_at: inserted_at)
|
||||
chat_mention = insert(:notification, type: "pleroma:chat_mention", inserted_at: inserted_at)
|
||||
|
||||
insert(:notification,
|
||||
type: "mention",
|
||||
notified_at: now,
|
||||
inserted_at: inserted_at
|
||||
)
|
||||
|
||||
[
|
||||
mention: mention,
|
||||
chat_mention: chat_mention,
|
||||
now: now
|
||||
]
|
||||
end
|
||||
|
||||
test "when mentions are in the timeframe", %{
|
||||
mention: mention,
|
||||
chat_mention: chat_mention,
|
||||
now: now
|
||||
} do
|
||||
assert Notification.users_ids_with_unread_mentions(NaiveDateTime.add(now, -60)) == [
|
||||
mention.user_id,
|
||||
chat_mention.user_id
|
||||
]
|
||||
end
|
||||
|
||||
test "when mentions are out of the timeframe", %{now: now} do
|
||||
assert Notification.users_ids_with_unread_mentions(NaiveDateTime.add(now, -62)) == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "for_user_unread_mentions/1" do
|
||||
setup do
|
||||
[user, muted, blocked] = insert_list(3, :user)
|
||||
{:ok, _} = User.mute(user, muted)
|
||||
{:ok, _} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"})
|
||||
{:ok, _} = User.block(user, blocked)
|
||||
{:ok, _} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"})
|
||||
|
||||
insert(:notification, type: "mention", user: user)
|
||||
insert(:notification, type: "pleroma:chat_mention", user: user)
|
||||
|
||||
inserted_at = NaiveDateTime.add(NaiveDateTime.utc_now(), -61)
|
||||
Repo.update_all(Notification, set: [inserted_at: inserted_at])
|
||||
[user: user, max: NaiveDateTime.add(NaiveDateTime.utc_now(), -60)]
|
||||
end
|
||||
|
||||
test "when mentions are in timeframe, exclude blocks and mutes", %{user: user, max: max} do
|
||||
assert Repo.aggregate(Notification, :count, :id) == 4
|
||||
assert user |> Notification.for_user_unread_mentions(max) |> length() == 2
|
||||
end
|
||||
|
||||
test "when mentions are out of the timeframe, exclude blocks and mutes", %{
|
||||
user: user,
|
||||
max: max
|
||||
} do
|
||||
assert Notification.for_user_unread_mentions(user, NaiveDateTime.add(max, -2)) == []
|
||||
end
|
||||
|
||||
test "respect user notification types", %{user: user, max: max} do
|
||||
user =
|
||||
Map.update!(
|
||||
user,
|
||||
:email_notifications,
|
||||
&Map.put(&1, "notifications", ["pleroma:chat_mention"])
|
||||
)
|
||||
|
||||
[mention] = Notification.for_user_unread_mentions(user, max)
|
||||
assert mention.type == "pleroma:chat_mention"
|
||||
end
|
||||
end
|
||||
|
||||
test "update_notified_at/1" do
|
||||
notifs = insert_list(2, :notification)
|
||||
|
||||
assert {2, nil} =
|
||||
notifs
|
||||
|> Enum.map(& &1.id)
|
||||
|> Notification.update_notified_at()
|
||||
end
|
||||
end
|
||||
|
@ -2239,18 +2239,19 @@ defmodule Pleroma.UserTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_email_notifications/2" do
|
||||
setup do
|
||||
user = insert(:user, email_notifications: %{"digest" => true})
|
||||
test "update_email_notifications/2" do
|
||||
user = insert(:user, email_notifications: %{"digest" => false, "notifications" => []})
|
||||
assert user.email_notifications["digest"] == false
|
||||
assert user.email_notifications["notifications"] == []
|
||||
|
||||
{:ok, user: user}
|
||||
end
|
||||
assert {:ok, result} =
|
||||
User.update_email_notifications(user, %{
|
||||
"digest" => true,
|
||||
"notifications" => ["mention", "pleroma:chat_mention"]
|
||||
})
|
||||
|
||||
test "Notifications are updated", %{user: user} do
|
||||
true = user.email_notifications["digest"]
|
||||
assert {:ok, result} = User.update_email_notifications(user, %{"digest" => false})
|
||||
assert result.email_notifications["digest"] == false
|
||||
end
|
||||
assert result.email_notifications["digest"]
|
||||
assert result.email_notifications["notifications"] == ["mention", "pleroma:chat_mention"]
|
||||
end
|
||||
|
||||
describe "local_nickname/1" do
|
||||
|
@ -206,6 +206,38 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do
|
||||
assert user_data["source"]["pleroma"]["no_rich_text"] == true
|
||||
end
|
||||
|
||||
test "updates the user's email_notifications setting", %{conn: conn} do
|
||||
resp =
|
||||
patch(conn, "/api/v1/accounts/update_credentials", %{
|
||||
email_notifications: %{
|
||||
"digest" => true,
|
||||
"notifications" => []
|
||||
}
|
||||
})
|
||||
|
||||
assert user_data = json_response_and_validate_schema(resp, 200)
|
||||
|
||||
assert user_data["pleroma"]["email_notifications"] == %{
|
||||
"digest" => true,
|
||||
"notifications" => []
|
||||
}
|
||||
|
||||
resp =
|
||||
patch(conn, "/api/v1/accounts/update_credentials", %{
|
||||
email_notifications: %{
|
||||
"digest" => false,
|
||||
"notifications" => ["mention", "pleroma:chat_mention"]
|
||||
}
|
||||
})
|
||||
|
||||
assert user_data = json_response_and_validate_schema(resp, 200)
|
||||
|
||||
assert user_data["pleroma"]["email_notifications"] == %{
|
||||
"digest" => false,
|
||||
"notifications" => ["mention", "pleroma:chat_mention"]
|
||||
}
|
||||
end
|
||||
|
||||
test "updates the user's name", %{conn: conn} do
|
||||
conn =
|
||||
patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
|
||||
|
@ -90,7 +90,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
||||
hide_follows_count: false,
|
||||
relationship: %{},
|
||||
skip_thread_containment: false,
|
||||
accepts_chat_messages: nil
|
||||
accepts_chat_messages: nil,
|
||||
email_notifications: %{
|
||||
"digest" => false,
|
||||
"notifications" => ["mention", "pleroma:chat_mention"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,7 +194,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
||||
hide_follows_count: false,
|
||||
relationship: %{},
|
||||
skip_thread_containment: false,
|
||||
accepts_chat_messages: nil
|
||||
accepts_chat_messages: nil,
|
||||
email_notifications: %{
|
||||
"digest" => false,
|
||||
"notifications" => ["mention", "pleroma:chat_mention"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
107
test/pleroma/workers/cron/email_mentions_worker_test.exs
Normal file
107
test/pleroma/workers/cron/email_mentions_worker_test.exs
Normal file
@ -0,0 +1,107 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.Cron.EmailMentionsWorkerTest do
|
||||
use Pleroma.DataCase
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
|
||||
import Pleroma.Factory
|
||||
import Swoosh.TestAssertions
|
||||
|
||||
alias Pleroma.Workers.Cron.EmailMentionsWorker
|
||||
|
||||
setup do
|
||||
clear_config(EmailMentionsWorker, enabled: true, timeframe: 1)
|
||||
inserted_at = NaiveDateTime.add(NaiveDateTime.utc_now(), -61)
|
||||
|
||||
n1 = insert(:notification, seen: true, type: "mention", inserted_at: inserted_at)
|
||||
n2 = insert(:notification, type: "follow", inserted_at: inserted_at)
|
||||
n3 = insert(:notification, type: "mention")
|
||||
mention = insert(:notification, type: "mention", inserted_at: inserted_at)
|
||||
chat_mention = insert(:notification, type: "pleroma:chat_mention", inserted_at: inserted_at)
|
||||
|
||||
n4 =
|
||||
insert(:notification,
|
||||
type: "mention",
|
||||
notified_at: NaiveDateTime.utc_now(),
|
||||
inserted_at: inserted_at
|
||||
)
|
||||
|
||||
[
|
||||
mention: mention,
|
||||
chat_mention: chat_mention,
|
||||
other_user_ids: [n1.user_id, n2.user_id, n3.user_id, n4.user_id]
|
||||
]
|
||||
end
|
||||
|
||||
test "creates jobs for users", %{
|
||||
mention: mention,
|
||||
chat_mention: chat_mention,
|
||||
other_user_ids: ids
|
||||
} do
|
||||
assert EmailMentionsWorker.perform(%{}) == :ok
|
||||
|
||||
assert_enqueued(
|
||||
worker: EmailMentionsWorker,
|
||||
args: %{op: "email_mentions", user_id: mention.user_id}
|
||||
)
|
||||
|
||||
assert_enqueued(
|
||||
worker: EmailMentionsWorker,
|
||||
args: %{op: "email_mentions", user_id: chat_mention.user_id}
|
||||
)
|
||||
|
||||
Enum.each(ids, fn id ->
|
||||
refute_enqueued(worker: EmailMentionsWorker, args: %{op: "email_mentions", user_id: id})
|
||||
end)
|
||||
|
||||
assert Repo.aggregate(Oban.Job, :count, :id) == 2
|
||||
|
||||
EmailMentionsWorker.perform(%{})
|
||||
|
||||
# no duplicates
|
||||
assert Repo.aggregate(Oban.Job, :count, :id) == 2
|
||||
end
|
||||
|
||||
test "doesn't create jobs for users without emails", %{mention: mention} do
|
||||
%{user: user} = Repo.preload(mention, :user)
|
||||
|
||||
user
|
||||
|> Ecto.Changeset.change(email: nil)
|
||||
|> Repo.update()
|
||||
|
||||
assert EmailMentionsWorker.perform(%{}) == :ok
|
||||
|
||||
refute_enqueued(
|
||||
worker: EmailMentionsWorker,
|
||||
args: %{op: "email_mentions", user_id: mention.user_id}
|
||||
)
|
||||
end
|
||||
|
||||
test "sends emails", %{mention: mention, chat_mention: chat_mention} do
|
||||
assert EmailMentionsWorker.perform(%{}) == :ok
|
||||
|
||||
mention = Repo.preload(mention, :user)
|
||||
|
||||
assert EmailMentionsWorker.perform(%Oban.Job{
|
||||
args: %{"op" => "email_mentions", "user_id" => mention.user_id}
|
||||
}) == :ok
|
||||
|
||||
assert_email_sent(
|
||||
to: {mention.user.name, mention.user.email},
|
||||
html_body: ~r/here is what you've missed!/i
|
||||
)
|
||||
|
||||
chat_mention = Repo.preload(chat_mention, :user)
|
||||
|
||||
assert EmailMentionsWorker.perform(%Oban.Job{
|
||||
args: %{"op" => "email_mentions", "user_id" => chat_mention.user_id}
|
||||
}) == :ok
|
||||
|
||||
assert_email_sent(
|
||||
to: {chat_mention.user.name, chat_mention.user.email},
|
||||
html_body: ~r/here is what you've missed!/i
|
||||
)
|
||||
end
|
||||
end
|
@ -469,7 +469,8 @@ defmodule Pleroma.Factory do
|
||||
|
||||
def notification_factory do
|
||||
%Pleroma.Notification{
|
||||
user: build(:user)
|
||||
user: build(:user),
|
||||
activity: build(:note_activity)
|
||||
}
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user