cnfs-comm 0.0.1.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +17 -0
- data/Rakefile +22 -0
- data/app/controllers/campaigns_controller.rb +4 -0
- data/app/controllers/comm/application_controller.rb +6 -0
- data/app/controllers/events_controller.rb +4 -0
- data/app/controllers/messages_controller.rb +19 -0
- data/app/controllers/providers_controller.rb +4 -0
- data/app/controllers/templates_controller.rb +4 -0
- data/app/controllers/whatsapps_controller.rb +73 -0
- data/app/jobs/call_job.rb +8 -0
- data/app/jobs/comm/application_job.rb +6 -0
- data/app/jobs/event_process_job.rb +4 -0
- data/app/jobs/message_process_job.rb +4 -0
- data/app/jobs/message_send_job.rb +4 -0
- data/app/mailers/application_mailer.rb +6 -0
- data/app/models/campaign.rb +17 -0
- data/app/models/comm/application_record.rb +7 -0
- data/app/models/event.rb +81 -0
- data/app/models/message.rb +21 -0
- data/app/models/provider.rb +24 -0
- data/app/models/providers/aws.rb +54 -0
- data/app/models/providers/twilio.rb +37 -0
- data/app/models/template.rb +54 -0
- data/app/models/tenant.rb +7 -0
- data/app/models/whatsapp.rb +4 -0
- data/app/operations/event_process.rb +41 -0
- data/app/operations/message_create.rb +116 -0
- data/app/operations/message_process.rb +17 -0
- data/app/operations/message_send.rb +25 -0
- data/app/policies/campaign_policy.rb +4 -0
- data/app/policies/comm/application_policy.rb +6 -0
- data/app/policies/event_policy.rb +4 -0
- data/app/policies/message_policy.rb +9 -0
- data/app/policies/provider_policy.rb +4 -0
- data/app/policies/template_policy.rb +4 -0
- data/app/resources/campaign_resource.rb +12 -0
- data/app/resources/comm/application_resource.rb +7 -0
- data/app/resources/event_resource.rb +25 -0
- data/app/resources/message_resource.rb +23 -0
- data/app/resources/provider_resource.rb +5 -0
- data/app/resources/template_resource.rb +5 -0
- data/app/resources/twilio_resource.rb +3 -0
- data/app/resources/whatsapp_resource.rb +5 -0
- data/config/environment.rb +0 -0
- data/config/routes.rb +10 -0
- data/config/sidekiq.yml +3 -0
- data/config/spring.rb +3 -0
- data/db/migrate/20190212220055_create_whatsapps.rb +19 -0
- data/db/migrate/20190217113829_create_providers.rb +18 -0
- data/db/migrate/20190317045825_create_campaigns.rb +12 -0
- data/db/migrate/20190317080012_create_templates.rb +12 -0
- data/db/migrate/20190317090002_create_events.rb +19 -0
- data/db/migrate/20190317114527_create_messages.rb +15 -0
- data/db/migrate/20191127070152_add_provider_id_to_message.rb +5 -0
- data/db/seeds/development/data.seeds.rb +35 -0
- data/db/seeds/development/tenants.seeds.rb +8 -0
- data/lib/ros/comm.rb +11 -0
- data/lib/ros/comm/console.rb +8 -0
- data/lib/ros/comm/engine.rb +53 -0
- data/lib/ros/comm/version.rb +7 -0
- data/lib/tasks/ros/comm_tasks.rake +16 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e83faabad39fa0034ef10af4b89f3f19d9070da73bd319edbcdd08763f516756
|
4
|
+
data.tar.gz: 2975ef6f8729dee40fab0a4524ba2fb30d91e31ecc10dd7c571177e6b3983e7c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 233b5c4d2200924ec43fb1f678ba93114a77573e1bb88d698a31f40cd3aa0c1fe15cd053878a993196e457abee47bae711ff3dc6ecbb962fa67b06f87ed98b57
|
7
|
+
data.tar.gz: 21a3f71ee15d2fcd1c1ebbcb1706e9fa9173bf202f7e934f43c5a67cb2d7910dc88513f756475125d24344a572be9b82e5da408549ce9653593518c32189e580
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2019 Robert Roach
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
## Summary
|
2
|
+
|
3
|
+
Created by rails-templates
|
4
|
+
|
5
|
+
|
6
|
+
def tcall(from: '+12565308753', to: '+6581132988')
|
7
|
+
@twilio_client.calls.create(to: to, from: from, url: 'http://demo.twilio.com/docs/voice.xml')
|
8
|
+
end
|
9
|
+
|
10
|
+
def tsms(from: '+12565308753', to: '+6581132988', body: 'Hey friend!')
|
11
|
+
@twilio_client.messages.create(
|
12
|
+
from: from,
|
13
|
+
to: to,
|
14
|
+
body: body
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Ros::Comm'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MessagesController < Comm::ApplicationController
|
4
|
+
def create
|
5
|
+
res = MessageCreate.call(params: assign_params, user: context[:user])
|
6
|
+
if res.success?
|
7
|
+
render json: json_resource(resource_class: MessageResource, record: res.model), status: :created
|
8
|
+
else
|
9
|
+
resource = ApplicationResource.new(res, nil)
|
10
|
+
handle_exceptions JSONAPI::Exceptions::ValidationErrors.new(resource)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def assign_params
|
17
|
+
jsonapi_params.permit(MessageResource.creatable_fields(context))
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# module ServiceTwilio
|
4
|
+
class WhatsappsController < Comm::ApplicationController
|
5
|
+
# skip_before_action :authenticate_user!
|
6
|
+
# before_action :set_whatsapp, only: [:show, :update, :destroy]
|
7
|
+
|
8
|
+
# GET /whatsapps
|
9
|
+
def index
|
10
|
+
@whatsapps = Whatsapp.all
|
11
|
+
|
12
|
+
render json: @whatsapps
|
13
|
+
end
|
14
|
+
|
15
|
+
# GET /whatsapps/1
|
16
|
+
def show
|
17
|
+
render json: @whatsapp
|
18
|
+
end
|
19
|
+
|
20
|
+
# POST /whatsapps
|
21
|
+
# NOTE: This is an endpoint that response to a Twilio notification
|
22
|
+
# when a whatsapp message is sent to a registered number
|
23
|
+
def create
|
24
|
+
@whatsapp = Whatsapp.new(whatsapp_params)
|
25
|
+
|
26
|
+
who = 'Blob'
|
27
|
+
who = 'Narayani' if @whatsapp.from.ends_with? '26'
|
28
|
+
|
29
|
+
if @whatsapp.save
|
30
|
+
# Put a message on a bus (rabbitMQ)
|
31
|
+
# Channels are named in a standardized way including service and tenant
|
32
|
+
# TODO:
|
33
|
+
# UserSpace, e.g. perx, truewards, LoyatlyCampaign listens on bus and sends message back
|
34
|
+
# Twilio service listens on bus and sends message
|
35
|
+
# Something like this
|
36
|
+
twiml = Twilio::TwiML::MessagingResponse.new do |r|
|
37
|
+
r.message(body: "Ahoy #{who}! Thanks so much for your message that said: '#{@whatsapp.Body}'")
|
38
|
+
end
|
39
|
+
render xml: twiml
|
40
|
+
else
|
41
|
+
# render json: @whatsapp, status: :created, location: @whatsapp
|
42
|
+
render json: @whatsapp.errors, status: :unprocessable_entity
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# PATCH/PUT /whatsapps/1
|
47
|
+
def update
|
48
|
+
if @whatsapp.update(whatsapp_params)
|
49
|
+
render json: @whatsapp
|
50
|
+
else
|
51
|
+
render json: @whatsapp.errors, status: :unprocessable_entity
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# DELETE /whatsapps/1
|
56
|
+
def destroy
|
57
|
+
@whatsapp.destroy
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Use callbacks to share common setup or constraints between actions.
|
63
|
+
def set_whatsapp
|
64
|
+
@whatsapp = Whatsapp.find(params[:id])
|
65
|
+
end
|
66
|
+
|
67
|
+
# Only allow a trusted parameter "white list" through.
|
68
|
+
# NOTE: Twilio sends params as SmsStatus
|
69
|
+
def whatsapp_params
|
70
|
+
request.request_parameters.deep_transform_keys!(&:underscore).except(:controller, :action)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
# end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Campaign < Comm::ApplicationRecord
|
4
|
+
has_many :events
|
5
|
+
has_many :templates
|
6
|
+
|
7
|
+
before_save :set_base_url
|
8
|
+
|
9
|
+
def set_base_url
|
10
|
+
# https://{{tenant}}-blackcomb-sales.uat.whistler.perxtech.io/
|
11
|
+
self.base_url ||= current_tenant.properties.fetch(:campaign_base_url, '')
|
12
|
+
end
|
13
|
+
|
14
|
+
def final_url
|
15
|
+
"#{base_url}loading/"
|
16
|
+
end
|
17
|
+
end
|
data/app/models/event.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Event < Comm::ApplicationRecord
|
4
|
+
# - includes/extends
|
5
|
+
include AASM
|
6
|
+
|
7
|
+
# - constants
|
8
|
+
|
9
|
+
# - gems and related
|
10
|
+
# TODO: we should have an extra status that informs us that all the messages
|
11
|
+
# for this event have been scheduled
|
12
|
+
aasm whiny_transitions: true, column: :status do
|
13
|
+
state :pending, initial: true
|
14
|
+
state :processing, :published
|
15
|
+
|
16
|
+
after_all_transitions :log_status_change
|
17
|
+
|
18
|
+
event :process do
|
19
|
+
transitions from: :pending, to: :processing
|
20
|
+
end
|
21
|
+
|
22
|
+
event :publish do
|
23
|
+
transitions from: :processing, to: :published
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# - serialized attributes
|
28
|
+
|
29
|
+
# - associations
|
30
|
+
belongs_to :template, optional: true
|
31
|
+
belongs_to :provider
|
32
|
+
belongs_to :campaign, optional: true
|
33
|
+
# maybe target should be cognito_pool_id
|
34
|
+
belongs_to_resource :target, polymorphic: true
|
35
|
+
belongs_to_resource :owner, polymorphic: true
|
36
|
+
|
37
|
+
has_many :messages, as: :owner
|
38
|
+
# api_has_many :users, through: :target
|
39
|
+
|
40
|
+
# - attr_accessible
|
41
|
+
|
42
|
+
# - scopes
|
43
|
+
|
44
|
+
# - class methods
|
45
|
+
|
46
|
+
# - validations
|
47
|
+
validate :provider_channel
|
48
|
+
# NOTE: if there channel is not weblink, then target is mandatory
|
49
|
+
validates :target, presence: true, if: -> { channel != 'weblink' }
|
50
|
+
|
51
|
+
# - callbacks
|
52
|
+
after_commit :queue_job, on: :create
|
53
|
+
|
54
|
+
# - other methods
|
55
|
+
|
56
|
+
# TODO: Decide if the target is always a Pool or not
|
57
|
+
# TODO: Implement as api_has_many :users, through: :target
|
58
|
+
def users
|
59
|
+
query_resource(:target) { |query| query.includes(:users) }.users
|
60
|
+
end
|
61
|
+
|
62
|
+
def log_status_change
|
63
|
+
Rails.logger.info("changing from #{aasm.from_state} to #{aasm.to_state} (event: #{aasm.current_event})")
|
64
|
+
end
|
65
|
+
|
66
|
+
# - private
|
67
|
+
private
|
68
|
+
|
69
|
+
def provider_channel
|
70
|
+
return unless provider
|
71
|
+
|
72
|
+
channels = provider.class.services + ['weblink']
|
73
|
+
return if channel.in? channels
|
74
|
+
|
75
|
+
errors.add(:channel, "must be one of: #{channels.join(' ')}")
|
76
|
+
end
|
77
|
+
|
78
|
+
def queue_job
|
79
|
+
EventProcessJob.set(wait_until: send_at).perform_later(id: id)
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Message < Comm::ApplicationRecord
|
4
|
+
belongs_to :provider
|
5
|
+
belongs_to :owner, polymorphic: true, optional: true
|
6
|
+
|
7
|
+
attr_accessor :recipient_id
|
8
|
+
|
9
|
+
def self.sent_to(phone_number)
|
10
|
+
search_number = phone_number.tr('^0-9', '%')
|
11
|
+
where('messages.to LIKE ?', search_number)
|
12
|
+
end
|
13
|
+
|
14
|
+
validate :provider_channel, if: :provider
|
15
|
+
|
16
|
+
def provider_channel
|
17
|
+
return if channel.in? provider.class.services
|
18
|
+
|
19
|
+
errors.add(:channel, "must be one of: #{provider.class.services.join(' ')}")
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Provider < Comm::ApplicationRecord
|
4
|
+
attr_reader :client
|
5
|
+
|
6
|
+
attr_encrypted_options.merge!(key: Settings.encryption_key, encode: true, encode_iv: true)
|
7
|
+
attr_encrypted :credential_1
|
8
|
+
attr_encrypted :credential_2
|
9
|
+
attr_encrypted :credential_3
|
10
|
+
|
11
|
+
validates :type, presence: true
|
12
|
+
|
13
|
+
def self.services
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
|
17
|
+
def provider_from
|
18
|
+
current_tenant.properties.dig(:from) || 'Perx'
|
19
|
+
end
|
20
|
+
|
21
|
+
def sms
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Providers
|
4
|
+
class Aws < Provider
|
5
|
+
alias_attribute :access_key_id, :credential_1
|
6
|
+
alias_attribute :secret_access_key, :credential_2
|
7
|
+
|
8
|
+
def self.services
|
9
|
+
%w[sms]
|
10
|
+
end
|
11
|
+
|
12
|
+
def client
|
13
|
+
return unless x_access_key_id && x_secret_access_key
|
14
|
+
|
15
|
+
@client ||= ::Aws::SNS::Client.new(client_params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def x_access_key_id
|
19
|
+
access_key_id || (current_tenant.platform_aws_enabled ? ENV['AWS_ACCESS_KEY_ID'] : nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
def x_secret_access_key
|
23
|
+
secret_access_key || (current_tenant.platform_aws_enabled ? ENV['AWS_SECRET_ACCESS_KEY'] : nil)
|
24
|
+
end
|
25
|
+
|
26
|
+
def provider_from
|
27
|
+
current_tenant.properties.dig(:from) || 'Perx'
|
28
|
+
end
|
29
|
+
|
30
|
+
def sms(from, to, body)
|
31
|
+
sender = from || provider_from
|
32
|
+
client.set_sms_attributes(attributes: { 'DefaultSenderID' => sender })
|
33
|
+
client.publish(phone_number: to, message: body)
|
34
|
+
rescue ::Aws::SNS::Errors::ServiceError => e
|
35
|
+
Rails.logger.warn("No AWS client configured for tenant.account_id. #{e.inspect}")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# TODO: Cleanup this logic. This should probably live in an initializer.
|
41
|
+
# The problem might be that a tenant uses Perx SNS credentials for sending
|
42
|
+
# the sms but then wants to use his own credentials for storing the assets
|
43
|
+
# in the S3. We should have the configuration being picked up from
|
44
|
+
# 1. settings, env variables, our defaults
|
45
|
+
def client_params
|
46
|
+
params = { region: 'ap-southeast-1',
|
47
|
+
access_key_id: x_access_key_id,
|
48
|
+
secret_access_key: x_secret_access_key }
|
49
|
+
|
50
|
+
params[:endpoint] = ENV['AWS_ENDPOINT'] unless ENV['AWS_ENDPOINT'].nil?
|
51
|
+
params
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|