class Expired::UsersDeletionService < Expired::MailRateLimiter def process_expired # we are working on two dataset because we apply two incompatible join on the same query # inner join on users not having dossier.en_instruction [so we do not destroy users with dossiers.en_instruction] # outer join on users not having dossier at all [so we destroy users without dossiers] [expired_users_without_dossiers, expired_users_with_dossiers].each do |expired_segment| delete_notified_users(expired_segment) send_inactive_close_to_expiration_notice(expired_segment) end end private # in case of perf downside : # consider using perform_all_later # consider changing notify_inactive_close_to_deletion method, taking a user_id, and updating inactive_close_to_expiration_notice_sent_at def send_inactive_close_to_expiration_notice(users) user_ids = to_notify_only(users).pluck(:id) user_ids.each do |user_id| send_with_delay(UserMailer.notify_inactive_close_to_deletion(User.find(user_id))) end User.where(id: user_ids).update_all(inactive_close_to_expiration_notice_sent_at: Time.zone.now.utc) end def delete_notified_users(users) user_ids = only_notified(users).pluck(:id) user_ids.each do |user_id| user = User.find(user_id) begin user.delete_and_keep_track_dossiers_also_delete_user(nil, reason: :user_expired) rescue => e Sentry.capture_exception(e, extra: { user_id: user.id }) end end end # rubocop:disable DS/Unscoped def expired_users_with_dossiers dossiers = Dossier.arel_table users = User.arel_table expired_users .joins( users.join(dossiers, Arel::Nodes::OuterJoin) .on(users[:id].eq(dossiers[:user_id]) .and(dossiers[:state].eq(Dossier.states.fetch(:en_instruction)))) .join_sources ) .where(dossiers[:id].eq(nil)) .group("users.id") end def expired_users_without_dossiers expired_users.where.missing(:dossiers) end def expired_users User.unscoped .where.missing(:expert, :instructeur, :administrateur) .where(last_sign_in_at: ..Expired::INACTIVE_USER_RETATION_IN_YEAR.years.ago) end # rubocop:enable DS/Unscoped def to_notify_only(users) users.where(inactive_close_to_expiration_notice_sent_at: nil) .limit(daily_limit) # ensure to not send too much email end def only_notified(users) users.where.not(inactive_close_to_expiration_notice_sent_at: Expired::REMAINING_WEEKS_BEFORE_EXPIRATION.weeks.ago..) .limit(daily_limit) # event if we do not send email, avoid to destroy 800k user in one batch end def daily_limit (ENV['EXPIRE_USER_DELETION_JOB_LIMIT'] || 10_000).to_i end end