FEATURE: Custom unsubscribe options (#17090)

With this change, plugins can create custom unsubscribe keys, extend the unsubscribe view with custom preferences, and decide how they are updated.
This commit is contained in:
Roman Rizzi 2022-06-21 15:49:47 -03:00 committed by GitHub
parent deee3c6f02
commit e0ba35350e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 376 additions and 246 deletions

View File

@ -6,38 +6,17 @@ class EmailController < ApplicationController
skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required
def unsubscribe
@not_found = true
@watched_count = nil
key = UnsubscribeKey.find_by(key: params[:key])
@found = key.present?
if key = UnsubscribeKey.find_by(key: params[:key])
if @user = key.user
post = key.post
@topic = post&.topic || key.topic
@digest_unsubscribe = !@topic && !SiteSetting.disable_digest_emails
@type = key.unsubscribe_key_type
@not_found = false
if @found
UnsubscribeKey
.get_unsubscribe_strategy_for(key)
&.prepare_unsubscribe_options(self)
if current_user.present? && (@user != current_user)
@different_user = @user.name
@return_url = request.original_url
end
watching = TopicUser.notification_levels[:watching]
@unsubscribed_from_all = @user.user_option.unsubscribed_from_all?
if @topic
@watching_topic = TopicUser.exists?(user_id: @user.id, notification_level: watching, topic_id: @topic.id)
if @topic.category_id
if CategoryUser.exists?(user_id: @user.id, notification_level: CategoryUser.watching_levels, category_id: @topic.category_id)
@watched_count = TopicUser.joins(:topic)
.where(user: @user, notification_level: watching, "topics.category_id" => @topic.category_id)
.count
end
end
else
@digest_frequencies = digest_frequencies(@user)
end
if current_user.present? && (@user != current_user)
@different_user = @user.name
@return_url = request.original_url
end
end
end
@ -46,80 +25,24 @@ class EmailController < ApplicationController
RateLimiter.new(nil, "unsubscribe_#{request.ip}", 10, 1.minute).performed!
key = UnsubscribeKey.find_by(key: params[:key])
raise Discourse::NotFound unless key && key.user
topic = key&.post&.topic || key.topic
raise Discourse::NotFound if key.nil? || key.user.nil?
user = key.user
updated = UnsubscribeKey.get_unsubscribe_strategy_for(key)
&.unsubscribe(params)
updated = false
if updated
cache_key = "unsub_#{SecureRandom.hex}"
Discourse.cache.write cache_key, user.email, expires_in: 1.hour
if topic
if params["unwatch_topic"]
TopicUser.where(topic_id: topic.id, user_id: user.id)
.update_all(notification_level: TopicUser.notification_levels[:tracking])
updated = true
end
if params["unwatch_category"] && topic.category_id
TopicUser.joins(:topic)
.where(:user => user,
:notification_level => TopicUser.notification_levels[:watching],
"topics.category_id" => topic.category_id)
.update_all(notification_level: TopicUser.notification_levels[:tracking])
CategoryUser.where(user_id: user.id,
category_id: topic.category_id,
notification_level: CategoryUser.watching_levels
)
.destroy_all
updated = true
end
if params["mute_topic"]
TopicUser.where(topic_id: topic.id, user_id: user.id)
.update_all(notification_level: TopicUser.notification_levels[:muted])
updated = true
end
end
if params["disable_mailing_list"]
user.user_option.update_columns(mailing_list_mode: false)
updated = true
end
if params['digest_after_minutes']
digest_frequency = params['digest_after_minutes'].to_i
user.user_option.update_columns(
digest_after_minutes: digest_frequency,
email_digests: digest_frequency.positive?
)
updated = true
end
if params["unsubscribe_all"]
user.user_option.update_columns(email_digests: false,
email_level: UserOption.email_level_types[:never],
email_messages_level: UserOption.email_level_types[:never],
mailing_list_mode: false)
updated = true
end
unless updated
redirect_back fallback_location: path("/")
else
key = "unsub_#{SecureRandom.hex}"
Discourse.cache.write key, user.email, expires_in: 1.hour
url = path("/email/unsubscribed?key=#{key}")
if topic
url += "&topic_id=#{topic.id}"
url = path("/email/unsubscribed?key=#{cache_key}")
if key.associated_topic
url += "&topic_id=#{key.associated_topic.id}"
end
redirect_to url
else
redirect_back fallback_location: path("/")
end
end
def unsubscribed
@ -130,31 +53,4 @@ class EmailController < ApplicationController
topic = Topic.find_by(id: params[:topic_id].to_i) if @topic_id
@topic = topic if topic && Guardian.new(nil).can_see?(topic)
end
private
def digest_frequencies(user)
frequency_in_minutes = user.user_option.digest_after_minutes
email_digests = user.user_option.email_digests
frequencies = DigestEmailSiteSetting.values.dup
never = frequencies.delete_at(0)
allowed_frequencies = %w[never weekly every_month every_six_months]
result = frequencies.reduce(frequencies: [], current: nil, selected: nil, take_next: false) do |memo, v|
memo[:current] = v[:name] if v[:value] == frequency_in_minutes && email_digests
next(memo) unless allowed_frequencies.include?(v[:name])
memo.tap do |m|
m[:selected] = v[:value] if m[:take_next] && email_digests
m[:frequencies] << [I18n.t("unsubscribe.digest_frequency.#{v[:name]}"), v[:value]]
m[:take_next] = !m[:take_next] && m[:current]
end
end
result.slice(:frequencies, :current, :selected).tap do |r|
r[:frequencies] << [I18n.t("unsubscribe.digest_frequency.#{never[:name]}"), never[:value]]
r[:selected] ||= never[:value]
r[:current] ||= never[:name]
end
end
end

View File

@ -4,7 +4,7 @@ class SubscriptionMailer < ActionMailer::Base
include Email::BuildEmailHelper
def confirm_unsubscribe(user, opts = {})
unsubscribe_key = UnsubscribeKey.create_key_for(user, "all")
unsubscribe_key = UnsubscribeKey.create_key_for(user, UnsubscribeKey::ALL_TYPE)
build_email user.email,
template: "unsubscribe_mailer",
site_title: SiteSetting.title,

View File

@ -216,6 +216,8 @@ class UserNotifications < ActionMailer::Base
def digest(user, opts = {})
build_summary_for(user)
@unsubscribe_key = UnsubscribeKey.create_key_for(@user, UnsubscribeKey::DIGEST_TYPE)
min_date = opts[:since] || user.last_emailed_at || user.last_seen_at || 1.month.ago
# Fetch some topics and posts to show
@ -753,7 +755,6 @@ class UserNotifications < ActionMailer::Base
@header_bgcolor = ColorScheme.hex_for_name('header_background')
@anchor_color = ColorScheme.hex_for_name('tertiary')
@markdown_linker = MarkdownLinker.new(@base_url)
@unsubscribe_key = UnsubscribeKey.create_key_for(@user, "digest")
@disable_email_custom_styles = !SiteSetting.apply_custom_styles_to_digest
end

View File

@ -616,7 +616,9 @@ class Post < ActiveRecord::Base
end
def unsubscribe_url(user)
"#{Discourse.base_url}/email/unsubscribe/#{UnsubscribeKey.create_key_for(user, self)}"
key_value = UnsubscribeKey.create_key_for(user, UnsubscribeKey::TOPIC_TYPE, post: self)
"#{Discourse.base_url}/email/unsubscribe/#{key_value}"
end
def self.url(slug, topic_id, post_number, opts = nil)

View File

@ -7,16 +7,44 @@ class UnsubscribeKey < ActiveRecord::Base
before_create :generate_random_key
def self.create_key_for(user, type)
if Post === type
create(user_id: user.id, unsubscribe_key_type: "topic", topic_id: type.topic_id, post_id: type.id).key
else
create(user_id: user.id, unsubscribe_key_type: type).key
ALL_TYPE = 'all'
DIGEST_TYPE = 'digest'
TOPIC_TYPE = 'topic'
class << self
def create_key_for(user, type, post: nil)
unsubscribe_key = new(user_id: user.id, unsubscribe_key_type: type)
if type == TOPIC_TYPE
unsubscribe_key.topic_id = post.topic_id
unsubscribe_key.post_id = post.id
end
unsubscribe_key.save!
unsubscribe_key.key
end
def user_for_key(key)
where(key: key).first&.user
end
def get_unsubscribe_strategy_for(key)
strategies = {
DIGEST_TYPE => EmailControllerHelper::DigestEmailUnsubscriber,
TOPIC_TYPE => EmailControllerHelper::TopicEmailUnsubscriber,
ALL_TYPE => EmailControllerHelper::BaseEmailUnsubscriber
}
DiscoursePluginRegistry.email_unsubscribers.each do |unsubcriber|
strategies.merge!(unsubcriber)
end
strategies[key.unsubscribe_key_type]&.new(key)
end
end
def self.user_for_key(key)
where(key: key).first.try(:user)
def associated_topic
@associated_topic ||= topic || post&.topic
end
private

View File

@ -1,7 +1,7 @@
<div class='container unsubscribe'>
<%- if @not_found || @different_user %>
<%- if !@found || @different_user %>
<%if @not_found%>
<%if !@found %>
<p><%= t "unsubscribe.not_found_description" %></p>
<%- else %>
<p><%= t("unsubscribe.different_user_description").html_safe %></p>
@ -78,6 +78,8 @@
</p>
<% end %>
<%= server_plugin_outlet "unsubscribe_options" %>
<% unless @unsubscribed_from_all %>
<p>
<label>

View File

@ -95,6 +95,8 @@ class DiscoursePluginRegistry
define_filtered_register :notification_consolidation_plans
define_filtered_register :email_unsubscribers
def self.register_auth_provider(auth_provider)
self.auth_providers << auth_provider
end

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
# This class and its children are instantiated and used by the EmailController.
module EmailControllerHelper
class BaseEmailUnsubscriber
def initialize(unsubscribe_key)
@unsubscribe_key = unsubscribe_key
end
attr_reader :unsubscribe_key
# Sets instance variables in the `EmailController#unsubscribe`, which are later available in the view.
# Don't forget to call super when extending this method.
def prepare_unsubscribe_options(controller)
controller.instance_variable_set(:@digest_unsubscribe, false)
controller.instance_variable_set(:@watched_count, nil)
controller.instance_variable_set(:@type, unsubscribe_key.unsubscribe_key_type)
controller.instance_variable_set(:@user, key_owner)
controller.instance_variable_set(
:@unsubscribed_from_all,
key_owner.user_option.unsubscribed_from_all?
)
end
# Called by the `EmailController#perform_unsubscribe` and defines what unsubscribing means.
#
# Receives the request params and returns a boolean indicating if any preferences were updated.
#
# Don't forget to call super when extending this method.
def unsubscribe(params)
updated = false
if params[:disable_mailing_list]
key_owner.user_option.update_columns(mailing_list_mode: false)
updated = true
end
if params[:unsubscribe_all]
key_owner.user_option.update_columns(email_digests: false,
email_level: UserOption.email_level_types[:never],
email_messages_level: UserOption.email_level_types[:never],
mailing_list_mode: false)
updated = true
end
updated
end
protected
def key_owner
unsubscribe_key.user
end
end
end

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
module EmailControllerHelper
class DigestEmailUnsubscriber < BaseEmailUnsubscriber
def prepare_unsubscribe_options(controller)
super(controller)
controller.instance_variable_set(:@digest_unsubscribe, !SiteSetting.disable_digest_emails)
frequency_in_minutes = key_owner.user_option.digest_after_minutes
email_digests = key_owner.user_option.email_digests
frequencies = DigestEmailSiteSetting.values.dup
never = frequencies.delete_at(0)
allowed_frequencies = %w[never weekly every_month every_six_months]
result = frequencies.reduce(frequencies: [], current: nil, selected: nil, take_next: false) do |memo, v|
memo[:current] = v[:name] if v[:value] == frequency_in_minutes && email_digests
next(memo) unless allowed_frequencies.include?(v[:name])
memo.tap do |m|
m[:selected] = v[:value] if m[:take_next] && email_digests
m[:frequencies] << [I18n.t("unsubscribe.digest_frequency.#{v[:name]}"), v[:value]]
m[:take_next] = !m[:take_next] && m[:current]
end
end
digest_frequencies = result.slice(:frequencies, :current, :selected).tap do |r|
r[:frequencies] << [I18n.t("unsubscribe.digest_frequency.#{never[:name]}"), never[:value]]
r[:selected] ||= never[:value]
r[:current] ||= never[:name]
end
controller.instance_variable_set(:@digest_frequencies, digest_frequencies)
end
def unsubscribe(params)
updated = super(params)
if params[:digest_after_minutes]
digest_frequency = params[:digest_after_minutes].to_i
unsubscribe_key.user.user_option.update_columns(
digest_after_minutes: digest_frequency,
email_digests: digest_frequency.positive?
)
updated = true
end
updated
end
end
end

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
module EmailControllerHelper
class TopicEmailUnsubscriber < BaseEmailUnsubscriber
def prepare_unsubscribe_options(controller)
super(controller)
watching = TopicUser.notification_levels[:watching]
topic = unsubscribe_key.associated_topic
controller.instance_variable_set(:@topic, topic)
controller.instance_variable_set(
:@watching_topic,
TopicUser.exists?(user: key_owner, notification_level: watching, topic_id: topic.id)
)
return if topic.category_id.blank?
return if !CategoryUser.exists?(user: key_owner, notification_level: CategoryUser.watching_levels, category_id: topic.category_id)
controller.instance_variable_set(
:@watched_count,
TopicUser.joins(:topic)
.where(user: key_owner, notification_level: watching).where(topics: { category_id: topic.category_id }).count
)
end
def unsubscribe(params)
updated = super(params)
topic = unsubscribe_key.associated_topic
return updated if topic.nil?
if params[:unwatch_topic]
TopicUser.where(topic_id: topic.id, user_id: key_owner.id)
.update_all(notification_level: TopicUser.notification_levels[:tracking])
updated = true
end
if params[:unwatch_category] && topic.category_id
TopicUser.joins(:topic)
.where(user: key_owner, notification_level: TopicUser.notification_levels[:watching])
.where(topics: { category_id: topic.category_id })
.update_all(notification_level: TopicUser.notification_levels[:tracking])
CategoryUser
.where(user_id: key_owner.id, category_id: topic.category_id, notification_level: CategoryUser.watching_levels)
.destroy_all
updated = true
end
if params[:mute_topic]
TopicUser.where(topic_id: topic.id, user_id: key_owner.id).update_all(notification_level: TopicUser.notification_levels[:muted])
updated = true
end
updated
end
end
end

View File

@ -1018,6 +1018,29 @@ class Plugin::Instance
StaticController::CUSTOM_PAGES[page] = blk ? { topic_id: blk } : options
end
# Let plugin define custom unsubscribe keys,
# set custom instance variables on the `EmailController#unsubscribe` action,
# and describe what unsubscribing for that key does.
#
# The method receives a class that inherits from `Email::BaseEmailUnsubscriber`.
# Take a look at it to know how to implement your child class.
#
# In conjunction with this, you'll have to:
#
# - Register a new connector under app/views/connectors/unsubscribe_options.
# We'll include the HTML inside the unsubscribe form, so you can add your fields using the
# instance variables you set in the controller previously. When the form is submitted,
# it sends the updated preferences to `EmailController#perform_unsubscribe`.
#
# - Your code is responsible for creating the custom key by calling `UnsubscribeKey#create_key_for`.
def register_email_unsubscriber(type, unsubscriber)
core_types = [UnsubscribeKey::ALL_TYPE, UnsubscribeKey::DIGEST_TYPE, UnsubscribeKey::TOPIC_TYPE]
raise ArgumentError.new('Type already exists') if core_types.include?(type)
raise ArgumentError.new('Not an email unsubscriber') if !unsubscriber.ancestors.include?(EmailControllerHelper::BaseEmailUnsubscriber)
DiscoursePluginRegistry.register_email_unsubscriber({ type => unsubscriber }, self)
end
protected
def self.js_path

View File

@ -720,4 +720,26 @@ describe Plugin::Instance do
)
end
end
describe '#register_email_unsubscriber' do
let(:plugin) { Plugin::Instance.new }
after do
DiscoursePluginRegistry.reset_register!(:email_unsubscribers)
end
it "doesn't let you override core unsubscribers" do
expect { plugin.register_email_unsubscriber(UnsubscribeKey::ALL_TYPE, Object) }.to raise_error(ArgumentError)
end
it "finds the plugin's custom unsubscriber" do
new_unsubscriber_type = 'new_type'
key = UnsubscribeKey.new(unsubscribe_key_type: new_unsubscriber_type)
CustomUnsubscriber = Class.new(EmailControllerHelper::BaseEmailUnsubscriber)
plugin.register_email_unsubscriber(new_unsubscriber_type, CustomUnsubscriber)
expect(UnsubscribeKey.get_unsubscribe_strategy_for(key).class).to eq(CustomUnsubscriber)
end
end
end

