From a0e69bdb4a55073e7d1098487c7383b4bb6f7fda Mon Sep 17 00:00:00 2001 From: Rick Carlino Date: Wed, 20 Nov 2019 17:49:39 -0600 Subject: [PATCH] Automatically delete old accounts after 14 days. TODO: Tests --- app/controllers/api/abstract_controller.rb | 1 + app/jobs/inactive_account_job.rb | 55 +++++++++++++++++++ app/mailers/inactivity_mailer.rb | 11 ++++ app/models/user.rb | 22 ++++++++ .../inactivity_mailer/send_warning.html.erb | 3 + ...19204916_add_inactivity_fields_to_users.rb | 5 ++ db/structure.sql | 6 +- 7 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 app/jobs/inactive_account_job.rb create mode 100644 app/mailers/inactivity_mailer.rb create mode 100644 app/views/inactivity_mailer/send_warning.html.erb create mode 100644 db/migrate/20191119204916_add_inactivity_fields_to_users.rb diff --git a/app/controllers/api/abstract_controller.rb b/app/controllers/api/abstract_controller.rb index a3fdaa4b5..4fc71cf3f 100644 --- a/app/controllers/api/abstract_controller.rb +++ b/app/controllers/api/abstract_controller.rb @@ -213,6 +213,7 @@ module Api # Devices have a `last_saw_api` field to assist users with debugging. # We update this column every time an FBOS device talks to the API. def mark_as_seen(bot = (current_user && current_user.device)) + current_user && current_user.reset_inactivity_tracking! when_farmbot_os do if bot v = fbos_version diff --git a/app/jobs/inactive_account_job.rb b/app/jobs/inactive_account_job.rb new file mode 100644 index 000000000..e8ad9e1ee --- /dev/null +++ b/app/jobs/inactive_account_job.rb @@ -0,0 +1,55 @@ +# Recurring task that deletes inactive accounts. +class InactiveAccountJob < ApplicationJob + queue_as :default + LIMIT = 1000 + INACTIVE_WITH_DEVICE = 11.months + 15.days + INACTIVE_NO_DEVICE = 2.months + 15.days + + def perform + notify_old_accounts + delete_old_accounts + end + + private + + def notify_old_accounts + all_inactive + .where(inactivity_warning_sent_at: nil) + .map(&:send_inactivity_warning) + end + + def delete_old_accounts + all_inactive + .where + .not(inactivity_warning_sent_at: nil) + .where("inactivity_warning_sent_at < ?", 14.days.ago) + .map(&:deactivate_account) + end + + # Returns a Map. Key is the number of warnings sent, value is a User object + # (not a device, but device is preloaded) + def all_inactive + return @all_inactive if @all_inactive + users = User.includes(:device) + + # They signed up for an account, but never configured a device. + no_device = users + .where("devices.fbos_version" => nil) + .references(:devices) + + # They signed up for an account and once had a working device. + ok_device = users + .where + .not("devices.fbos_version" => nil) + .references(:devices) + + inactive_3mo = no_device + .where("last_sign_in_at < ?", INACTIVE_NO_DEVICE.ago) + inactive_11mo = ok_device + .where("last_sign_in_at < ?", INACTIVE_WITH_DEVICE.ago) + @all_inactive = inactive_11mo + .or(inactive_3mo) + .order("RANDOM()") + .limit(LIMIT) + end +end diff --git a/app/mailers/inactivity_mailer.rb b/app/mailers/inactivity_mailer.rb new file mode 100644 index 000000000..a85dc8d33 --- /dev/null +++ b/app/mailers/inactivity_mailer.rb @@ -0,0 +1,11 @@ +class InactivityMailer < ApplicationMailer + attr_reader :user + + SUBJECT = "Your FarmBot Account Will Be Deleted Due to Inactivity" + ORDER = { 1 => "First", 2 => "Second", 3 => "Final" } + + def send_warning(user) + @user = user + mail to: user.email, subject: SUBJECT + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 3c715fe0e..3f659ead3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -54,4 +54,26 @@ class User < ApplicationRecord .current .raw_amqp_send(msg.to_json, Api::RmqUtilsController::PUBLIC_BROADCAST) end + + # The web app deletes account that go inactive for long periods. + # It is called when the user logs in to the app. + def reset_inactivity_tracking! + update!(inactivity_warning_sent_at: nil) + end + + def send_inactivity_warning + User.transaction do + update!(inactivity_warning_sent_at: Time.now) + InactivityMailer.send_warning(self).deliver_later + end + end + + def deactivate_account + User.transaction do + raise "HALTING ERRONEOUS DELETION" if last_sign_in_at > 3.months.ago + # Prevent double deletion / race conditions. + u.update!(last_sign_in_at: Time.now, inactivity_warning_sent_at: nil) + u.delay.destroy! + end + end end diff --git a/app/views/inactivity_mailer/send_warning.html.erb b/app/views/inactivity_mailer/send_warning.html.erb new file mode 100644 index 000000000..f3bfe5da8 --- /dev/null +++ b/app/views/inactivity_mailer/send_warning.html.erb @@ -0,0 +1,3 @@ +You have not logged in <%= time_ago_in_words(user.last_sign_in_at) %>. Your account will be automatically deleted after 14 days of inactivity. + +To halt the deletion process, please log in to your account. diff --git a/db/migrate/20191119204916_add_inactivity_fields_to_users.rb b/db/migrate/20191119204916_add_inactivity_fields_to_users.rb new file mode 100644 index 000000000..05d3c0592 --- /dev/null +++ b/db/migrate/20191119204916_add_inactivity_fields_to_users.rb @@ -0,0 +1,5 @@ +class AddInactivityFieldsToUsers < ActiveRecord::Migration[6.0] + def change + add_column :users, :inactivity_warning_sent_at, :datetime + end +end diff --git a/db/structure.sql b/db/structure.sql index 01572be0b..72bcb61d7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1648,7 +1648,8 @@ CREATE TABLE public.users ( confirmation_token character varying, agreed_to_terms_at timestamp without time zone, confirmation_sent_at timestamp without time zone, - unconfirmed_email character varying + unconfirmed_email character varying, + inactivity_warning_sent_at timestamp without time zone ); @@ -3366,6 +3367,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20190924190539'), ('20190930202839'), ('20191002125625'), -('20191107170431'); +('20191107170431'), +('20191119204916');