Skip to content

Commit 3493d32

Browse files
authored
Merge pull request #2162 from gnclmorais/me-from-mailchimp-to-flowdesk
Replace Mailchimp with a Flodesk integration
2 parents 1dd972b + 720a7e9 commit 3493d32

File tree

12 files changed

+371
-141
lines changed

12 files changed

+371
-141
lines changed

Gemfile

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ gem 'tzinfo-data'
4848
gem 'chosen-rails'
4949
gem 'commonmarker'
5050

51-
gem 'gibbon', '~> 3.5.0'
51+
gem 'faraday'
5252

5353
gem 'stripe'
5454

@@ -84,6 +84,7 @@ group :development, :test do
8484
gem 'fabrication'
8585
gem 'faker'
8686
gem 'launchy'
87+
gem 'pry-rails'
8788
gem 'pry-byebug'
8889
gem 'pry-remote'
8990
gem 'rspec-collection_matchers'

Gemfile.lock

+4-5
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,6 @@ GEM
173173
foreman (0.88.1)
174174
friendly_id (5.5.1)
175175
activerecord (>= 4.0.0)
176-
gibbon (3.5.0)
177-
faraday (>= 1.0)
178-
multi_json (>= 1.11.0)
179176
globalid (1.2.1)
180177
activesupport (>= 6.1)
181178
haml (6.3.0)
@@ -237,7 +234,6 @@ GEM
237234
mini_portile2 (2.8.8)
238235
minitest (5.25.4)
239236
msgpack (1.7.5)
240-
multi_json (1.15.0)
241237
multi_xml (0.6.0)
242238
net-http (0.4.1)
243239
uri
@@ -301,6 +297,8 @@ GEM
301297
pry-byebug (3.10.1)
302298
byebug (~> 11.0)
303299
pry (>= 0.13, < 0.15)
300+
pry-rails (0.3.11)
301+
pry (>= 0.13.0)
304302
pry-remote (0.1.8)
305303
pry (~> 0.9)
306304
slop (~> 3.0)
@@ -515,10 +513,10 @@ DEPENDENCIES
515513
dotenv-rails
516514
fabrication
517515
faker
516+
faraday
518517
font_awesome5_rails
519518
foreman
520519
friendly_id
521-
gibbon (~> 3.5.0)
522520
haml
523521
high_voltage
524522
icalendar
@@ -539,6 +537,7 @@ DEPENDENCIES
539537
pickadate-rails
540538
premailer-rails
541539
pry-byebug
540+
pry-rails
542541
pry-remote
543542
public_activity
544543
puma (~> 6.4)

app/views/admin/contacts/index.html.haml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
%th Sponsor
1515
%th Contact name
1616
%th Contact email
17-
%th Mailchimp
17+
%th Mailing list
1818
%tbody
1919
- @contacts.each do |contact|
2020
%tr
+29-30
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
1+
- content_for :head do
2+
:javascript
3+
(function(w, d, t, h, s, n) {
4+
w.FlodeskObject = n;
5+
var fn = function() {
6+
(w[n].q = w[n].q || []).push(arguments);
7+
};
8+
w[n] = w[n] || fn;
9+
var f = d.getElementsByTagName(t)[0];
10+
var v = '?v=' + Math.floor(new Date().getTime() / (120 * 1000)) * 60;
11+
var sm = d.createElement(t);
12+
sm.async = true;
13+
sm.type = 'module';
14+
sm.src = h + s + '.mjs' + v;
15+
f.parentNode.insertBefore(sm, f);
16+
var sn = d.createElement(t);
17+
sn.async = true;
18+
sn.noModule = true;
19+
sn.src = h + s + '.js' + v;
20+
f.parentNode.insertBefore(sn, f);
21+
})(window, document, 'script', 'https://assets.flodesk.com', '/universal', 'fd');
22+
123
.row.justify-content-md-center
224
.col-md-8
3-
%h2= t('homepage.newsletter.title')
4-
%p= t('homepage.newsletter.description')
5-
6-
-# Mailchimp Signup Form
7-
#mc_embed_signup
8-
%form.validate#mc-embedded-subscribe-form{ action: "https://codebar.us8.list-manage.com/subscribe/post?u=b4652d85b385945c79f2ffa2e&amp;id=3798974cb3",
9-
method: "post",
10-
name: "mc-embedded-subscribe-form",
11-
target: "_blank",
12-
novalidate: true }
13-
#mc_embed_signup_scroll
14-
#indicates-required
15-
.mc-field-group.mb-3
16-
.d-flex.justify-content-between
17-
%label.form-label{ for: "mce-EMAIL" }
18-
Email Address
19-
%span.asterisk *
20-
%small
21-
%span.asterisk * indicates required
22-
%input.required.email.form-control#mce-EMAIL{ type: 'email', value: '', name: 'EMAIL' }
23-
24-
.clear#mce-responses
25-
.response#mce-error-response{ style: 'display:none' }
26-
.response#mce-success-response{ style: 'display:none' }
27-
28-
-# real people should not fill this in and expect good things - do not remove this or risk form bot signups
29-
%div{ style: 'position: absolute; left: -5000px;', 'aria-hidden': 'true' }
30-
%input{ type: 'text', name: 'b_b4652d85b385945c79f2ffa2e_3798974cb3', tabindex: '-1', value: '' }
31-
.d-flex.justify-content-between.align-items-center
32-
%input.btn.btn-primary#mc-embedded-subscribe{ type: 'submit', value: 'Subscribe', name: 'subscribe' }
25+
-# Flodesk signup form
26+
#fd-form-67b7415796213859df0a8a86
27+
:javascript
28+
if (window.fd) window.fd('form', {
29+
formId: '67b7415796213859df0a8a86',
30+
containerEl: '#fd-form-67b7415796213859df0a8a86'
31+
});