View File

@ -20,7 +20,7 @@ describe UnsubscribeKey do
describe 'key' do
fab!(:user) { Fabricate(:user) }
let!(:key) { UnsubscribeKey.create_key_for(user, "digest") }
let!(:key) { UnsubscribeKey.create_key_for(user, UnsubscribeKey::DIGEST_TYPE) }
it 'has a temporary key' do
expect(key).to be_present

View File

@ -1,142 +1,121 @@
# frozen_string_literal: true
RSpec.describe EmailController do
describe EmailController do
fab!(:user) { Fabricate(:user) }
fab!(:topic) { Fabricate(:topic) }
fab!(:private_topic) { Fabricate(:private_message_topic) }
context '.perform unsubscribe' do
describe '#perform_unsubscribe' do
it 'raises not found on invalid key' do
post "/email/unsubscribe/123.json"
expect(response.status).to eq(404)
end
fab!(:user) { Fabricate(:user) }
let(:key) { UnsubscribeKey.create_key_for(user, "all") }
describe 'unsubscribe from all emails' do
let(:key) { UnsubscribeKey.create_key_for(user, UnsubscribeKey::ALL_TYPE) }
it 'can fully unsubscribe' do
user.user_option.update_columns(email_digests: true,
email_level: UserOption.email_level_types[:never],
email_messages_level: UserOption.email_level_types[:never],
mailing_list_mode: true)
it 'can fully unsubscribe' do
user.user_option.update_columns(email_digests: true,
email_level: UserOption.email_level_types[:never],
email_messages_level: UserOption.email_level_types[:never],
mailing_list_mode: true)
post "/email/unsubscribe/#{key}.json",
params: { unsubscribe_all: "1" }
post "/email/unsubscribe/#{key}.json", params: { unsubscribe_all: "1" }
expect(response.status).to eq(302)
expect(response.status).to eq(302)
get response.redirect_url
get response.redirect_url
# cause it worked ... yay
expect(body).to include(user.email)
# cause it worked ... yay
expect(body).to include(user.email)
user.user_option.reload
user.user_option.reload
expect(user.user_option.email_digests).to eq(false)
expect(user.user_option.email_level).to eq(UserOption.email_level_types[:never])
expect(user.user_option.email_messages_level).to eq(UserOption.email_level_types[:never])
expect(user.user_option.mailing_list_mode).to eq(false)
expect(user.user_option.email_digests).to eq(false)
expect(user.user_option.email_level).to eq(UserOption.email_level_types[:never])
expect(user.user_option.email_messages_level).to eq(UserOption.email_level_types[:never])
expect(user.user_option.mailing_list_mode).to eq(false)
end
it 'can disable mailing list' do
user.user_option.update_columns(mailing_list_mode: true)
post "/email/unsubscribe/#{key}.json",
params: { disable_mailing_list: "1" }
expect(response.status).to eq(302)
expect(user.user_option.reload.mailing_list_mode).to eq(false)
end
end
it 'can disable mailing list' do
user.user_option.update_columns(mailing_list_mode: true)
describe 'unsubscribe from digest' do
let(:key) { UnsubscribeKey.create_key_for(user, UnsubscribeKey::DIGEST_TYPE) }
post "/email/unsubscribe/#{key}.json",
params: { disable_mailing_list: "1" }
it 'Can change digest frequency' do
weekly_interval_minutes = 10080
user.user_option.update_columns(email_digests: true, digest_after_minutes: 0)
expect(response.status).to eq(302)
post "/email/unsubscribe/#{key}.json",
params: { digest_after_minutes: weekly_interval_minutes.to_s }
user.user_option.reload
expect(response.status).to eq(302)
expect(user.user_option.reload.digest_after_minutes).to eq(weekly_interval_minutes)
end
expect(user.user_option.mailing_list_mode).to eq(false)
it 'Can disable email digests setting frequency to zero' do
user.user_option.update_columns(email_digests: true, digest_after_minutes: 10080)
post "/email/unsubscribe/#{key}.json",
params: { digest_after_minutes: '0' }
expect(response.status).to eq(302)
user.user_option.reload
expect(user.user_option.digest_after_minutes).to be_zero
expect(user.user_option.email_digests).to eq(false)
end
end
it 'Can change digest frequency' do
weekly_interval_minutes = 10080
user.user_option.update_columns(email_digests: true, digest_after_minutes: 0)
describe 'unsubscribe from a topic' do
fab!(:a_post) { Fabricate(:post) }
let(:key) { UnsubscribeKey.create_key_for(user, UnsubscribeKey::TOPIC_TYPE, post: a_post) }
post "/email/unsubscribe/#{key}.json",
params: { digest_after_minutes: weekly_interval_minutes.to_s }
it 'can unwatch topic' do
TopicUser.change(user.id, a_post.topic_id, notification_level: TopicUser.notification_levels[:watching])
expect(response.status).to eq(302)
post "/email/unsubscribe/#{key}.json", params: { unwatch_topic: "1" }
user.user_option.reload
expect(response.status).to eq(302)
expect(TopicUser.get(a_post.topic, user).notification_level).to eq(TopicUser.notification_levels[:tracking])
end
expect(user.user_option.digest_after_minutes).to eq(weekly_interval_minutes)
end
it 'can mute topic' do
TopicUser.change(user.id, a_post.topic_id, notification_level: TopicUser.notification_levels[:watching])
it 'Can disable email digests setting frequency to zero' do
user.user_option.update_columns(email_digests: true, digest_after_minutes: 10080)
post "/email/unsubscribe/#{key}.json", params: { mute_topic: "1" }
post "/email/unsubscribe/#{key}.json",
params: { digest_after_minutes: '0' }
expect(response.status).to eq(302)
expect(TopicUser.get(a_post.topic, user).notification_level).to eq(TopicUser.notification_levels[:muted])
end
expect(response.status).to eq(302)
it 'can unwatch category' do
cu = CategoryUser.create!(user_id: user.id,
category_id: a_post.topic.category_id,
notification_level: CategoryUser.notification_levels[:watching])
user.user_option.reload
post "/email/unsubscribe/#{key}.json", params: { unwatch_category: "1" }
expect(user.user_option.digest_after_minutes).to be_zero
expect(user.user_option.email_digests).to eq(false)
end
expect(response.status).to eq(302)
expect(CategoryUser.find_by(id: cu.id)).to eq(nil)
end
it 'can unwatch topic' do
p = Fabricate(:post)
key = UnsubscribeKey.create_key_for(p.user, p)
it 'can unwatch first post from category' do
cu = CategoryUser.create!(user_id: user.id,
category_id: a_post.topic.category_id,
notification_level: CategoryUser.notification_levels[:watching_first_post])
TopicUser.change(p.user_id, p.topic_id, notification_level: TopicUser.notification_levels[:watching])
post "/email/unsubscribe/#{key}.json", params: { unwatch_category: "1" }
post "/email/unsubscribe/#{key}.json",
params: { unwatch_topic: "1" }
expect(response.status).to eq(302)
expect(TopicUser.get(p.topic, p.user).notification_level).to eq(TopicUser.notification_levels[:tracking])
end
it 'can mute topic' do
p = Fabricate(:post)
key = UnsubscribeKey.create_key_for(p.user, p)
TopicUser.change(p.user_id, p.topic_id, notification_level: TopicUser.notification_levels[:watching])
post "/email/unsubscribe/#{key}.json",
params: { mute_topic: "1" }
expect(response.status).to eq(302)
expect(TopicUser.get(p.topic, p.user).notification_level).to eq(TopicUser.notification_levels[:muted])
end
it 'can unwatch category' do
p = Fabricate(:post)
key = UnsubscribeKey.create_key_for(p.user, p)
cu = CategoryUser.create!(user_id: p.user.id,
category_id: p.topic.category_id,
notification_level: CategoryUser.notification_levels[:watching])
post "/email/unsubscribe/#{key}.json",
params: { unwatch_category: "1" }
expect(response.status).to eq(302)
expect(CategoryUser.find_by(id: cu.id)).to eq(nil)
end
it 'can unwatch first post from category' do
p = Fabricate(:post)
key = UnsubscribeKey.create_key_for(p.user, p)
cu = CategoryUser.create!(user_id: p.user.id,
category_id: p.topic.category_id,
notification_level: CategoryUser.notification_levels[:watching_first_post])
post "/email/unsubscribe/#{key}.json",
params: { unwatch_category: "1" }
expect(response.status).to eq(302)
expect(CategoryUser.find_by(id: cu.id)).to eq(nil)
expect(response.status).to eq(302)
expect(CategoryUser.find_by(id: cu.id)).to eq(nil)
end
end
end
@ -149,6 +128,8 @@ RSpec.describe EmailController do
end
describe 'when topic is public' do
fab!(:topic) { Fabricate(:topic) }
it 'should return the right response' do
key = SecureRandom.hex
Discourse.cache.write(key, user.email)
@ -159,6 +140,8 @@ RSpec.describe EmailController do
end
describe 'when topic is private' do
fab!(:private_topic) { Fabricate(:private_message_topic) }
it 'should return the right response' do
key = SecureRandom.hex
Discourse.cache.write(key, user.email)
@ -177,10 +160,11 @@ RSpec.describe EmailController do
end
fab!(:user) { Fabricate(:user) }
let(:unsubscribe_key) { UnsubscribeKey.create_key_for(user, key_type) }
let(:unsubscribe_key) { UnsubscribeKey.create_key_for(user, key_type, post: post) }
context 'Unsubscribe from digest' do
let(:key_type) { 'digest' }
let(:key_type) { UnsubscribeKey::DIGEST_TYPE }
let(:post) { nil }
it 'displays log out button if wrong user logged in' do
sign_in(Fabricate(:admin))
@ -264,7 +248,7 @@ RSpec.describe EmailController do
context 'Unsubscribe from a post' do
fab!(:post) { Fabricate(:post) }
let(:user) { post.user }
let(:key_type) { post }
let(:key_type) { UnsubscribeKey::TOPIC_TYPE }
it 'correctly handles watched categories' do
cu = create_category_user(:watching)

View File

@ -646,9 +646,9 @@ describe UserMerger do
end
it "updates unsubscribe keys" do
UnsubscribeKey.create_key_for(source_user, "digest")
UnsubscribeKey.create_key_for(target_user, "digest")
UnsubscribeKey.create_key_for(walter, "digest")
UnsubscribeKey.create_key_for(source_user, UnsubscribeKey::DIGEST_TYPE)
UnsubscribeKey.create_key_for(target_user, UnsubscribeKey::DIGEST_TYPE)
UnsubscribeKey.create_key_for(walter, UnsubscribeKey::DIGEST_TYPE)
merge_users!