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