From a07943439718cf970d3c3dded5f66f606716e2fb Mon Sep 17 00:00:00 2001 From: Ben Halpern Date: Wed, 13 Sep 2023 13:01:39 -0400 Subject: [PATCH] Add new invitation params (#20074) * Add new invitation fields * SMTP enabled check * Update spec/requests/api/v1/admin/users_spec.rb Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Add devise_mailer spec * Add devise_mailer spec * Update spec/mailers/devise_mailer_spec.rb Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update spec/mailers/devise_mailer_spec.rb Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update spec/mailers/devise_mailer_spec.rb Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../admin/invitations_controller.rb | 29 +++++++++-- .../concerns/api/admin/users_controller.rb | 8 +++- app/mailers/devise_mailer.rb | 9 ++++ app/models/user.rb | 2 +- app/views/admin/invitations/new.html.erb | 21 ++++++++ .../mailer/invitation_instructions.html.erb | 24 ++++++++-- spec/mailers/devise_mailer_spec.rb | 48 +++++++++++++++++++ .../devise_invitable/mailer_preview.rb | 9 +++- spec/requests/admin/invitations_spec.rb | 20 ++++++++ spec/requests/api/v1/admin/users_spec.rb | 21 ++++++++ 10 files changed, 179 insertions(+), 12 deletions(-) diff --git a/app/controllers/admin/invitations_controller.rb b/app/controllers/admin/invitations_controller.rb index d732b2ca001f0..2cc412556153f 100644 --- a/app/controllers/admin/invitations_controller.rb +++ b/app/controllers/admin/invitations_controller.rb @@ -15,6 +15,9 @@ def new; end def create email = params.dig(:user, :email) + custom_invite_subject = params.dig(:user, :custom_invite_subject) + custom_invite_message = params.dig(:user, :custom_invite_message) + custom_invite_footnote = params.dig(:user, :custom_invite_footnote) if User.exists?(email: email.downcase, registered: true) flash[:error] = I18n.t("admin.invitations_controller.duplicate", email: email) @@ -23,10 +26,16 @@ def create end username = "#{email.split('@').first.gsub(/[^0-9a-z ]/i, '')}_#{rand(1000)}" - User.invite!(email: email, - username: username, - profile_image: ::Images::ProfileImageGenerator.call, - registered: false) + User.invite!({ email: email, + username: username, + profile_image: ::Images::ProfileImageGenerator.call, + registered: false }, + nil, + { + custom_invite_subject: custom_invite_subject, + custom_invite_message: custom_invite_message, + custom_invite_footnote: custom_invite_footnote + }) flash[:success] = I18n.t("admin.invitations_controller.create_success") redirect_to admin_invitations_path end @@ -50,5 +59,17 @@ def resend end redirect_to admin_invitations_path end + + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:invite, + keys: %i[email + name + username + custom_invite_subject + custom_invite_message + custom_invite_footnote + profile_image + registered]) + end end end diff --git a/app/controllers/concerns/api/admin/users_controller.rb b/app/controllers/concerns/api/admin/users_controller.rb index a188828a3ff61..295d031c01396 100644 --- a/app/controllers/concerns/api/admin/users_controller.rb +++ b/app/controllers/concerns/api/admin/users_controller.rb @@ -5,7 +5,13 @@ module UsersController def create # NOTE: We can add an inviting user here, e.g. User.invite!(current_user, user_params). - User.invite!(user_params.merge(registered: false)) + options = { + custom_invite_subject: params[:custom_invite_subject], + custom_invite_message: params[:custom_invite_message], + custom_invite_footnote: params[:custom_invite_footnote] + } + + User.invite!(user_params.merge(registered: false), nil, options) head :ok end diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb index 077382c2b8403..511046535e489 100644 --- a/app/mailers/devise_mailer.rb +++ b/app/mailers/devise_mailer.rb @@ -10,4 +10,13 @@ def use_settings_general_values "#{Settings::Community.community_name} <#{ForemInstance.from_email_address}>" ActionMailer::Base.default_url_options[:host] = Settings::General.app_domain end + + # rubocop:disable Style/OptionHash + def invitation_instructions(record, token, opts = {}) + @message = opts[:custom_invite_message] + @footnote = opts[:custom_invite_footnote] + headers = { subject: opts[:custom_invite_subject].presence || "Invitation Instructions" } + super(record, token, opts.merge(headers)) + end + # rubocop:enable Style/OptionHash end diff --git a/app/models/user.rb b/app/models/user.rb index 1c39eb7ed8a05..2fdeff6805938 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -39,7 +39,7 @@ class User < ApplicationRecord attr_accessor :scholar_email, :new_note, :note_for_current_role, :user_status, :merge_user_id, :add_credits, :remove_credits, :add_org_credits, :remove_org_credits, :ip_address, - :current_password + :current_password, :custom_invite_subject, :custom_invite_message, :custom_invite_footnote acts_as_followable acts_as_follower diff --git a/app/views/admin/invitations/new.html.erb b/app/views/admin/invitations/new.html.erb index f83b4e9877a67..f012cb78161d1 100644 --- a/app/views/admin/invitations/new.html.erb +++ b/app/views/admin/invitations/new.html.erb @@ -30,6 +30,27 @@ placeholder: "Email of invitee", required: true %> +
+ <%= f.label :custom_invite_subject, "Subject (Optional)", class: "crayons-field__label" %> + <%= f.text_field :custom_invite_subject, + class: "crayons-textfield", + placeholder: 'REPLACES "Invitation Instructions"', + required: true %> +
+
+ <%= f.label :custom_invite_message, "Custom Message (Optional)", class: "crayons-field__label" %> + <%= f.text_area :custom_invite_message, + class: "crayons-textfield", + placeholder: "REPLACES INTRO: Hello... You have been invited to join...", + required: true %> +
+
+ <%= f.label :custom_invite_footnote, "Custom Footer (Optional)", class: "crayons-field__label" %> + <%= f.text_field :custom_invite_footnote, + class: "crayons-textfield", + placeholder: "Comes right after invitation link", + required: true %> +
<%= f.submit "Invite User", class: "crayons-btn" %>
diff --git a/app/views/devise/mailer/invitation_instructions.html.erb b/app/views/devise/mailer/invitation_instructions.html.erb index 5439d8a80809f..102c47a2a3f77 100644 --- a/app/views/devise/mailer/invitation_instructions.html.erb +++ b/app/views/devise/mailer/invitation_instructions.html.erb @@ -1,13 +1,27 @@ -

<%= t("devise.mailer.invitation_instructions.hello") %>

-

<%= t("devise.mailer.invitation_instructions.someone_invited_you", community_name: Settings::Community.community_name, url: root_url) %>

+<% if @message.present? %> + <%= Markdown.new(@message).to_html.html_safe %> +<% else %> +

<%= t("devise.mailer.invitation_instructions.hello") %>

-

<%= t("devise.mailer.invitation_instructions.accept_instructions") %>:

+

<%= t("devise.mailer.invitation_instructions.someone_invited_you", community_name: Settings::Community.community_name, url: root_url) %>

-

<%= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, invitation_token: @token) %>

