1
0
Fork 0
mobilizon/lib/service/workers/send_activity_recap_worker.ex
Thomas Citharel 4213e1f1ec
Send activity recap emails outside of the transaction
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2022-05-11 11:24:20 +02:00

137 lines
4.2 KiB
Elixir

defmodule Mobilizon.Service.Workers.SendActivityRecapWorker do
@moduledoc """
Worker to send activity recaps
"""
use Oban.Worker, queue: "notifications"
alias Mobilizon.{Activities, Actors, Users}
alias Mobilizon.Activities.Activity
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Notifier.Email
alias Mobilizon.Storage.Repo
alias Mobilizon.Users.{Setting, User}
require Logger
import Mobilizon.Service.DateTime,
only: [
is_between_hours?: 1,
is_between_hours_on_first_day?: 1,
is_delay_ok_since_last_notification_sent?: 1
]
@impl Oban.Worker
def perform(%Job{}) do
Logger.info("Sending scheduled activity recap")
case Repo.transaction(&produce_notifications/0, timeout: :infinity) do
{:ok, res} ->
Logger.info("Processed #{length(res)} notifications to send")
Enum.each(res, fn %{
activities: activities,
user:
%User{settings: %Setting{group_notifications: group_notifications}} =
user
} ->
Logger.info(
"Asking to send email notification #{group_notifications} to user #{user.email} for #{length(activities)} activities"
)
Email.send(user, activities, recap: group_notifications)
end)
{:error, err} ->
Logger.error("Error producing notifications #{inspect(err)}")
{:error, err}
end
end
defp produce_notifications do
Users.stream_users_for_recap()
|> Enum.to_list()
|> Repo.preload([:settings, :activity_settings])
|> Enum.filter(&filter_elegible_users/1)
|> Enum.map(fn %User{} = user ->
%{
activities: activities_for_user(user),
user: user
}
end)
|> Enum.filter(fn %{activities: activities, user: _user} -> length(activities) > 0 end)
end
defp activities_for_user(
%User{settings: %Setting{last_notification_sent: last_notification_sent}} = user
) do
user
|> Users.get_actors_for_user()
|> Enum.flat_map(&group_memberships(&1, last_notification_sent))
|> Enum.uniq()
end
defp group_memberships(%Actor{id: actor_id} = actor, last_notification_sent) do
actor
|> group_memberships_for_actor()
|> Enum.uniq()
|> Enum.flat_map(&activities_for_group(&1, actor_id, last_notification_sent))
end
defp group_memberships_for_actor(%Actor{} = actor) do
Actors.list_groups_member_of(actor)
end
defp activities_for_group(
%Actor{id: group_id, type: :Group},
actor_asking_id,
last_notification_sent
) do
group_id
|> Activities.list_group_activities_for_recap(actor_asking_id, last_notification_sent)
# Don't send my own activities
|> Enum.filter(fn %Activity{author: %Actor{id: author_id}} -> author_id != actor_asking_id end)
end
defp filter_elegible_users(%User{
settings: %Setting{last_notification_sent: nil, group_notifications: :one_hour}
}) do
Logger.debug("Sending because never sent before, and we must do it at most once an hour")
true
end
defp filter_elegible_users(%User{
settings: %Setting{
last_notification_sent: %DateTime{} = last_notification_sent,
group_notifications: :one_hour
}
}) do
Logger.debug(
"Testing if it's less than an hour since the last time we sent an activity recap"
)
is_delay_ok_since_last_notification_sent?(last_notification_sent)
end
# If we're between notification hours
defp filter_elegible_users(%User{
settings: %Setting{
group_notifications: :one_day,
timezone: timezone
}
}) do
Logger.debug("Testing if we're between daily sending hours")
is_between_hours?(timezone: timezone || "Etc/UTC")
end
# If we're on the first day of the week between notification hours
defp filter_elegible_users(%User{
locale: locale,
settings: %Setting{
group_notifications: :one_week,
timezone: timezone
}
}) do
Logger.debug("Testing if we're between weekly sending day and hours")
is_between_hours_on_first_day?(timezone: timezone || "Etc/UTC", locale: locale)
end
end