diff --git a/app/jobs/scheduled/clean_up_inactive_users.rb b/app/jobs/scheduled/clean_up_inactive_users.rb new file mode 100644 index 00000000000..801f05c4fb6 --- /dev/null +++ b/app/jobs/scheduled/clean_up_inactive_users.rb @@ -0,0 +1,29 @@ +module Jobs + + class CleanUpInactiveUsers < Jobs::Scheduled + every 1.day + + def execute(args) + return if SiteSetting.clean_up_inactive_users_after_days <= 0 + + destroyer = UserDestroyer.new(Discourse.system_user) + + User.joins("LEFT JOIN posts ON posts.user_id = users.id") + .where(last_posted_at: nil) + .where("posts.user_id IS NULL") + .where("users.last_seen_at < ?", SiteSetting.clean_up_inactive_users_after_days.days.ago) + .where(trust_level: 0) + .find_each do |user| + + begin + destroyer.destroy(user, context: I18n.t("user.destroy_reasons.inactive_user")) + rescue => e + Discourse.handle_job_exception(e, + message: "Cleaning up inactive users", + extra: { user_id: user.id } + ) + end + end + end + end +end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index f050e03edb7..8b830a6ae31 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1829,6 +1829,8 @@ en: ignored_users_message_gap_days: "How long to wait before notifying moderators again about a user who has been ignored by many others." + clean_up_inactive_users_after_days: "Number of days before an inactive user (trust level 0 without any posts) is removed." + user_website_domains_whitelist: "User website will be verified against these domains. Pipe-delimited list." allow_profile_backgrounds: "Allow users to upload profile backgrounds." @@ -2201,6 +2203,7 @@ en: unused_staged_user: "Unused staged user" fixed_primary_email: "Fixed primary email for staged user" same_ip_address: "Same IP address (%{ip_address}) as other users" + inactive_user: "Inactive user" flags_reminder: flags_were_submitted: diff --git a/config/site_settings.yml b/config/site_settings.yml index aa8700667ae..6ead1d384f6 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -541,6 +541,8 @@ users: default: 365 client: true min: 1 + clean_up_inactive_users_after_days: + default: 730 groups: enable_group_directory: diff --git a/spec/jobs/clean_up_inactive_users_spec.rb b/spec/jobs/clean_up_inactive_users_spec.rb new file mode 100644 index 00000000000..5d01233fe3a --- /dev/null +++ b/spec/jobs/clean_up_inactive_users_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +RSpec.describe Jobs::CleanUpInactiveUsers do + context 'when user is inactive' do + let(:user) { Fabricate(:user) } + let(:active_user) { Fabricate(:active_user) } + + it 'should clean up the user' do + user.update!(last_seen_at: 3.years.ago, trust_level: 0) + active_user + + expect { described_class.new.execute({}) }.to change { User.count }.by(-1) + expect(User.find_by(id: user.id)).to eq(nil) + end + end + + context 'when user is not inactive' do + + let!(:active_user_1) { Fabricate(:post, user: Fabricate(:user, trust_level: 0)).user } + let!(:active_user_2) { Fabricate(:user, trust_level: 0, last_seen_at: 2.days.ago) } + let!(:active_user_3) { Fabricate(:user, trust_level: 1) } + + it 'should not clean up active users' do + expect { described_class.new.execute({}) }.to_not change { User.count } + expect(User.find_by(id: active_user_1.id)).to_not eq(nil) + expect(User.find_by(id: active_user_2.id)).to_not eq(nil) + expect(User.find_by(id: active_user_3.id)).to_not eq(nil) + end + end +end