+

<%= t("devise.mailer.invitation_instructions.accept_instructions") %>:

+ +<% end %> + +

<%= link_to t("devise.mailer.invitation_instructions.accept"), + accept_invitation_url(@resource, invitation_token: @token), + style: "color:white; background: #{Settings::UserExperience.primary_brand_color_hex}; border-radius: 8px; padding: 10px 20px; font-size: 1.33em; display: inline-block; margin: 8px 0; text-decoration: none; font-weight: bold;" %>

<% if @resource.invitation_due_at %>

<%= t("devise.mailer.invitation_instructions.accept_until", due_date: l(@resource.invitation_due_at, format: :"devise.mailer.invitation_instructions.accept_until_format")) %>

<% end %> -

<%= t("devise.mailer.invitation_instructions.ignore") %>

+
+ <% if @footnote.present? %> + <%= Markdown.new(@footnote).to_html.html_safe %> + <% end %> + +

<%= t("devise.mailer.invitation_instructions.ignore") %>

+
diff --git a/spec/mailers/devise_mailer_spec.rb b/spec/mailers/devise_mailer_spec.rb index f78f64c5cf1d4..4031021179695 100644 --- a/spec/mailers/devise_mailer_spec.rb +++ b/spec/mailers/devise_mailer_spec.rb @@ -56,4 +56,52 @@ end end end + + describe "#invitation_instructions" do + let(:token) { "some_token" } + let(:custom_invite_message) { "Join our community!!" } + let(:custom_invite_footnote) { "Looking forward to seeing you!!" } + let(:custom_invite_subject) { "You've Been Invited" } + + let(:opts) do + { + custom_invite_message: custom_invite_message, + custom_invite_footnote: custom_invite_footnote, + custom_invite_subject: custom_invite_subject + } + end + + let(:email) { described_class.invitation_instructions(user, token, opts) } + + before do + allow(Settings::SMTP).to receive_messages( + from_email_address: "custom_noreply@example.com", + reply_to_email_address: "custom_reply@example.com", + ) + end + + it "uses the custom invite subject if provided" do + expect(email.subject).to eq(custom_invite_subject) + end + + it "falls back to default subject if custom subject is not provided" do + email_without_custom_subject = described_class.invitation_instructions(user, token, {}) + expect(email_without_custom_subject.subject).to eq("Invitation Instructions") + end + + it "includes the custom invite message if provided" do + # Ensure your email view actually includes @message + expect(email.to_s).to include(custom_invite_message) + end + + it "does not include overridden default message if invite message is provided" do + # Ensure your email view actually includes @message + expect(email.to_s).not_to include("