config/initializers/flodesk.rb

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
require 'services/flodesk'
2+
3+
key = Rails.env.test? ? 'test' : ENV['FLODESK_KEY']
4+
Rails.logger.warn 'Missing FLODESK_KEY environment variable' unless key
5+
6+
# We use Flodesk for newsletter sending, so we need both values
7+
Rails.logger.warn 'Missing NEWSLETTER_ID environment variable' unless ENV['NEWSLETTER_ID']
8+
9+
Flodesk::Client.api_key = key
10+
Flodesk::Client.complete_timeout = 15
11+
Flodesk::Client.open_timeout = 15

config/initializers/mailchimp.rb

-6
This file was deleted.

config/locales/en.yml

+3-6
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,6 @@ en:
9999
workshop_email_subject: "Regarding hosting a workshop"
100100
chapters:
101101
title: "Chapters"
102-
newsletter:
103-
title: Subscribe to our newsletter
104-
description: Stay connected with the codebar community! Get regular updates on upcoming events, job opportunities, scholarships, and other learning resources.
105102
events:
106103
upcoming: "Upcoming Events"
107104
donation_platforms:
@@ -567,7 +564,7 @@ en:
567564
l2: Compliance with legal obligations
568565
how_we_share:
569566
title: 1.2 How we share your information
570-
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.'
567+
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.'
571568
legal_basis:
572569
title: 1.3 Legal basis for our use of your personal information
573570
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.
@@ -599,10 +596,10 @@ en:
599596
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.
600597
cookies:
601598
title: 7. Cookies
602-
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).'
599+
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).'
603600
contact:
604601
title: 8. Contact
605-
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."
602+
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."
606603
breach:
607604
title: What happens if you violate codebar’s Code of Conduct?
608605
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.

