From b9764d39c3f1e8f3c4746fc611c4cc692cc67144 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Tue, 19 Aug 2025 18:55:22 +0200 Subject: [PATCH] Skip trial emails for active users --- app/jobs/users/mailer_sending_job.rb | 11 +++ app/models/import.rb | 20 ++--- app/models/user.rb | 2 +- spec/jobs/users/mailer_sending_job_spec.rb | 94 +++++++++++++++++++--- spec/models/user_spec.rb | 2 +- trials_feature_checklist.md | 28 ------- 6 files changed, 103 insertions(+), 54 deletions(-) delete mode 100644 trials_feature_checklist.md diff --git a/app/jobs/users/mailer_sending_job.rb b/app/jobs/users/mailer_sending_job.rb index 4b7db707..bbce993f 100644 --- a/app/jobs/users/mailer_sending_job.rb +++ b/app/jobs/users/mailer_sending_job.rb @@ -6,8 +6,19 @@ class Users::MailerSendingJob < ApplicationJob def perform(user_id, email_type, **options) user = User.find(user_id) + if trial_related_email?(email_type) && user.active? + Rails.logger.info "Skipping #{email_type} email for user #{user_id} - user is already subscribed" + return + end + params = { user: user }.merge(options) UsersMailer.with(params).public_send(email_type).deliver_later end + + private + + def trial_related_email?(email_type) + %w[trial_expires_soon trial_expired].include?(email_type.to_s) + end end diff --git a/app/models/import.rb b/app/models/import.rb index 08f1cfa5..74024798 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -23,18 +23,6 @@ class Import < ApplicationRecord user_data_archive: 8 } - private - - def file_size_within_limit - return unless file.attached? - - if file.blob.byte_size > 11.megabytes - errors.add(:file, 'is too large. Trial users can only upload files up to 10MB.') - end - end - - public - def process! if user_data_archive? process_user_data_archive! @@ -71,4 +59,12 @@ class Import < ApplicationRecord def remove_attached_file file.purge_later end + + def file_size_within_limit + return unless file.attached? + + if file.blob.byte_size > 11.megabytes + errors.add(:file, 'is too large. Trial users can only upload files up to 10MB.') + end + end end diff --git a/app/models/user.rb b/app/models/user.rb index db156935..3f4046a0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -28,7 +28,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength attribute :admin, :boolean, default: false - enum :status, { inactive: 0, active: 1, trial: 3 } + enum :status, { inactive: 0, active: 1, trial: 2 } def safe_settings Users::SafeSettings.new(settings) diff --git a/spec/jobs/users/mailer_sending_job_spec.rb b/spec/jobs/users/mailer_sending_job_spec.rb index 9d0041a6..f4ce4faa 100644 --- a/spec/jobs/users/mailer_sending_job_spec.rb +++ b/spec/jobs/users/mailer_sending_job_spec.rb @@ -10,42 +10,88 @@ RSpec.describe Users::MailerSendingJob, type: :job do describe '#perform' do context 'when email_type is welcome' do - it 'sends welcome email' do + it 'sends welcome email to trial user' do expect(UsersMailer).to receive(:with).with({ user: user }) expect(UsersMailer).to receive(:welcome).and_return(mailer_double) expect(mailer_double).to receive(:deliver_later) described_class.perform_now(user.id, 'welcome') end + + it 'sends welcome email to active user' do + active_user = create(:user) + expect(UsersMailer).to receive(:with).with({ user: active_user }) + expect(UsersMailer).to receive(:welcome).and_return(mailer_double) + expect(mailer_double).to receive(:deliver_later) + + described_class.perform_now(active_user.id, 'welcome') + end end context 'when email_type is explore_features' do - it 'sends explore_features email' do + it 'sends explore_features email to trial user' do expect(UsersMailer).to receive(:with).with({ user: user }) expect(UsersMailer).to receive(:explore_features).and_return(mailer_double) expect(mailer_double).to receive(:deliver_later) described_class.perform_now(user.id, 'explore_features') end + + it 'sends explore_features email to active user' do + active_user = create(:user) + expect(UsersMailer).to receive(:with).with({ user: active_user }) + expect(UsersMailer).to receive(:explore_features).and_return(mailer_double) + expect(mailer_double).to receive(:deliver_later) + + described_class.perform_now(active_user.id, 'explore_features') + end end context 'when email_type is trial_expires_soon' do - it 'sends trial_expires_soon email' do - expect(UsersMailer).to receive(:with).with({ user: user }) - expect(UsersMailer).to receive(:trial_expires_soon).and_return(mailer_double) - expect(mailer_double).to receive(:deliver_later) + context 'with trial user' do + it 'sends trial_expires_soon email' do + expect(UsersMailer).to receive(:with).with({ user: user }) + expect(UsersMailer).to receive(:trial_expires_soon).and_return(mailer_double) + expect(mailer_double).to receive(:deliver_later) - described_class.perform_now(user.id, 'trial_expires_soon') + described_class.perform_now(user.id, 'trial_expires_soon') + end + end + + context 'with active user' do + let(:active_user) { create(:user) } + + it 'skips sending trial_expires_soon email' do + expect(UsersMailer).not_to receive(:with) + expect(UsersMailer).not_to receive(:trial_expires_soon) + expect(Rails.logger).to receive(:info).with("Skipping trial_expires_soon email for user #{active_user.id} - user is already subscribed") + + described_class.perform_now(active_user.id, 'trial_expires_soon') + end end end context 'when email_type is trial_expired' do - it 'sends trial_expired email' do - expect(UsersMailer).to receive(:with).with({ user: user }) - expect(UsersMailer).to receive(:trial_expired).and_return(mailer_double) - expect(mailer_double).to receive(:deliver_later) + context 'with trial user' do + it 'sends trial_expired email' do + expect(UsersMailer).to receive(:with).with({ user: user }) + expect(UsersMailer).to receive(:trial_expired).and_return(mailer_double) + expect(mailer_double).to receive(:deliver_later) - described_class.perform_now(user.id, 'trial_expired') + described_class.perform_now(user.id, 'trial_expired') + end + end + + context 'with active user' do + let(:active_user) { create(:user) } + + it 'skips sending trial_expired email' do + expect(UsersMailer).not_to receive(:with) + expect(UsersMailer).not_to receive(:trial_expired) + expect(Rails.logger).to receive(:info).with("Skipping trial_expired email for user #{active_user.id} - user is already subscribed") + + described_class.perform_now(active_user.id, 'trial_expired') + end end end @@ -72,4 +118,28 @@ RSpec.describe Users::MailerSendingJob, type: :job do end end end + + describe '#trial_related_email?' do + subject { described_class.new } + + it 'returns true for trial_expires_soon' do + expect(subject.send(:trial_related_email?, 'trial_expires_soon')).to be true + end + + it 'returns true for trial_expired' do + expect(subject.send(:trial_related_email?, 'trial_expired')).to be true + end + + it 'returns false for welcome' do + expect(subject.send(:trial_related_email?, 'welcome')).to be false + end + + it 'returns false for explore_features' do + expect(subject.send(:trial_related_email?, 'explore_features')).to be false + end + + it 'returns false for unknown email types' do + expect(subject.send(:trial_related_email?, 'unknown_email')).to be false + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index dd7e69a9..2c07580a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -18,7 +18,7 @@ RSpec.describe User, type: :model do end describe 'enums' do - it { is_expected.to define_enum_for(:status).with_values(inactive: 0, active: 1, trial: 3) } + it { is_expected.to define_enum_for(:status).with_values(inactive: 0, active: 1, trial: 2) } end describe 'callbacks' do diff --git a/trials_feature_checklist.md b/trials_feature_checklist.md deleted file mode 100644 index ed9da0b4..00000000 --- a/trials_feature_checklist.md +++ /dev/null @@ -1,28 +0,0 @@ -# Trials Feature Checklist - -## ✅ Already Implemented - -- [x] **7-day trial activation** - `set_trial` method sets `status: :trial` and `active_until: 7.days.from_now` -- [x] **Welcome email** - Sent immediately after registration -- [x] **Scheduled emails** - Feature exploration (day 2), trial expires soon (day 5), trial expired (day 7) -- [x] **Trial status enum** - `{ inactive: 0, active: 1, trial: 3 }` -- [x] **Navbar Trial Display** - Show number of days left in trial at subscribe button -- [x] **Account Deletion Cleanup** - User deletes account during trial, cleanup scheduled emails - - [x] Worker to not send emails if user is deleted - -## ❌ Missing/TODO Items - -### Core Requirements -- [x] **Specs** - Add specs for all implemented features - - [x] User model trial callbacks and methods - - [x] Trial webhook job with JWT encoding - - [x] Mailer sending job for all email types - - [x] JWT encoding service - - -## Manager (separate application) -- [ ] **Manager Webhook** - Create user in Manager service after registration -- [ ] **Manager callback** - Manager should daily check user statuses and once trial is expired, update user status to inactive in Dawarich -- [ ] **Trial Credit** - Should trial time be credited to first paid month? - - [ ] Yes, Manager after payment adds subscription duration to user's active_until -- [ ] **User Reactivation** - Handle user returning after trial expired