sm_sms_campaign_webhook 0.1.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +243 -4
- data/app/controllers/sm_sms_campaign_webhook/application_controller.rb +22 -0
- data/app/controllers/sm_sms_campaign_webhook/webhook_controller.rb +25 -0
- data/app/exceptions/sm_sms_campaign_webhook/error.rb +6 -0
- data/app/exceptions/sm_sms_campaign_webhook/invalid_payload.rb +6 -0
- data/app/exceptions/sm_sms_campaign_webhook/invalid_payload_value.rb +6 -0
- data/app/exceptions/sm_sms_campaign_webhook/missing_config_error.rb +6 -0
- data/app/exceptions/sm_sms_campaign_webhook/payload_dispatch_error.rb +6 -0
- data/app/jobs/sm_sms_campaign_webhook/application_job.rb +7 -0
- data/app/jobs/sm_sms_campaign_webhook/dispatch_payload_job.rb +14 -0
- data/app/jobs/sm_sms_campaign_webhook/process_campaign_engagement_job.rb +14 -0
- data/app/models/sm_sms_campaign_webhook/campaign_engagement.rb +216 -0
- data/app/models/sm_sms_campaign_webhook/campaign_engagement/answer.rb +72 -0
- data/app/operations/sm_sms_campaign_webhook/campaign_engagement_operation.rb +36 -0
- data/app/operations/sm_sms_campaign_webhook/payload_operation.rb +26 -0
- data/app/processors/sm_sms_campaign_webhook/default_processor.rb +11 -0
- data/app/processors/sm_sms_campaign_webhook/processable.rb +21 -0
- data/config/routes.rb +7 -0
- data/lib/generators/sm_sms_campaign_webhook/install/USAGE +12 -0
- data/lib/generators/sm_sms_campaign_webhook/install/install_generator.rb +35 -0
- data/lib/generators/sm_sms_campaign_webhook/install/templates/README +25 -0
- data/lib/generators/sm_sms_campaign_webhook/install/templates/sm_sms_campaign_webhook.rb +9 -0
- data/lib/generators/sm_sms_campaign_webhook/install/templates/sms_payload_processor.rb.erb +13 -0
- data/lib/sm_sms_campaign_webhook.rb +25 -2
- data/lib/sm_sms_campaign_webhook/engine.rb +11 -0
- data/lib/sm_sms_campaign_webhook/version.rb +1 -1
- data/sm_sms_campaign_webhook.gemspec +27 -19
- metadata +66 -16
- data/.gitignore +0 -16
- data/.rspec +0 -3
- data/.travis.yml +0 -18
- data/Gemfile +0 -4
- data/Rakefile +0 -6
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ee7507ed2364d7482af89dc6a0fda95b689ffe0c9e12f12b4a8cd8f1edf07d0
|
4
|
+
data.tar.gz: a0a7d6b50a54b6d518d5ef870727217b7c3cee1816ef1038cab61e6f5d506907
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb4b43bfc4733b09458fea2a6c008f33a3a271918ae16cecd66571b6f97bc221f67748c8f5b9b7ef6ce811232ac96b5ce44433da6cb64d91083979b625f4a8f2
|
7
|
+
data.tar.gz: b078c3040867520148ad9e129a6845100c82d277a4e68b60beb8b51495876c2915251c13f208a0e3326f14dbe64c46e4355077a95719d4e5abcefe58569f55b9
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [1.0.0] - 2019-07-26
|
10
|
+
### Added
|
11
|
+
- Mountable Rails engine as API
|
12
|
+
- POST /api/webhook resource requiring JSON payload for asynchronous dispatching and processing
|
13
|
+
- Require inbound POST requests be authorization requests with an auth token
|
14
|
+
- Support for Rails 5.2.x, 6.0.x
|
15
|
+
- Data models for campaign engagement event payloads
|
16
|
+
- Helper method to get specific campaign engagement answer
|
17
|
+
- Payload operation library to dispatch and process deserialized JSON with supported event data modeling
|
18
|
+
- Processable behavior definition for app implementors
|
19
|
+
- Default processor mixing in processable behavior with noisy errors
|
20
|
+
- ActiveJob library for asynchronous handling of payload dispatching and processing
|
21
|
+
- Configuration support for required app implementation values
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
- Require Ruby >= 2.5
|
25
|
+
- CI to test against Ruby 2.5.5, 2.6.3
|
26
|
+
- CI to test against Rails 5.2.x, 6.0.x
|
27
|
+
|
28
|
+
## [0.1.1] - 2019-07-16
|
29
|
+
### Changed
|
30
|
+
- Changelog URI to correct value in gemspec
|
31
|
+
|
9
32
|
## [0.1.0] - 2019-07-16
|
10
33
|
### Added
|
11
34
|
- NOOP gem configured for development
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
# SmSmsCampaignWebhook
|
1
|
+
# SmSmsCampaignWebhook
|
2
|
+
|
3
|
+
![Southern Made - Galaxy Logo](https://raw.github.com/SouthernMade/sm_sms_campaign_webhook/develop/logo_galaxymark.png) by [Southern Made](https://www.southernmade.com/)
|
2
4
|
|
3
5
|
[![Gem Version](https://badge.fury.io/rb/sm_sms_campaign_webhook.svg)](https://badge.fury.io/rb/sm_sms_campaign_webhook)
|
4
6
|
[![Travis Build Status](https://travis-ci.org/SouthernMade/sm_sms_campaign_webhook.svg?branch=develop)](https://travis-ci.org/SouthernMade/sm_sms_campaign_webhook)
|
@@ -15,12 +17,36 @@ This gem will help app implementors using [Rails](https://rubyonrails.org) setup
|
|
15
17
|
|
16
18
|
Work closely with your Southern Made project manager to gather details about what needs to be tracked, what fields to verify, and which scenarios are expected to be supported!
|
17
19
|
|
20
|
+
## Table of Contents
|
21
|
+
|
22
|
+
- [Installation](#installation)
|
23
|
+
- [Configuration](#configuration)
|
24
|
+
- [Auto Generate Config](#auto-generate-config)
|
25
|
+
- [Webhook Auth Token](#webhook-auth-token)
|
26
|
+
- [ActiveJob](#activejob)
|
27
|
+
- [Mount the Webhook Engine](#mount-the-webhook-engine)
|
28
|
+
- [Webhook Initializer](#webhook-initializer)
|
29
|
+
- [Payload Processor](#payload-processor)
|
30
|
+
- [Usage](#usage)
|
31
|
+
- [Campaign Engagement](#campaign-engagement)
|
32
|
+
- [Processor Expections](#processor-expections)
|
33
|
+
- [Campaign Engagement Data Model](#campaign-engagement-data-model)
|
34
|
+
- [Campaign Engagement Answer Data Model](#campaign-engagement-answer-data-model)
|
35
|
+
- [Development](#development)
|
36
|
+
- [Versioning](#versioning)
|
37
|
+
- [Testing](#testing)
|
38
|
+
- [Documentation](#documentation)
|
39
|
+
- [Contributing](#contributing)
|
40
|
+
- [License](#license)
|
41
|
+
|
18
42
|
## Installation
|
19
43
|
|
44
|
+
This gem is tested with Rails 5.2.x, 6.0.x versions.
|
45
|
+
|
20
46
|
Add this line to your application's Gemfile:
|
21
47
|
|
22
48
|
```ruby
|
23
|
-
gem 'sm_sms_campaign_webhook'
|
49
|
+
gem 'sm_sms_campaign_webhook', '~> 1.0'
|
24
50
|
```
|
25
51
|
|
26
52
|
And then execute:
|
@@ -31,9 +57,223 @@ Or install it yourself as:
|
|
31
57
|
|
32
58
|
$ gem install sm_sms_campaign_webhook
|
33
59
|
|
60
|
+
## Configuration
|
61
|
+
|
62
|
+
These are the steps to configure your app to be ready to capture SMS campaign service payloads.
|
63
|
+
|
64
|
+
### Auto Generate Config
|
65
|
+
|
66
|
+
You can setup most app configuration by running the generator:
|
67
|
+
|
68
|
+
```
|
69
|
+
$ bundle exec rails generate sm_sms_campaign_webhook:install
|
70
|
+
```
|
71
|
+
|
72
|
+
Some things will still require manual configuration and will be identified after generation:
|
73
|
+
|
74
|
+
- [Webhook Auth Token](#webhook-auth-token)
|
75
|
+
- [ActiveJob](#activejob)
|
76
|
+
|
77
|
+
After that, be sure to read the [Usage](#usage) section for payload processor details!
|
78
|
+
|
79
|
+
If you prefer to setup everything by hand, be sure to check out:
|
80
|
+
|
81
|
+
- [Webhook Initializer](#webhook-initializer)
|
82
|
+
- [Payload Processor](#payload-processor)
|
83
|
+
|
84
|
+
### Webhook Auth Token
|
85
|
+
|
86
|
+
The `SM_SMS_CAMPAIGN_WEBHOOK_AUTH_TOKEN` value is required to be an `ENV` value to avoid leaking production values. It will be used to authorize payload requests from the SMS campaign service.
|
87
|
+
|
88
|
+
Set this value using the rails secret generator:
|
89
|
+
|
90
|
+
```
|
91
|
+
$ bundle exec rails secret
|
92
|
+
```
|
93
|
+
|
94
|
+
And copy the result to your `.env` or applicable config file:
|
95
|
+
|
96
|
+
```
|
97
|
+
SM_SMS_CAMPAIGN_WEBHOOK_AUTH_TOKEN="******"
|
98
|
+
```
|
99
|
+
|
100
|
+
### ActiveJob
|
101
|
+
|
102
|
+
Payloads will be dispatched and processed asynchronously using [ActiveJob](https://edgeguides.rubyonrails.org/active_job_basics.html). Southern Made prefers that the app be configured with [Sidekiq](https://github.com/mperham/sidekiq) as the queue adapter.
|
103
|
+
|
104
|
+
You can set the adapter in `config/application.rb` with:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class Application < Rails::Application
|
108
|
+
config.active_job.queue_adapter = :sidekiq
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
Update your Procfile or appropriate config to launch worker processes:
|
113
|
+
|
114
|
+
```
|
115
|
+
worker: RAILS_MAX_THREADS=${SIDEKIQ_CONCURRENCY:-5} bundle exec sidekiq --config config/sidekiq.yml
|
116
|
+
```
|
117
|
+
|
118
|
+
More detailed instructions about using Sidekiq can be found in the [Sidekiq Wiki](https://github.com/mperham/sidekiq/wiki).
|
119
|
+
|
120
|
+
### Mount the Webhook Engine
|
121
|
+
|
122
|
+
If you opted to [auto generate the config](#auto-generate-config), this can be skipped.
|
123
|
+
|
124
|
+
Add the following to `config/routes.rb` in your app to mount the webhook:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
mount SmSmsCampaignWebhook::Engine => "/sms_campaign"
|
128
|
+
```
|
129
|
+
|
130
|
+
This sets the app up to receive POST requests from the SMS campaign service:
|
131
|
+
|
132
|
+
POST /sms_campaign/api/webhook
|
133
|
+
|
134
|
+
Be sure to replace `/sms_campaign` with whatever mount point you choose. Once you share the webhook URI with your project manager, avoid changing it; they will configure it with the correspending SMS campaign!
|
135
|
+
|
136
|
+
### Webhook Initializer
|
137
|
+
|
138
|
+
If you opted to [auto generate the config](#auto-generate-config), this can be skipped.
|
139
|
+
|
140
|
+
App implementors must configure some library options. Here are all supported configuration options identifying their default values for `config/initializers/sm_sms_campaign_webhook.rb`:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
require "sm_sms_campaign_webhook"
|
144
|
+
|
145
|
+
SmSmsCampaignWebhook.config do |config|
|
146
|
+
# SMS campaign payload processor implementing SmSmsCampaignWebhook::Processable behavior.
|
147
|
+
# default: SmSmsCampaignWebhook::DefaultProcessor (raises errors for processing)
|
148
|
+
# config.processor = SmsPayloadProcessor
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### Payload Processor
|
153
|
+
|
154
|
+
If you opted to [auto generate the config](#auto-generate-config), this can be skipped. However, you will still need to implement the processor methods!
|
155
|
+
|
156
|
+
The default payload processor will raise errors while processing. You are required to provide a working payload processor to properly handle the data received from the SMS campaign service.
|
157
|
+
|
158
|
+
To create a processor, create a custom class mixing in `SmSmsCampaignWebhook::Processable` behavior. For example, we can create a custom processor named `SmsPayloadProcessor`:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
class SmsPayloadProcessor
|
162
|
+
include SmSmsCampaignWebhook::Processable
|
163
|
+
|
164
|
+
# Implement required methods for Processable behavior.
|
165
|
+
|
166
|
+
# @param campaign_engagement [SmSmsCampaignWebhook::CampaignEngagement]
|
167
|
+
# def self.process_campaign_engagement(campaign_engagement)
|
168
|
+
# # NOOP - I need to be implemented.
|
169
|
+
# end
|
170
|
+
end
|
171
|
+
```
|
172
|
+
|
173
|
+
This class will continue raising errors until the required methods are implemented. Please see the [Processable mixin](https://github.com/SouthernMade/sm_sms_campaign_webhook/blob/develop/app/processors/sm_sms_campaign_webhook/processable.rb) for expected method definitions.
|
174
|
+
|
175
|
+
Finally the configuration needs to be updated to use the custom processor. Add this within the config block in `config/initializers/sm_sms_campaign_webhook.rb`:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
SmSmsCampaignWebhook.config do |config|
|
179
|
+
#...
|
180
|
+
config.processor = SmsPayloadProcessor
|
181
|
+
#...
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
34
185
|
## Usage
|
35
186
|
|
36
|
-
|
187
|
+
The main goal is to ingest the data contained in payloads received from the SMS campaign service. Your app knows best what to do with the data, so your primary focus is implementing the required methods of a payload processor.
|
188
|
+
|
189
|
+
Assuming that you completed configuration with the [auto generate installer](#auto-generate-config) or [manually created a processor](#payload-processor), this section will expand what to do with it.
|
190
|
+
|
191
|
+
### Campaign Engagement
|
192
|
+
|
193
|
+
This payload represents a user's phone interaction with the SMS campaign. This includes:
|
194
|
+
|
195
|
+
- First contact with a SMS campaign by keyword
|
196
|
+
- Responding to subsequent SMS campaign messages
|
197
|
+
- Continued engagement for multi-entry SMS campaigns
|
198
|
+
|
199
|
+
Payloads will POST to the webhook every time a phone interacts with the campaign, so the processor behavior should expect to see repeats of inbound payloads from a phone!
|
200
|
+
|
201
|
+
It is important that you work closely with your Southern Made project manager to determine which scenarios are relevant for your app. They will be able to tell you:
|
202
|
+
|
203
|
+
- Fields + value types that will be in answers
|
204
|
+
- Required fields to complete registration/entry
|
205
|
+
- How to interpret voting style numeric answers
|
206
|
+
|
207
|
+
#### Processor Expections
|
208
|
+
|
209
|
+
You must behavior for this method to ingest campaign engagement data in your paylod processor:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
def self.process_campaign_engagement(campaign_engagement)
|
213
|
+
# ...
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
It will receive an instance of the [SmSmsCampaignWebhook::CampaignEngagement](https://github.com/SouthernMade/sm_sms_campaign_webhook/blob/develop/app/models/sm_sms_campaign_webhook/campaign_engagement.rb) data model. This method will need to handle scenarios such as:
|
218
|
+
|
219
|
+
- Registering/creating a user account
|
220
|
+
- Logging registration/entries
|
221
|
+
- Interpreting + logging vote responses
|
222
|
+
|
223
|
+
Check with your Southern Made project manager for expectations.
|
224
|
+
|
225
|
+
#### Campaign Engagement Data Model
|
226
|
+
|
227
|
+
The payload will be modeled with the [SmSmsCampaignWebhook::CampaignEngagement](https://github.com/SouthernMade/sm_sms_campaign_webhook/blob/develop/app/models/sm_sms_campaign_webhook/campaign_engagement.rb) class. It provides basic methods to extract values out of the payload. The data model coerces values to the appropriate types in Ruby.
|
228
|
+
|
229
|
+
Some example message passing to an instance:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
campaign_engagement.event_uuid # UUID - unique payload event
|
233
|
+
campaign_engagement.campaign_keyword # String - SMS campaign entry point
|
234
|
+
campaign_engagement.phone_id # Integer - represents specific phone
|
235
|
+
campaign_engagement.phone_number # String - phone number interacting with SMS campaign
|
236
|
+
|
237
|
+
# This represents a specific phone engaging with a specific campaign.
|
238
|
+
# The value will differ for each entry in multi-entry campaigns.
|
239
|
+
# For standard campaigns we will only see one value.
|
240
|
+
campaign_engagement.phone_campaign_state_id # Integer
|
241
|
+
|
242
|
+
# These values help determine if and when answers were received
|
243
|
+
# for all campaign messages.
|
244
|
+
campaign_engagement.phone_campaign_state_completed? # TrueClass,FalseClass
|
245
|
+
campaign_engagement.phone_campaign_state_completed_at # DateTime
|
246
|
+
```
|
247
|
+
|
248
|
+
It also provides a useful helper methods related to campaign engagement answers. For example:
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
# Are any campaign engagement answers in the payload?
|
252
|
+
campaign_engagement.phone_campaign_state_answers? # TrueClass,FalseClass
|
253
|
+
|
254
|
+
# This tries to find an answer for the requested field.
|
255
|
+
# If a match is found it returns instance of
|
256
|
+
# SmSmsCampaignWebhook::CampaignEngagement::Answer data model.
|
257
|
+
# If a match is not found it return nil (NilClass).
|
258
|
+
campaign_enagement.answer_for(field: "email") # Returned type answer specific
|
259
|
+
```
|
260
|
+
|
261
|
+
#### Campaign Engagement Answer Data Model
|
262
|
+
|
263
|
+
The [SmSmsCampaignWebhook::CampaignEngagement::Answer](https://github.com/SouthernMade/sm_sms_campaign_webhook/blob/develop/app/models/sm_sms_campaign_webhook/campaign_engagement/answer.rb) class models the answer data contained in the campaign engagement payload. It consists of:
|
264
|
+
|
265
|
+
- field (`String`)
|
266
|
+
- value (varies)
|
267
|
+
- collected_at (`DateTime`)
|
268
|
+
|
269
|
+
The value data types could be one of the following:
|
270
|
+
|
271
|
+
- string (`String`)
|
272
|
+
- email (`String`)
|
273
|
+
- date (`Date`)
|
274
|
+
- number (`Integer`)
|
275
|
+
- boolean (`TrueClass`, `FalseClass`)
|
276
|
+
- us_state (`String`)
|
37
277
|
|
38
278
|
## Development
|
39
279
|
|
@@ -43,7 +283,6 @@ This gem uses [git-flow](https://github.com/nvie/gitflow) to manage deployments.
|
|
43
283
|
|
44
284
|
Gem versioning follows [Semantic Versioning](https://semver.org).
|
45
285
|
|
46
|
-
|
47
286
|
### Testing
|
48
287
|
|
49
288
|
This project uses Rspec for testing. Specs must be green for any PR to be accepted!
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_controller/metal/http_authentication"
|
4
|
+
require "active_support/security_utils"
|
5
|
+
|
6
|
+
module SmSmsCampaignWebhook
|
7
|
+
# General webhook controller configuration.
|
8
|
+
class ApplicationController < ActionController::API
|
9
|
+
include ActionController::HttpAuthentication::Token::ControllerMethods
|
10
|
+
|
11
|
+
before_action :authenticate
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# Verify auth token is present and matches the configured value.
|
16
|
+
def authenticate
|
17
|
+
authenticate_or_request_with_http_token do |token, options|
|
18
|
+
ActiveSupport::SecurityUtils.secure_compare(token, SmSmsCampaignWebhook.auth_token)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require_dependency "sm_sms_campaign_webhook/application_controller"
|
5
|
+
|
6
|
+
module SmSmsCampaignWebhook
|
7
|
+
# API webhook for POST requests from SMS campaign service.
|
8
|
+
class WebhookController < ApplicationController
|
9
|
+
# POST /api/webhook
|
10
|
+
# @see DispatchPayloadJob#perform
|
11
|
+
def create
|
12
|
+
# Deserialize the payload.
|
13
|
+
payload = JSON.parse(request.body.read)
|
14
|
+
logger.debug "#{self.class} - Payload: #{payload.inspect}"
|
15
|
+
|
16
|
+
# Dispatch the payload to the appropriate payload processor.
|
17
|
+
DispatchPayloadJob.perform_later(payload)
|
18
|
+
|
19
|
+
head :no_content
|
20
|
+
rescue JSON::ParserError => e
|
21
|
+
logger.warn "#{self.class} - Bad Request: #{e.class} - #{e}"
|
22
|
+
head :bad_request
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_dependency "sm_sms_campaign_webhook/application_job"
|
4
|
+
|
5
|
+
module SmSmsCampaignWebhook
|
6
|
+
# Handles SMS campaign payload dispatch to processor async.
|
7
|
+
class DispatchPayloadJob < ApplicationJob
|
8
|
+
# @param payload [Hash] Deserialized payload from SMS campaign service
|
9
|
+
# @see PayloadOperation.dispatch
|
10
|
+
def perform(payload)
|
11
|
+
PayloadOperation.dispatch(payload: payload)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_dependency "sm_sms_campaign_webhook/application_job"
|
4
|
+
|
5
|
+
module SmSmsCampaignWebhook
|
6
|
+
# Handles campaign engagement payload processing async.
|
7
|
+
class ProcessCampaignEngagementJob < ApplicationJob
|
8
|
+
# @param payload [Hash] Campaign engagement event payload
|
9
|
+
# @see CampaignEngagementOperation.process
|
10
|
+
def perform(payload)
|
11
|
+
CampaignEngagementOperation.process(payload: payload)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|