#{I18n.t('devise.mailer.invitation_instructions.accept_instructions')}") + end + + it "includes the custom invite footnote if provided" do + # Ensure your email view actually includes @footnote + expect(email.to_s).to include(custom_invite_footnote) + end + end end diff --git a/spec/mailers/previews/devise_invitable/mailer_preview.rb b/spec/mailers/previews/devise_invitable/mailer_preview.rb index 9eb6a353d920c..aa4ed3235de2d 100644 --- a/spec/mailers/previews/devise_invitable/mailer_preview.rb +++ b/spec/mailers/previews/devise_invitable/mailer_preview.rb @@ -2,7 +2,14 @@ module DeviseInvitable class MailerPreview < ActionMailer::Preview def invitation_instructions - Devise.mailer.invitation_instructions(User.last, "faketoken") + user = User.invite!(email: "test@email.com", + username: "username") + options = { + custom_invite_subject: "Custom subject!!", + custom_invite_message: "# Hey!\n\nJoin [our Forem](https://example.com).\n\n## Testing!", + custom_invite_footnote: "Custom footnote! _Yay_" + } + Devise.mailer.invitation_instructions(user, "faketoken", options) end end end diff --git a/spec/requests/admin/invitations_spec.rb b/spec/requests/admin/invitations_spec.rb index 87ed3fc155d47..230950f15f1d4 100644 --- a/spec/requests/admin/invitations_spec.rb +++ b/spec/requests/admin/invitations_spec.rb @@ -40,6 +40,26 @@ expect(enqueued_jobs.first[:args]).to match(array_including("invitation_instructions")) end + it "enqueues an invitation email to be sent with custom subject", :aggregate_failures do + allow(DeviseMailer).to receive(:invitation_instructions).and_call_original + + assert_enqueued_with(job: Devise.mailer.delivery_job) do + user_params = { email: "hey#{rand(1000)}@email.co", + custom_invite_subject: "Custom Subject!", + custom_invite_message: "**Custom message**", + custom_invite_footnote: "Custom footnote" } + post admin_invitations_path, params: { user: user_params } + end + + expect(DeviseMailer).to have_received(:invitation_instructions) do |_user, _token, args| + expect(args).to include( + custom_invite_subject: "Custom Subject!", + custom_invite_message: "**Custom message**", + ) + end + expect(enqueued_jobs.first[:args]).to match(array_including("invitation_instructions")) + end + it "does not create an invitation if a user with that email exists" do expect do post admin_invitations_path, diff --git a/spec/requests/api/v1/admin/users_spec.rb b/spec/requests/api/v1/admin/users_spec.rb index 87bf2cd7427f4..d300de1c03f1a 100644 --- a/spec/requests/api/v1/admin/users_spec.rb +++ b/spec/requests/api/v1/admin/users_spec.rb @@ -49,6 +49,27 @@ expect(response).to have_http_status(:ok) end + it "enqueues an invitation email to be sent with custom options", :aggregate_failures do + allow(DeviseMailer).to receive(:invitation_instructions).and_call_original + + assert_enqueued_with(job: Devise.mailer.delivery_job) do + params = { email: "hey#{rand(1000)}@email.co", + custom_invite_subject: "Custom Subject!", + custom_invite_message: "**Custom message**", + custom_invite_footnote: "Custom footnote" } + + post api_admin_users_path, params: params, headers: headers + end + + expect(DeviseMailer).to have_received(:invitation_instructions) do |_user, _token, args| + expect(args).to include( + custom_invite_subject: "Custom Subject!", + custom_invite_message: "**Custom message**", + ) + end + expect(enqueued_jobs.first[:args]).to match(array_including("invitation_instructions")) + end + it "marks user as registered false" do post api_admin_users_path, params: params, headers: headers