lib/services/flodesk.rb

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
require 'faraday'
2+
3+
module Flodesk
4+
# Subscriber status
5+
ACTIVE = 'active'.freeze
6+
7+
class Client
8+
API_ENDPOINT = 'https://api.flodesk.com/v1/'.freeze
9+
DEFAULT_TIMEOUT = 60
10+
11+
class_attribute :api_key
12+
class_attribute :complete_timeout
13+
class_attribute :open_timeout
14+
15+
# Allows for setting these values in `config/initializers/flodesk.rb`
16+
class << self
17+
def api_key
18+
@@api_key
19+
end
20+
21+
def complete_timeout
22+
@@complete_timeout
23+
end
24+
25+
def open_timeout
26+
@@open_timeout
27+
end
28+
end
29+
30+
attr_accessor :api_endpoint, :debug, :logger
31+
32+
# We need 3 actions:
33+
#
34+
# 1. subscribe --> params(list_id, email, first_name, last_name)
35+
# Documentation: https://developers.flodesk.com/#tag/subscriber/operation/createOrUpdateSubscriber
36+
# Endpoint: https://api.flodesk.com/v1/subscribers
37+
#
38+
# 2. unsubscribe --> params(list_id, email)
39+
# Documentation: https://developers.flodesk.com/#tag/subscriber/operation/removeSubscriberFromSegments
40+
# Endpoint: https://api.flodesk.com/v1/subscribers/{id_or_email}/segments
41+
#
42+
# 3. subscribed? --> params(list_id, email)
43+
# Documentation: https://developers.flodesk.com/#tag/subscriber/operation/retrieveSubscriber
44+
# Endpoint: https://api.flodesk.com/v1/subscribers/{id_or_email}
45+
46+
def initialize(api_key: nil, complete_timeout: nil, open_timeout: nil)
47+
@api_key = api_key || self.class.api_key || ENV['FLODESK_KEY']
48+
@api_key = @api_key.strip if @api_key
49+
50+
@complete_timeout = complete_timeout || self.class.complete_timeout || DEFAULT_TIMEOUT
51+
@open_timeout = open_timeout || self.class.open_timeout || DEFAULT_TIMEOUT
52+
end
53+
54+
def disabled?
55+
!@api_key
56+
end
57+
58+
def subscribe(email:, first_name:, last_name:, segment_ids:, double_optin: true)
59+
body = { email:, first_name:, last_name:, segment_ids:, double_optin: }
60+
61+
request(:post, 'subscribers', body)
62+
end
63+
64+
def unsubscribe(email:, segment_ids:)
65+
body = { segment_ids: }
66+
67+
request(:delete, "subscribers/#{email}/segments", body)
68+
end
69+
70+
def subscribed?(email:, segment_ids:)
71+
response = request(:get, "subscribers/#{email}")
72+
response => { status:, body: }
73+
74+
return false if response.is_a?(FlodeskError) && status == 404
75+
76+
body.symbolize_keys => { status:, segments: }
77+
78+
# If not subscribed, stop here
79+
is_active = status.to_s.eql?(ACTIVE)
80+
return false unless is_active
81+
82+
segment_ids.all? do |segment_id|
83+
segments.any? { |segment| segment_id.to_s.eql?(segment.symbolize_keys[:id]) }
84+
end
85+
end
86+
87+
private
88+
89+
def connection
90+
options = {
91+
headers: {
92+
user_agent: 'codebar (codebar.io)'
93+
},
94+
request: {
95+
timeout: @complete_timeout,
96+
open_timeout: @open_timeout
97+
}
98+
}
99+
100+
# https://lostisland.github.io/faraday/#/customization/request-options
101+
@connection ||= Faraday.new(url: API_ENDPOINT, **options) do |config|
102+
config.request :json
103+
104+
# Beware: the order of these lines matter. Examples:
105+
# - https://mattbrictson.com/blog/advanced-http-techniques-in-ruby#pitfall-raise_error-and-logger-in-the-wrong-order
106+
# - https://stackoverflow.com/a/67182791/590525
107+
config.response :raise_error
108+
config.response :logger, Rails.logger, headers: true, bodies: true, log_level: :debug
109+
config.response :json
110+
111+
# https://developers.flodesk.com/#section/Authentication/api_key
112+
config.request :authorization, 'Basic', -> { @api_key }
113+
end
114+
end
115+
116+
def request(http_method, endpoint, body = {})
117+
# Faraday's `delete` does not accept body at the time of writing
118+
response = if http_method == :delete
119+
connection.run_request(http_method, endpoint, body, nil)
120+
else
121+
connection.public_send(http_method, endpoint, body)
122+
end
123+
124+
{
125+
status: response.status,
126+
body: response.body
127+
}
128+
rescue Faraday::Error => e
129+
FlodeskError.new(e.response_body['message'], {
130+
raw_body: e.response_body,
131+
status_code: e.response_status
132+
})
133+
end
134+
end
135+
136+
# Inspired by https://github.com/amro/gibbon/blob/master/lib/gibbon/mailchimp_error.rb
137+
class FlodeskError < StandardError
138+
attr_reader :status_code, :raw_body
139+
140+
def initialize(message = '', params = {})
141+
@status_code = params[:status_code]
142+
@raw_body = params[:raw_body]
143+
144+
super(message)
145+
end
146+
147+
def to_s
148+
"#{super} #{instance_variables_to_s}"
149+
end
150+
151+
private
152+
153+
def instance_variables_to_s
154+
%i[status_code raw_body].map do |attr|
155+
attr_value = send(attr)
156+
157+
"@#{attr}=#{attr_value.inspect}"
158+
end.join(', ')
159+
end
160+
end
161+
end

0 commit comments

Comments
 (0)