From 050a2e27269c941024fde1e7fc276f1deab4f62f Mon Sep 17 00:00:00 2001 From: Michael Kohl Date: Wed, 12 Feb 2020 02:25:48 +0700 Subject: [PATCH] Implement Mailchimp unsubscribe webhook (#5804) [deploy] --- .../mailchimp_unsubscribes_controller.rb | 30 +++++++++++++++++++ app/models/site_config.rb | 4 +++ config/routes.rb | 4 +++ .../mailchimp/unsubscribe_spec.rb | 29 ++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 app/controllers/incoming_webhooks/mailchimp_unsubscribes_controller.rb create mode 100644 spec/requests/incoming_webhooks/mailchimp/unsubscribe_spec.rb diff --git a/app/controllers/incoming_webhooks/mailchimp_unsubscribes_controller.rb b/app/controllers/incoming_webhooks/mailchimp_unsubscribes_controller.rb new file mode 100644 index 000000000..8c4237101 --- /dev/null +++ b/app/controllers/incoming_webhooks/mailchimp_unsubscribes_controller.rb @@ -0,0 +1,30 @@ +class IncomingWebhooks::MailchimpUnsubscribesController < ApplicationController + class InvalidListID < StandardError; end + + LIST_MAPPINGS = { + mailchimp_newsletter_id: :email_newsletter, + mailchimp_sustaining_members_id: :email_membership_newsletter, + mailchimp_tag_moderators_id: :email_tag_mod_newsletter, + mailchimp_community_moderators_id: :email_community_mod_newsletter + }.freeze + + def create + not_authorized unless valid_secret? + user = User.find_by!(email: params.dig(:data, :email)) + user.update(email_type => false) + end + + private + + def valid_secret? + params[:secret] == SiteConfig.mailchimp_incoming_webhook_secret + end + + def email_type + list_id = params.dig(:data, :list_id) + key = LIST_MAPPINGS.keys.detect { |k| SiteConfig.public_send(k) == list_id } + raise InvalidListID unless key + + LIST_MAPPINGS[key] + end +end diff --git a/app/models/site_config.rb b/app/models/site_config.rb index 9c8110dff..2d3f515da 100644 --- a/app/models/site_config.rb +++ b/app/models/site_config.rb @@ -37,6 +37,10 @@ class SiteConfig < RailsSettings::Base field :mailchimp_tag_moderators_id, type: :string, default: "" field :mailchimp_community_moderators_id, type: :string, default: "" + # Mailchimp webhook secret. Part of the callback URL in the Mailchimp settings. + # + field :mailchimp_incoming_webhook_secret, type: :string, default: "" + # Email digest frequency field :periodic_email_digest_max, type: :integer, default: 0 field :periodic_email_digest_min, type: :integer, default: 2 diff --git a/config/routes.rb b/config/routes.rb index 1dfafdbf0..458764c3b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -150,6 +150,10 @@ Rails.application.routes.draw do resources :reads, only: [:create] end + namespace :incoming_webhooks do + post "/mailchimp/:secret/unsubscribe", to: "mailchimp_unsubscribes#create", as: :mailchimp_unsubscribe + end + resources :messages, only: [:create] resources :chat_channels, only: %i[index show create update] resources :chat_channel_memberships, only: %i[create update destroy] diff --git a/spec/requests/incoming_webhooks/mailchimp/unsubscribe_spec.rb b/spec/requests/incoming_webhooks/mailchimp/unsubscribe_spec.rb new file mode 100644 index 000000000..b56d3b4d1 --- /dev/null +++ b/spec/requests/incoming_webhooks/mailchimp/unsubscribe_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +RSpec.describe "IncomingWebhooks::MailchimpUnsubscribesController", type: :request do + let(:user) { create(:user, email_digest_periodic: true) } + + describe "POST /webhooks/mailchimp/:secret/unsubscribe" do + let(:secret) { "secret" } + let(:list_id) { "1234" } + let(:params) { { data: { email: user.email, list_id: list_id } } } + + before do + allow(SiteConfig).to receive(:mailchimp_incoming_webhook_secret).and_return(secret) + end + + it "return not authorized if the secret is incorrect" do + expect do + post "/incoming_webhooks/mailchimp/wrong_secret/unsubscribe", params: params + end.to raise_error(Pundit::NotAuthorizedError) + end + + it "unsubscribes the user if the secret is correct" do + SiteConfig.mailchimp_newsletter_id = list_id + + expect do + post "/incoming_webhooks/mailchimp/#{secret}/unsubscribe", params: params + end.to change { user.reload.email_newsletter }.from(true).to(false) + end + end +end