Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Mailchimp with a Flodesk integration #2162

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ gem 'tzinfo-data'
gem 'chosen-rails'
gem 'commonmarker'

gem 'gibbon', '~> 3.5.0'
gem 'faraday'

gem 'stripe'

Expand Down Expand Up @@ -84,6 +84,7 @@ group :development, :test do
gem 'fabrication'
gem 'faker'
gem 'launchy'
gem 'pry-rails'
gem 'pry-byebug'
gem 'pry-remote'
gem 'rspec-collection_matchers'
Expand Down
9 changes: 4 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,6 @@ GEM
foreman (0.88.1)
friendly_id (5.5.1)
activerecord (>= 4.0.0)
gibbon (3.5.0)
faraday (>= 1.0)
multi_json (>= 1.11.0)
globalid (1.2.1)
activesupport (>= 6.1)
haml (6.3.0)
Expand Down Expand Up @@ -231,7 +228,6 @@ GEM
mini_portile2 (2.8.8)
minitest (5.25.1)
msgpack (1.7.2)
multi_json (1.15.0)
multi_xml (0.6.0)
net-http (0.4.1)
uri
Expand Down Expand Up @@ -294,6 +290,8 @@ GEM
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (>= 0.13, < 0.15)
pry-rails (0.3.11)
pry (>= 0.13.0)
pry-remote (0.1.8)
pry (~> 0.9)
slop (~> 3.0)
Expand Down Expand Up @@ -514,10 +512,10 @@ DEPENDENCIES
dotenv-rails
fabrication
faker
faraday
font_awesome5_rails
foreman
friendly_id
gibbon (~> 3.5.0)
haml
high_voltage
icalendar
Expand All @@ -538,6 +536,7 @@ DEPENDENCIES
pickadate-rails
premailer-rails
pry-byebug
pry-rails
pry-remote
public_activity
puma (~> 6.4)
Expand Down
2 changes: 1 addition & 1 deletion app/views/admin/contacts/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
%th Sponsor
%th Contact name
%th Contact email
%th Mailchimp
%th Mailing list
%tbody
- @contacts.each do |contact|
%tr
Expand Down
59 changes: 29 additions & 30 deletions app/views/shared/_newsletter.html.haml
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
- content_for :head do
:javascript
(function(w, d, t, h, s, n) {
w.FlodeskObject = n;
var fn = function() {
(w[n].q = w[n].q || []).push(arguments);
};
w[n] = w[n] || fn;
var f = d.getElementsByTagName(t)[0];
var v = '?v=' + Math.floor(new Date().getTime() / (120 * 1000)) * 60;
var sm = d.createElement(t);
sm.async = true;
sm.type = 'module';
sm.src = h + s + '.mjs' + v;
f.parentNode.insertBefore(sm, f);
var sn = d.createElement(t);
sn.async = true;
sn.noModule = true;
sn.src = h + s + '.js' + v;
f.parentNode.insertBefore(sn, f);
})(window, document, 'script', 'https://assets.flodesk.com', '/universal', 'fd');

.row.justify-content-md-center
.col-md-8
%h2= t('homepage.newsletter.title')
%p= t('homepage.newsletter.description')

-# Mailchimp Signup Form
#mc_embed_signup
%form.validate#mc-embedded-subscribe-form{ action: "https://codebar.us8.list-manage.com/subscribe/post?u=b4652d85b385945c79f2ffa2e&amp;id=3798974cb3",
method: "post",
name: "mc-embedded-subscribe-form",
target: "_blank",
novalidate: true }
#mc_embed_signup_scroll
#indicates-required
.mc-field-group.mb-3
.d-flex.justify-content-between
%label.form-label{ for: "mce-EMAIL" }
Email Address
%span.asterisk *
%small
%span.asterisk * indicates required
%input.required.email.form-control#mce-EMAIL{ type: 'email', value: '', name: 'EMAIL' }

.clear#mce-responses
.response#mce-error-response{ style: 'display:none' }
.response#mce-success-response{ style: 'display:none' }

-# real people should not fill this in and expect good things - do not remove this or risk form bot signups
%div{ style: 'position: absolute; left: -5000px;', 'aria-hidden': 'true' }
%input{ type: 'text', name: 'b_b4652d85b385945c79f2ffa2e_3798974cb3', tabindex: '-1', value: '' }
.d-flex.justify-content-between.align-items-center
%input.btn.btn-primary#mc-embedded-subscribe{ type: 'submit', value: 'Subscribe', name: 'subscribe' }
-# Flodesk signup form
#fd-form-678b693a7ae9331608185173
:javascript
if (window.fd) window.fd('form', {
formId: '678b693a7ae9331608185173',
containerEl: '#fd-form-678b693a7ae9331608185173'
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before release, this snippet has to come from codebar’s Flodesk account (right now this came from my test one).

9 changes: 9 additions & 0 deletions config/initializers/flodesk.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'services/flodesk'

key = Rails.env.test? ? 'test' : ENV['FLODESK_KEY']

logger.warn 'Missing key for Flodesk' unless key

Flodesk::Client.api_key = key
Flodesk::Client.complete_timeout = 15
Flodesk::Client.open_timeout = 15
6 changes: 0 additions & 6 deletions config/initializers/mailchimp.rb

This file was deleted.

9 changes: 3 additions & 6 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,6 @@ en:
workshop_email_subject: "Regarding hosting a workshop"
chapters:
title: "Chapters"
newsletter:
title: Subscribe to our newsletter
description: Stay connected with the codebar community! Get regular updates on upcoming events, job opportunities, scholarships, and other learning resources.
events:
upcoming: "Upcoming Events"
donation_platforms:
Expand Down Expand Up @@ -567,7 +564,7 @@ en:
l2: Compliance with legal obligations
how_we_share:
title: 1.2 How we share your information
p1_html: 'When you subscribe to an event, we share your name with our host companies as we are required to provide a list with all attendee names when you subscribe to any of our events. We **do not share** your email address or any other personal information.'
p1_html: 'When you subscribe to an event, we share your name with our host companies as we are required to provide a list with all attendee names when you subscribe to any of our events. We **do not share** your email address or any other personal information.'
legal_basis:
title: 1.3 Legal basis for our use of your personal information
p1: As set out in the table above, sometimes the legal basis on which we collect and process your data is because our legitimate interests make the processing necessary, and those legitimate interests are not overridden by your interests or fundamental rights and freedoms. For example, we collect and store your data in order to process event information, and to ensure the efficient running and promotion of workshops.
Expand Down Expand Up @@ -599,10 +596,10 @@ en:
p1: We reserve the right to modify or update this Privacy Policy at any time in accordance with this provision. If we make changes to this Privacy Policy, we will post the revised Privacy Policy on the codebar website. Please regularly review https://codebar.io/privacy-policy to check for any updates or changes to our Privacy Policy. The date this Privacy Policy was last revised is identified at the top of this page.
cookies:
title: 7. Cookies
p1_html: 'We use cookies to recognise you and your location. You can control cookies through your browser settings and other tools. For more information read our [cookie policy](https://codebar.io/cookie-policy).'
p1_html: 'We use cookies to recognise you and your location. You can control cookies through your browser settings and other tools. For more information read our [cookie policy](https://codebar.io/cookie-policy).'
contact:
title: 8. Contact
p1: "If you have any questions or complaints about this Privacy Policy or our information handling practices, you may email us at company@codebar.io stating 'Data inquiry' in the subject title, or by postal mail at: codebar Ltd, International House, 101 King's Cross Road, London, WC1X 9LP."
p1: "If you have any questions or complaints about this Privacy Policy or our information handling practices, you may email us at company@codebar.io stating 'Data inquiry' in the subject title, or by postal mail at: codebar Ltd, International House, 101 King's Cross Road, London, WC1X 9LP."
matyikriszta marked this conversation as resolved.
Show resolved Hide resolved
breach:
title: What happens if you violate codebar’s Code of Conduct?
opening_para: All codebar events are dedicated to providing a harassment-free experience for everyone, regardless of gender, sexual orientation, disability, physical appearance, body size, race, or religion. We do not tolerate harassment towards any community member or organiser in any form. Harassment includes any type of aggressive behaviour or offensive verbal comments related to gender, sexual orientation, disability, physical appearance, body size, race, religion, sexual images in public spaces, deliberate intimidation, stalking, following, harassing photography or recording, sustained disruption of talks or other events, inappropriate physical contact, and unwelcome sexual attention.
Expand Down
157 changes: 157 additions & 0 deletions lib/services/flodesk.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
require 'faraday'

module Flodesk
# Subscriber status
ACTIVE = 'active'.freeze

class Client
API_ENDPOINT = 'https://api.flodesk.com/v1/'.freeze
DEFAULT_TIMEOUT = 60

class_attribute :api_key
class_attribute :complete_timeout
class_attribute :open_timeout

# Allows for setting these values in `config/initializers/flodesk.rb`
class << self
def api_key
@@api_key
end

def complete_timeout
@@complete_timeout
end

def open_timeout
@@open_timeout
end
end

attr_accessor :api_endpoint, :debug, :logger

# We need 3 actions:
#
# 1. subscribe --> params(list_id, email, first_name, last_name)
# Documentation: https://developers.flodesk.com/#tag/subscriber/operation/createOrUpdateSubscriber
# Endpoint: https://api.flodesk.com/v1/subscribers
#
# 2. unsubscribe --> params(list_id, email)
# Documentation: https://developers.flodesk.com/#tag/subscriber/operation/removeSubscriberFromSegments
# Endpoint: https://api.flodesk.com/v1/subscribers/{id_or_email}/segments
#
# 3. subscribed? --> params(list_id, email)
# Documentation: https://developers.flodesk.com/#tag/subscriber/operation/retrieveSubscriber
# Endpoint: https://api.flodesk.com/v1/subscribers/{id_or_email}

def initialize(api_key: nil, complete_timeout: nil, open_timeout: nil)
@api_key = api_key || self.class.api_key || ENV['FLODESK_KEY']
@api_key = @api_key.strip if @api_key

@complete_timeout = complete_timeout || self.class.complete_timeout || DEFAULT_TIMEOUT
@open_timeout = open_timeout || self.class.open_timeout || DEFAULT_TIMEOUT
end

def disabled?
!@api_key
end

def subscribe(email:, first_name:, last_name:, segment_ids:, double_optin: true)
body = { email:, first_name:, last_name:, segment_ids:, double_optin: }

request(:post, 'subscribers', body)
end

def unsubscribe(email:, segment_ids:)
body = { segment_ids: }

request(:delete, "subscribers/#{email}/segments", body)
end

def subscribed?(email:, segment_ids:)
response = request(:get, "subscribers/#{email}")
body = OpenStruct.new(response[:body])

# If not subscribed, stop here
is_active = body.status.to_s.eql?(ACTIVE)
return false unless is_active

segment_ids.all? do |segment_id|
body.segments.any? { |segment| segment_id.to_s.eql?(segment['id']) }
end
end

private

def connection
options = {
headers: {
user_agent: 'codebar (codebar.io)'
},
request: {
timeout: @complete_timeout,
open_timeout: @open_timeout
}
}

# https://lostisland.github.io/faraday/#/customization/request-options
@connection ||= Faraday.new(url: API_ENDPOINT, **options) do |config|
config.request :json

# Beware: the order of these lines matter. Examples:
# - https://mattbrictson.com/blog/advanced-http-techniques-in-ruby#pitfall-raise_error-and-logger-in-the-wrong-order
# - https://stackoverflow.com/a/67182791/590525
config.response :raise_error
config.response :logger, Rails.logger, headers: true, bodies: true, log_level: :debug
config.response :json

# https://developers.flodesk.com/#section/Authentication/api_key
config.request :authorization, 'Basic', -> { @api_key }
end
end

def request(http_method, endpoint, body = {})
# Faraday's `delete` does not accept body at the time of writing
response = if http_method == :delete
connection.run_request(http_method, endpoint, body, nil)
else
connection.public_send(http_method, endpoint, body)
end

{
status: response.status,
body: JSON.parse(response.body)
}
rescue Faraday::Error => e
FlodeskError.new(e.response_body['message'], {
raw_body: e.response_body,
status_code: e.response_status
})
end
end

# Inspired by https://github.com/amro/gibbon/blob/master/lib/gibbon/mailchimp_error.rb
class FlodeskError < StandardError
attr_reader :status_code, :raw_body

def initialize(message = '', params = {})
@status_code = params[:status_code]
@raw_body = params[:raw_body]

super(message)
end

def to_s
"#{super} #{instance_variables_to_s}"
end

private

def instance_variables_to_s
%i[status_code raw_body].map do |attr|
attr_value = send(attr)

"@#{attr}=#{attr_value.inspect}"
end.join(', ')
end
end
end
Loading
Loading